LANG SELRCT

Apps Script Reference  (Create: Create new Spreadsheet | Create new Apps Script

Monday, September 23, 2019

Googleカレンダーに予定を入れるプログラムを作りたい


createEvent(title, startTime, endTime, options)
でできるようです。


guestsが複数の場合は、配列ではなく、文字列でカンマを区切るようです。
a comma-separated list of email addresses that should be added as guests



コード.gs
function addCalendar() {
  var event = CalendarApp.getDefaultCalendar();
  var title = 'タイトル';
  var startTime = new Date('2019/09/23 12:00');
  var endTime = new Date('2019/09/23 13:00');
  var option = {
    description: '説明',
    location: '成田空港',
    guests: 'foo@gmail.com,bar@gmail.com',
    sendInvites: false
  }
  event.createEvent(title, startTime, endTime, option)
  Logger.log('Event ID: ' + event.getId());
}

意訳
この機能がやること
デフォルトのカレンダーを取得する
タイトル
開始日時
終了日時

説明
場所
参加者のメールアドレス(複数の場合はカンマ区切りの文字列)
ゲストに送信するか否か(true / false)

カレンダーに予定を入れる
Event IDをログに出す





Friday, September 20, 2019

G suiteの管理者コンソールでアドオンを削除したい


G suiteの管理者機能で、スプレッドシートのadd onをドメイン内で有効にした際、それを無効にする方法が知りたくなって調べてみると
G Suite から Marketplace アプリを削除する
という公式ヘルプがありました。


Marketplace アプリの管理
https://admin.google.com/AdminHome?hl=ja#AppsList:serviceType=MARKETPLACE

ここでオンオフの切り替えや削除ができるようだ。


参考

G Suite から Marketplace アプリを削除する

Wednesday, September 18, 2019

IFTTTで特定のエリアに入ったらLINEに通知したい


IFTTTを利用して、地図上であらかじめ指定したエリアに入ったらLINEに通知する

ということをやっていきます。


必要なもの
  • Googleアカウント
  • LINEアカウント
  • 位置情報をONにしたスマートフォン(Android端末でのみ検証済)


まずは「This」をクリックして


検索ボックスに「Location」と入力して出てきたLocationアイコンをクリックします。


左から「エリアに入った時」「エリアから出た時」「その両方」という選択肢があって
ここでは一番右の「You enter or exit an area」を選択します。
(入ったとき(enter)だけなら一番左、出たとき(exit)だけなら真ん中を選択します)


入力ボックスに住所や駅名などを入れるとその場所の地図が表示されるので
「ー」「+」で範囲を調整して
「Create trigger」をクリックします。


「That」をクリックして


入力ボックスに「LINE」と入力して出てくるLINEのアイコンをクリックします。


「Send message」のアイコンをクリックして


「Recipient」で通知先を選択して「Create action」をクリックします。


「Finish」をクリックして設定完了です。


実行結果

その後、スマホを持って指定したエリアに入ると
「You entered an area」と通知が来て
そのエリアから出ると
「You exited an area」と通知がきます。

(エリアに入って(出て)すぐではなく数分後に通知が来ます)


位置情報がトリガーになるため、移動しないと結果を得られないのはちょっとおもしろい体験でした。


IFTTTのアカウントを作成する(Google連携)


IFTTTのアカウントを作成する手順を書きます。
(ここではGoogleアカウントで連携してみます)



ログイン画面を開きます
https://ifttt.com/join



「Continue with Google」をクリックします



自分のGoogleアカウントを選択します


これでアカウントが作成されてIFTTTが使えるようになりました


スマートフォンでもアプリを作成できるので、インストールしておくと便利です。



アプリの作成は

「Get more」の横のアイコンをクリックして



「Create」を選択して



ここから「This」と「That」でやりたいことを選択していきます




関連記事

IFTTTで特定のエリアに入ったらLINEに通知したい


JIRAのカンバンボードで完了になって1週間更新がなかったらボードから消したい


完了をずっと表示続けると縦長になっていくので、完了してから1週間更新がなかったらボードに表示しない設定を考えてボードの設定をしました。


自分の環境でのJQLですが、たぶんこんな感じでいけるはず

project = KT
AND (status in ("BACKLOG", "In Progress") OR (status in ("DONE") AND updatedDate > startOfWeek(-1)))
ORDER BY Rank ASC


設定手順

右の…メニューから「ボード設定」を選択


基本メニューの「フィルタクエリを編集」をクリック


プロジェクト名ステータスを環境に合わせて変更して検索する

project = KT
AND (status in ("BACKLOG", "In Progress") OR (status in ("DONE") AND updatedDate > startOfWeek(-1))) 
ORDER BY Rank ASC

検索結果を「保存する」


Tuesday, September 17, 2019

JIRAの権限スキームで添付ファイルのアップロードを不可にしたい


プロジェクト設定をクリック


権限 > アクション > 権限を編集を選択


添付ファイルの作成の「削除」をクリック


削除したい権限を選択して「削除」をクリック



補足

対象の権限スキームを使っているプロジェクト全てに影響するため、個別のプロジェクトだけに設定したい場合は、権限スキームを分ける必要があります。



参考

添付ファイルの設定
https://ja.confluence.atlassian.com/adminjiracloud/configuring-file-attachments-818578880.html

Saturday, September 14, 2019

Google Formで一度選択された値を次回フォームを開いた時の選択肢に表示しない


以下のようなケースを実現したくてフォーム側のスクリプトエディタに書いたコードです

  1. 特定の質問の選択肢をスプレッドシートから読み込む
  2. フォーム送信後、選択された値に一致する行のB列にメールアドレスを入れる
  3. B列に値が入っていないA列の値のみ取得してフォームの選択肢を書き換える


itemId と ss_url を設定して
afterSubmit
をフォーム送信時のトリガーに設定する


コード.gs
var itemId = フォームの質問のID;
var ss_url = "https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit#gid=0";
var sheet = returnSheet(ss_url);
var email = Session.getActiveUser().getEmail();

/************************************
フォーム送信時のトリガーに設定しておく
************************************/
function afterSubmit(e) {
  var obj = getItemAnswers(e);
  var value = getTargetAnswer(obj)
  var sheet = returnSheet(ss_url);
  var email = e.response.getRespondentEmail();
  setValueToSheet(value, email);
  getUpdatedValuesFromSheet(sheet);
}

/************************************
シートを返す
************************************/
function returnSheet(ss_url) {
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sh_id = ss_url.split("gid=")[1];
  var shs = ss.getSheets();
  for (var i = 0; i < shs.length; i++) {
    if (shs[i].getSheetId() == sh_id) {
      break;
    }
  }
  return ss.getSheets()[i];
}

/************************************
フォームで回答された値をシートに入力する
************************************/
function setValueToSheet(value, email) {
  var row = findRow(value);
  Logger.log(row)
  if(value !== "none") {
    sheet.getRange(row, 2).setValue(email);
  }
}

/************************************
渡されたvalue, colから対象の行を見つける
************************************/
function findRow(value){
  var array = createArray();
  var row = array.indexOf(value) + 1;
  return row;
}

/************************************
1行目、1列目からlast_row行、2列の範囲の値を取得して返す
************************************/
function createArray() {
  var last_row = sheet.getLastRow();
  var range = sheet.getRange(1, 1, last_row, 2);
  var values = range.getValues();
  var array = [];
  for(var i = 0; i < values.length; i++){
    array.push(values[i][0]);
  }
  return array;
}

/************************************
更新されたシートで2列目に値が入っていない1列目の値を配列で返す
************************************/
function getUpdatedValuesFromSheet() {
  var lastRow = sheet.getLastRow();
  var range = sheet.getRange(2, 1, lastRow-1, 2);
  var values = range.getValues();
  var choices = [];
  for(var i = 0; i < values.length; i++) {
    if(values[i][1] === "") {
      choices.push(values[i][0]);
    }
  }
  setChoiceValues(itemId, choices);
}

/************************************
フォームのitemIdの質問の選択肢を書き換える
************************************/
function setChoiceValues(itemId, values) {
  var form = FormApp.getActiveForm();
  var item = form.getItemById(itemId);
  if(values.length < 1) {
    values = ["none"];
  }
  var choices = item.asMultipleChoiceItem().setChoiceValues(values);
}

/************************************
送信時の回答を取得して返す
************************************/
function getItemAnswers(e) {
 FormApp.getActiveForm();
  var itemResponses = e.response.getItemResponses();
  var obj = {};
  for (var i = 0; i < itemResponses.length; i++) {
    var itemResponse = itemResponses[i];
    var item_id = itemResponse.getItem().getId();
    var question = itemResponse.getItem().getTitle();
    var answer = itemResponse.getResponse();
    obj[item_id] = answer;
  }
  return obj;
}

/************************************
itemIdの回答を返す
************************************/
function getTargetAnswer(obj) {
  var targetAnswer = obj[itemId];
  return targetAnswer;
}




補足

残る課題

  • 複数人が同時にフォームを開いた場合は、後から開いた方の回答が反映されない
    • シートへのリンクをフォームに貼ってシートを直接編集したほうが確実かも
  • シートが変更されたときに実行するトリガーでできたらその方が良いかも



関連記事

Google Formの質問項目を取得する
Google Formで質問の選択肢の値を取得したい
Google Formの質問の選択肢をコードで書き換えたい
Google Formの質問項目をスプレッドシートから読み込みたい
Google FormのonOpenトリガーはエディタに対して実行されるらしい

Google Formの質問項目をスプレッドシートから読み込みたい


このようにA列に値を入力したシートがあって


A列の2行目以降の値をフォームの特定の質問の選択肢に入れたい



事前準備


以下のコード.gsで設定する箇所

  • itemId:項目を入れたいフォームの質問のID
  • ss_url:A列に項目用の値が入っているスプレッドシートのURL


コード.gs
var itemId = フォームの質問のID;
var ss_url = "https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit#gid=0";

function setChoicesFromSheet() {  
  var sheet = returnSheet(ss_url);
  var values = getValuesFromSheet(sheet);
  setChoiceValues(itemId, values);
}

function returnSheet(ss_url) {
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sh_id = ss_url.split("gid=")[1];
  var shs = ss.getSheets();
  for (var i = 0; i < shs.length; i++) {
    if (shs[i].getSheetId() == sh_id) {
      break;
    }
  }
  var sheet = ss.getSheets()[i];
  return sheet;
}

function getValuesFromSheet(sheet) {
  var lastRow = sheet.getLastRow();
  var range = sheet.getRange(2, 1, lastRow-1, 1);
  var values = range.getValues();
  var choices = [];
  for(var i = 0; i < values.length; i++) {
    choices.push(values[i][0]);
  }
  return choices;
}

function setChoiceValues(itemId, values) {
  var form = FormApp.getActiveForm();
  var item = form.getItemById(itemId);
  var choices = item.asMultipleChoiceItem().setChoiceValues(values);
}


itemId と ss_url を設定して
setChoicesFromSheet()
を実行すると
フォームの質問の選択肢がシートの値に書き換えられます


関連記事

Google Formの質問項目を取得する
Google Formで質問の選択肢の値を取得したい
Google Formの質問の選択肢をコードで書き換えたい

Google FormのonOpenトリガーはエディタに対して実行されるらしい


Google フォームを開いたときに
スプレッドシートなどからデータを読み込んで
フォームの質問項目を可変にできないか考えた時にぶつかりました。

フォームを開いたときに実行する処理は onOpen ではできなそうだとわかったので備忘録として。

(2019/09/14現在)



Simple Triggers > onOpen(e) に書いてありました
https://developers.google.com/apps-script/guides/triggers/#onopene

The onOpen(e) trigger runs automatically when a user opens a spreadsheet, document, presentation, or form that they have permission to edit. (The trigger does not run when responding to a form, only when opening the form to edit it.) onOpen(e) is most commonly used to add custom menu items to Google Sheets, Slides, Docs, or Forms.

onOpen(e)トリガーは、ユーザーが編集権限のあるスプレッドシート、ドキュメント、プレゼンテーション、またはフォームを開くと自動的に実行されます。 (フォームに応答するときはトリガーを実行せず、フォームを開いて編集するときのみ。)onOpen(e)は、Googleスプレッドシート、スライド、ドキュメント、またはフォームにカスタムメニュー項目を追加するために最も一般的に使用されます。

つまり、編集権限のあるフォームを開いたときに実行されるので、回答するフォームを開いたときには実行されないということか。



「フォームを編集するエディタ画面」に対して onOpenは実行される


「回答するフォーム画面」に対して onOpenは実行されない


補足

できないとわかったところで、じゃあどうすればいいのかを考えてみる
質問の選択肢の値変更をフォームに反映するアイデア

アイデア1
トリガーで1分おきにシートの情報を見てフォームの項目を更新し続けようか?
  • これは楽だけど無駄な処理が発生し続ける

アイデア2
開いたときではなく、送信したときに更新したらどう?
  • フォームのトリガーには「onOpen」のほかに「onSubmit」がある
  • onSubmitは「送信」ボタンが押されたことをトリガーに発動する
  • 送信する度に選択肢の中身を変えたい場合に使えそう
  • 早いものがちで選択肢が減っていくとかやりたい場合

アイデア3
スプレッドシートの特定の範囲が編集されたらフォームの項目を更新する?
  • スプレッドシート側にonEditのトリガーを設定しておけばできそう

アイデア4
自動化は諦めて手動でスクリプトを実行する?
  • 項目を変更するのが手動ならそのときにスクリプトも手動実行する

アイデア5
すなおにフォームのエディタを開いて手動で項目の値を変更する
  • コードもトリガーも設定せずに手動でやっちゃう
  • 変更する頻度が少なければこれでよさそう


参考

Simple Triggers > onOpen(e)
https://developers.google.com/apps-script/guides/triggers/#onopene

Google Formの質問の選択肢をコードで書き換えたい


このように
「はれ」「あめ」「くもり」の選択肢がある質問で


選択肢に「ゆき」を追加してみる


実行手順
  1. 質問のIDを取得する
  2. 質問のIDを指定して選択肢を書き換える



コード.gs
function get_items() {// 1. 質問のIDを取得する
  var form = FormApp.getActiveForm();
  var items = form.getItems();
  var arrays = [];
  for (var i = 0; i < items.length; i++) {
    var obj = {};
    var item = items[i];
    obj['text'] = item.getHelpText();
    obj['id'] = item.getId();
    obj['index'] = item.getIndex();
    obj['title'] = item.getTitle();
    obj['type'] = item.getType();
    arrays.push(obj);
  }
  Logger.log(arrays);
}

function setChoiceValues() {// 2. 質問のIDを指定して選択肢を書き換える
  var itemId = 1167513156;
  var form = FormApp.getActiveForm();
  var item = form.getItemById(itemId);
  var values = ["はれ", "あめ", "くもり", "ゆき"];
  var choices = item.asMultipleChoiceItem().setChoiceValues(values);
}


まず、get_items() を実行すると

このようなログが出力されるので


この中の id=1167513156 がその質問のIDなので
(※IDは実行する環境によって異なるので自分のログに出た数値を使います)

setChoiceValues()の

var itemId = 1167513156

に入れて

setChoiceValues() を実行すると

var values = ["はれ", "あめ", "くもり", "ゆき"]
で設定した値で選択肢が作られます



関連記事

Google Formで質問の選択肢の値を取得したい

Google Formで質問の選択肢の値を取得したい


このような
ラジオボタンで「はれ」「あめ」「くもり」を選択する
フォームがあって

質問の選択肢の値を取得したいとき


以下の手順でやってみました

  1. 質問項目のタイトルやIDを取得する
  2. 指定したIDの質問の選択肢の値を取得する



1. 質問項目のタイトルやIDを取得する

コード.gs
function get_items() {
  var form = FormApp.getActiveForm();
  var items = form.getItems();
  var arrays = [];
  for (var i = 0; i < items.length; i++) {
    var obj = {};
    var item = items[i];
    obj['text'] = item.getHelpText();
    obj['id'] = item.getId();
    obj['index'] = item.getIndex();
    obj['title'] = item.getTitle();
    obj['type'] = item.getType();
    arrays.push(obj);
  }
  Logger.log(arrays);
}


実行結果


2. 指定したIDの質問の選択肢の値を取得する

コード.gs
function getChoiceValues() {
  var itemId = 1167513156;// itemのID
  var form = FormApp.getActiveForm();
  var item = form.getItemById(itemId).asMultipleChoiceItem();
  var choices = item.getChoices();
  for(var i = 0; i < choices.length; i++) {
    var choice = choices[i].getValue();
    Logger.log(choice);
  }
}


実行結果

これで選択肢の値を取得することができました


Thursday, September 12, 2019

JIRA APIでissueの変更履歴を取得する4


JIRA APIでissueの変更履歴を取得する
の発展形で、実行を自動化したときに書いたコードです。


やりたいこと

JIRAのissue keyをA列に取得しておいて
それらのステータスの最終変更日時を取得してシートに書き出したい。

ということをやるときに以下の点も考慮しました。
  • 対象のissue keyが多いとGASの6分の実行制限にかかるので
  • 1課題1秒くらいとして1分間に余裕持って50課題の履歴を取得する
  • 1分ごとのトリガーを用意して
  • ヘッダにstatus_last_changed, from, toの列を自動追加して
  • 次回実行はstatus_last_changedの列で値が入力されている次の行から
  • 前回の実行が終わっていなければもう1分待つ
  • 最後の実行時に残りが50課題も無ければあるだけ実行して
  • トリガーを自動で削除する



コードは長くなりましたが、以下の4つの変数を設定すれば動くように書きました。
  • ISSUE_URL
  • ss_url
  • rownums
  • issue_key_col



事前準備
  • JIRAのissue keyをA列に書き出しておく
  • スクリプトのプロパティにtokenという名でbasic認証用の文字列を入れておく



関連記事
JIRA APIでissueの変更履歴を取得する
JIRA APIでissueの変更履歴を取得する2
JIRA APIでissueの変更履歴を取得する3



コード.gs
var ISSUE_URL = 'https://SITENAME.atlassian.net/rest/api/2/issue/';
var ss_url = "https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit#gid=0";
var rownums = 50;
var issue_key_col = 1;// A列にissue keyがある想定で1列目


/************************************
初回実行でトリガーを登録する
************************************/
function initialize() {
  create_header_values();
  console.log('トリガーを登録します' + new Date());
  set_property('running', false);
  createTriggerEvery1Minutes();
}

function create_header_values() {
  var sheet = get_sheet();
  var valeus = [
   "status_last_changed",
   "from",
   "to"
  ];
  set_header(sheet, valeus);
}

/************************************
トリガーを作成する
************************************/
function createTriggerEvery1Minutes() {
  ScriptApp.newTrigger('run')
           .timeBased()
           .everyMinutes(1)
           .create();
}

/************************************
run 1分ごとに実行される
************************************/
function run() {
  if(getProps('running') === 'true') {
    console.log('前の処理が終わっていないので次の1分を待つ' + new Date())
  } else {
    console.log('前の処理が終わっているので実行する' + new Date())
    set_property('running', true);
    get_last_status_change_date();
  }
}

/************************************
runで判定後に実行される
シートを取得して
シートからJIRAのissue keyを取得して
最終ステータスの日時と何から何へを取得して
シートに書き込む
************************************/
function get_last_status_change_date() {
  var sheet = get_sheet();
  var keys = get_keys(sheet);// ["KT-6", "KT-7"];// 列で値が入っている最終行の下から50行分取得して1分おきにトリガーセットして完了したらトリガー削除
  var last_statuses = get_last_statuses(keys);
  var values = create_value(last_statuses, sheet);
  set_values(values, sheet);
  set_property('running', false);
}

/************************************
指定したss_urlのシートの0番目のシートを取得して返す
************************************/
function get_sheet() {
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sheet = ss.getSheets()[0];
  return sheet;
}

/************************************
シート内のA列の2行目から入力されているissue keyを取得して
二次元配列で取得されるので一次元配列にして返す
************************************/
function get_keys(sheet) {
  //var col = 2; // B列
  var col = getProps("start_col");
  var last_row = get_last_row(sheet, col);
  var rangeA = sheet.getRange(last_row + 1, issue_key_col, rownums, 1);
  var valuesA = rangeA.getValues();
  var keys = array_push_apply(valuesA);
  Logger.log(keys);
  return keys;
}

/************************************
二次元配列を一次元配列にして返す
************************************/
function array_push_apply(arrays){
  for(var i = 1; i < arrays.length; i++){
    Array.prototype.push.apply(arrays[0], arrays[i]);
  }
  return arrays[0];
}

/************************************
issue keyの最後のステータス変更を取得して
last_statuses配列に入れて返す
************************************/
function get_last_statuses(keys) {
  var last_statuses = [];
  for(var i = 0; i < keys.length; i++) {
    var key = keys[i];
    if(key === '') {// keyがなくなれば
      console.log('トリガーを削除します' + new Date())
      deleteTrigger("run");// トリガーを削除する
      break;
    } else { // keyがあれば
     var latest_status = get_latest_status(key);
     last_statuses.push(latest_status);
   }
  }
  return last_statuses;
}

/************************************
issue keyの最後のステータス変更の履歴を取得して返す
************************************/
function get_latest_status(key) {
  var histories = get_issue_histories(key);
  var arrays = get_status(histories);
  var latest_status = arrays[0];// descにした配列の先頭がlatest
  return latest_status;
}

/************************************
issueの情報からchangelogのhistoriesを取得して返す
************************************/
function get_issue_histories(key) {
  var response = get_issue(key);
  var jobj = JSON.parse(response);
  var histories = jobj["changelog"]["histories"];
  return histories;
}

/************************************
issueの情報を?expand=changelog付けて取得して返す
************************************/
function get_issue(key) {
  var token = getProp("token");
  var options = {
    contentType: "application/json",
    headers: {"Authorization": " Basic " + token}
  };
  var url = ISSUE_URL + key + "?expand=changelog";
  var response = UrlFetchApp.fetch(url, options);
  return response;
}

/************************************
histories(履歴)の数だけ繰り返す
さらに履歴の中のitemsの数だけ繰り返す
fieldがstatusならcreatedとfromとtoのオブジェクトを作って
arrays配列に入れて
descending_arrayで日時を基準に降順にして返す
************************************/
function get_status(histories) {
  var arrays = [];
  for(var i = 0; i < histories.length; i++) {
    var history = histories[i];
    var created = format_date(history["created"]);
    var items = history["items"];
    for(var j = 0; j < items.length; j++) {
      var item = items[j];
      var field = item["field"];
      if(field === "status") {
        var obj = {};
        obj["created"] = created;
        obj["from"] = item["fromString"];
        obj["to"] = item["toString"];
        arrays.push(obj);
      }
    }
  }
  var desc = descending_array(arrays);
  return desc;  
}

/************************************
シートに入れる値を作って入れる
************************************/
function create_value(last_statuses, sheet) {
  var values = [];
  for(var i = 0; i < last_statuses.length; i++) {
    var latest_status = last_statuses[i];
    if(latest_status === undefined) {
      values.push(["-", "-", "-"]);// statusの変更履歴がなければ-にする→しないとlatest_status["created"]などがエラーになるのでここで回避
      //set_values([["-", "-", "-"]], sheet)
    } else {
      var created = latest_status["created"];
      var from = latest_status["from"];
      var to = latest_status["to"];
      values.push([created, from, to]);
      //set_values([[created, from, to]], sheet)
    }
  }
  return values;
}

/************************************
シートに値を入れる
************************************/
function set_values(array, sheet){
  //var start_col = 2;
  var start_col = getProps("start_col");
  var last_row = get_last_row(sheet, start_col);
  var start_row = last_row + 1;
  var num_rows = array.length;
  var num_cols = array[0].length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(array); 
}

/************************************
指定したcol(列)の値が入っている最終行を取得して返す
************************************/
function get_last_row(sheet, col) {
  var start_row = 1;
  var num_cols = 1;
  var sh_last_row = sheet.getLastRow();
  var values = sheet.getRange(start_row, col, sh_last_row, num_cols).getValues();
  for (var i = values.length - 1; i >= 0; i--) {
    if (values[i] != "") {
      break;
    }
  }
  var last_row = i + 1;
  return last_row;
}

/************************************
受け取ったkeyの値をスクリプトのプロパティから返す
************************************/
function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}

/************************************
日時をフォーマットして返す
************************************/
function format_date(date) {
  return Utilities.formatDate(new Date(date), "asia/tokyo", "yyyy-MM-dd HH:mm:ss");
}

/************************************
日時を降順にした配列を返す
************************************/
function descending_array(array) {
  var descending = array.sort(sorting_desc);
  return descending;
}

/************************************
array配列内の日時を降順にする
************************************/
function sorting_desc(a, b){
  if(a[0] > b[0]){
    return -1;
  }else if(a[0] < b[0] ){
    return 1;
  }else{
   return 0;
  }
}

/************************************
トリガーを削除する
************************************/
function deleteTrigger(functionName) {
  var allTriggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < allTriggers.length; i++) {
    if (allTriggers[i].getHandlerFunction() == functionName) {
      ScriptApp.deleteTrigger(allTriggers[i]);
      break;
    }
  }
}

/************************************
スクリプトのプロパティを読み書きする
************************************/
var ScriptProperties = PropertiesService.getScriptProperties();

function getProps(id) {
  return ScriptProperties.getProperty(id)
}

function set_property(key, value){
  ScriptProperties.setProperty(key, value); 
}

/************************************
1行目に自動入力する
************************************/
function set_header(sheet, values){
  var start_col = sheet.getLastColumn() + 1;
  set_property("start_col", start_col);
  var start_row = 1;
  var num_rows = 1;
  var num_cols = values.length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues([values]); 
}




Macの自動アップデートのON/OFFをどこで設定するのか知りたい


App Storeの環境設定を開いて


自動アップデートのチェックをON/OFFする


補足

システム環境設定のソフトウェアアップデートの中にも
Macを自動的に最新の状態に保つチェックボックスがある。


参考

Mac を最新の状態に保つ
https://support.apple.com/ja-jp/guide/mac-help/mchlpx1065/mac

Monday, September 9, 2019

JIRA APIでissueの変更履歴を取得する3


指定したissue keyでステータスが変更された最終日時を取得してシートに書き出したい

と思って書き出すコードを書きました。

書き出す前のシート:

  • KEY:issue keyは入力しておく
  • created:ステータスが変更された最終日時
  • from:変更前のステータス
  • to:変更後のステータス



書き出した後のシート:
コード.gsの get_last_status_change_date() を実行すると、created, from, to が入力されます。


関連記事

JIRA APIでissueの変更履歴を取得する
JIRA APIでissueの変更履歴を取得する2


事前準備
  • シートのA列にissue keyを入れておく
  • スクリプトのプロパティにtokenという名前でbasic認証する文字列を入れておく


コード.gs内で各自で変更する箇所
  1. ISSUE_URLの SITENAME
  2. ss_urlの SPREADSHEET_ID



コード.gs
var ISSUE_URL = 'https://SITENAME.atlassian.net/rest/api/2/issue/';
var ss_url = "https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit#gid=0";

/************************************
これを実行する
シートを取得して
シートからJIRAのissue keyを取得して
最終ステータスの日時と何から何へを取得して
シートに書き込む
************************************/
function get_last_status_change_date() {
  var sheet = get_sheet();
  var keys = get_keys(sheet);// ["KT-6", "KT-7"];
  var last_statuses = get_last_statuses(keys);
  var values = create_value(last_statuses, sheet);
  set_values(values, sheet);
}

/************************************
指定したss_urlのシートの0番目のシートを取得して返す
************************************/
function get_sheet() {
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sheet = ss.getSheets()[0];
  return sheet;
}

/************************************
シート内のA列の2行目から入力されているissue keyを取得して
二次元配列で取得されるので一次元配列にして返す
************************************/
function get_keys(sheet) {
  var last_row = sheet.getLastRow();
  var rangeA = sheet.getRange(2, 1, last_row-1, 1);
  var valuesA = rangeA.getValues();
  var keys = array_push_apply(valuesA);
  return keys;
}

/************************************
二次元配列を一次元配列にして返す
************************************/
function array_push_apply(arrays){
  for(var i = 1; i < arrays.length; i++){
    Array.prototype.push.apply(arrays[0], arrays[i]);
  }
  return arrays[0];
}

/************************************
issue keyの最後のステータス変更を取得して
last_statuses配列に入れて返す
************************************/
function get_last_statuses(keys) {
  var last_statuses = [];
  for(var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var latest_status = get_latest_status(key);
    last_statuses.push(latest_status);
  }
  return last_statuses;
}

/************************************
issue keyの最後のステータス変更の履歴を取得して返す
************************************/
function get_latest_status(key) {
  var histories = get_issue_histories(key);
  var arrays = get_status(histories);
  var latest_status = arrays[0];// descにした配列の先頭がlatest
  return latest_status;
}

/************************************
issueの情報からchangelogのhistoriesを取得して返す
************************************/
function get_issue_histories(key) {
  var response = get_issue(key);
  var jobj = JSON.parse(response);
  var histories = jobj["changelog"]["histories"];
  return histories;
}

/************************************
issueの情報を?expand=changelog付けて取得して返す
************************************/
function get_issue(key) {
  var token = getProp("token");
  var options = {
    contentType: "application/json",
    headers: {"Authorization": " Basic " + token}
  };
  var url = ISSUE_URL + key + "?expand=changelog";
  var response = UrlFetchApp.fetch(url, options);
  return response;
}

/************************************
histories(履歴)の数だけ繰り返す
さらに履歴の中のitemsの数だけ繰り返す
fieldがstatusならcreatedとfromとtoのオブジェクトを作って
arrays配列に入れて
descending_arrayで日時を基準に降順にして返す
************************************/
function get_status(histories) {
  var arrays = [];
  for(var i = 0; i < histories.length; i++) {
    var history = histories[i];
    var created = format_date(history["created"]);
    var items = history["items"];
    for(var j = 0; j < items.length; j++) {
      var item = items[j];
      var field = item["field"];
      if(field === "status") {
        var obj = {};
        obj["created"] = created;
        obj["from"] = item["fromString"];
        obj["to"] = item["toString"];
        arrays.push(obj);
      }
    }
  }
  var desc = descending_array(arrays);
  return desc;  
}

/************************************
シートに入れる値を作って入れる
************************************/
function create_value(last_statuses, sheet) {
  var values = [];
  for(var i = 0; i < last_statuses.length; i++) {
    var latest_status = last_statuses[i];
    if(latest_status === undefined) {
      values.push(["-", "-", "-"]);// statusの変更履歴がなければ-にする→しないとlatest_status["created"]などがエラーになるのでここで回避
    } else {
      var created = latest_status["created"];
      var from = latest_status["from"];
      var to = latest_status["to"];
      values.push([created, from, to]);
    }
  }
  return values;
}

/************************************
シートに値を入れる
************************************/
function set_values(array, sheet){
  var start_col = 2;
  var last_row = get_last_row(sheet, start_col);
  var start_row = last_row + 1;
  var num_rows = array.length;
  var num_cols = array[0].length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(array); 
}

/************************************
指定したcol(列)の値が入っている最終行を取得して返す
************************************/
function get_last_row(sheet, col) {
  var start_row = 1;
  var num_cols = 1;
  var sh_last_row = sheet.getLastRow();
  var values = sheet.getRange(start_row, col, sh_last_row, num_cols).getValues();
  for (var i = values.length - 1; i >= 0; i--) {
    if (values[i] != "") {
      break;
    }
  }
  var last_row = i + 1;
  return last_row;
}

/************************************
受け取ったkeyの値をスクリプトのプロパティから返す
************************************/
function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}

/************************************
日時をフォーマットして返す
************************************/
function format_date(date) {
  return Utilities.formatDate(new Date(date), "asia/tokyo", "yyyy-MM-dd HH:mm:ss");
}

/************************************
日時を降順にした配列を返す
************************************/
function descending_array(array) {
  var descending = array.sort(sorting_desc);
  return descending;
}

/************************************
array配列内の日時を降順にする
************************************/
function sorting_desc(a, b){
  if(a[0] > b[0]){
    return -1;
  }else if(a[0] < b[0] ){
    return 1;
  }else{
   return 0;
  }
}





Sunday, September 8, 2019

JIRA APIでissueの変更履歴を取得する2


以前書いた
JIRA APIでissueの変更履歴を取得する

ではhistoriesの先頭でitemsの先頭のみを取得しましたが
今回はそれぞれ複数ある場合のコードを書来ました。


事前準備
  • スクリプトプロパティにtokenという名前でbasic認証用の文字列を入れておきます
  • ISSUE_URLのSITENAMEは各自の設定によります
  • myFunctionのkeyは対象の課題キーを入れます



コード.gs
var ISSUE_URL = 'https://SITENAME.atlassian.net/rest/api/2/issue/';

function myFunction() {
  var key = "KEY-1";
  var histories = get_issue_histories(key);
  var arrays = [];
  for(var i = 0; i < histories.length; i++) {
    var history = histories[i];
    var created = format_date(history["created"]);
    var items = history["items"];
    for(var j = 0; j < items.length; j++) {
      var item = items[j];
      var field = item["field"];
      var from = item["fromString"];
      var to = item["toString"];
      Logger.log([created, field, from, to]);
    }
  }
  Logger.log(arrays);
}

function get_issue_histories(key) {
  var response = get_issue(key);
  var jobj = JSON.parse(response);
  var histories = jobj["changelog"]["histories"];
  return histories;
}

function get_issue(key) {
  var token = getProp("token");
  var options = {
    contentType: "application/json",
    headers: {"Authorization": " Basic " + token}
  };
  var url = ISSUE_URL + key + '?expand=changelog';
  var response = UrlFetchApp.fetch(url, options);
  return response;
}


function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}


function format_date(date) {
  return Utilities.formatDate(new Date(date), "asia/tokyo", "yyyy-MM-dd HH:mm")
}



実行結果

事前準備完了後にmyFunctionを実行すると
対象の課題について変更した日時とfieldとfromとtoがログに出ます。


関連記事

JIRA APIでissueの変更履歴を取得する

GASでAPIを作ってみる(ドライブのJSONから返す)


このようなオブジェクトをJSONにしてGoogleドライブに保存しておいて
  var obj = {
    'hello': 'はろー',
    'world': 'わーるど',
    'foo': 'ふー',
    'bar': 'ばー'
  };


GASのWebアプリでhelloとfooをパラメータで渡したら、はろーとふーを返してくれるアプリ

https://script.google.com/macros/s/WebアプリのID/exec?sentence=hello foo

を作ってみる



  1. GoogleドライブにJSONファイルを保存する
  2. JSONを読み込んで返すWebアプリを作る
  3. そのアプリにアクセスしてJSONから欲しいデータを取得する


関連記事

JSONデータを作ってGoogleドライブに保存する
Google Apps Script側でAPIを用意してJSONを返したい
GASでAPIを作ってみる(ContentService)


1. GoogleドライブにJSONファイルを保存する

以下コード1.gsの「file_name」「folder_id」を設定して
save_as_json()を実行すると
指定したフォルダにcreate_data()のJSONファイルが保存され
そのファイルIDがログに出ます。


コード1.gs
/************************************
JSONファイルを作成してドライブに保存する
************************************/
function save_as_json() {
  var file_name = 'ファイル名';// 保存するJSONファイルの名前
  var folder_id = 'フォルダID';// JSONファイルを保存するGoogleドライブのフォルダID
  
  var json = create_data();
  var file_id = create_file(json, file_name, folder_id);
  Logger.log(file_id);
}

function create_data() {
  var obj = {
    'hello': 'はろー', 
    'world': 'わーるど', 
    'foo': 'ふー', 
    'bar': 'ばー'
  }; 
  var json = JSON.stringify(obj);
  return json;
}

function create_file(json, file_name, folder_id) {
  var content_type = 'application/json';
  var blob = Utilities.newBlob("", content_type, file_name);
  var file = blob.setDataFromString(json, 'UTF-8');
  var folder = DriveApp.getFolderById(folder_id);
  var created_file = folder.createFile(file);
  var file_id = created_file.getId();
  return file_id;
}


2. JSONを読み込んで返すWebアプリを作る

コード1.gsで保存したJSONのIDを
以下のコード2.gsの「jsonFileId」のJSON_IDに入れて
Webアプリをデプロイします。


コード2.gs
/************************************
JSONから一致するkeyのvalueを返す
************************************/
function doGet (e) {
  var jsonFileId = "JSON_ID";// 保存したJSONファイルのID
  
  var sentence = e.parameter.sentence;
  var json = getJSON(jsonFileId);
  var values = returnValues(json, sentence);
  return ContentService.createTextOutput(values)
  .setMimeType(ContentService.MimeType.JSON);
}

function getJSON(fileId) {
  var file = DriveApp.getFileById(fileId);
  var blob = file.getBlob();
  var json = blob.getDataAsString();
  return json;
}

function returnValues(json, sentence) {
  var jobj = JSON.parse(json);
  var words = sentence.split(' ');
  var values = [];
  for(var i = 0; i < words.length; i++) {
    var word = words[i];
    var phonetic = jobj[word];
    values.push(phonetic);
  }
  return values;
}



3. そのアプリにアクセスしてJSONから欲しいデータを取得する

Webアプリをデプロイ後にそのURLにパラメータ(?sentence=hello foo)をつけてアクセスすると
JSONから一致する「はろー, ふー」が返ってくるはずです。

https://script.google.com/macros/s/WebアプリのID/exec?sentence=hello foo


Saturday, September 7, 2019

Chrome Extensionsをリスト表示したい(Extensions Toolbar Menu)


Chrome Extensionsをこのようにリスト表示する機能がExperimentsにありました。



実験的な機能のため、動作は保証されていないようですが試してみました。



手順
  1. chrome://flags をアドレスバーに入れて開く
  2. Extensions Toolbar Menu を検索する
  3. Enabledにする


参考

Googleの公式記事は見つけられませんでしたが、Pixel 3aのDiscover(右にスワイプして表示される情報)が教えてくれた。
https://www.lifehacker.jp/2019/09/how-to-enable-chromes-new-extension-menu.html

Discover
https://support.google.com/websearch/answer/2819496?co=GENIE.Platform%3DAndroid&hl=ja

Friday, September 6, 2019

Blockly Games Musicをやってみました


Blockly Gamesの Music(音楽)
https://blockly-games.appspot.com/music?lang=ja


Blockly Gamesの Music(音楽)では
ブロックを繋げて指定した音を再生することができるようです。
レベルが上がるごとにできることが増えていきます。



指定できる音の範囲
  • C3〜A4
 

指定できる楽器
  • piano, trumpet, banjo, violin, guitar, flute, drum, choir

再生できる五線譜と数
  • ト音記号
  • 4つまでのようです



コード(和音)を作ってみる

これで良いのかわかりませんが
レベル10の自由課題で
カノンのコード(和音)進行をハ長調で作ってみました。

各コードを関数にできたら楽かなと思いましたが
1つの五線譜に1つの音までのようなので
1音ずつ同時に再生させるようにしました。


クリアしたリンク

クリアしたリンクを共有できるので残しておきましたが
自分でクリアしたい人は以下のURLにアクセスしないで自力でやってみてください
  1. https://blockly-games.appspot.com/music?lang=ja&level=1#kppkhw
  2. https://blockly-games.appspot.com/music?lang=ja&level=2#m2xpy6
  3. https://blockly-games.appspot.com/music?lang=ja&level=3#5pbjad
  4. https://blockly-games.appspot.com/music?lang=ja&level=4#d9g2wm
  5. https://blockly-games.appspot.com/music?lang=ja&level=5#38znrn
  6. https://blockly-games.appspot.com/music?lang=ja&level=6#p6vgbg
  7. https://blockly-games.appspot.com/music?lang=ja&level=7#c5sxqa
  8. https://blockly-games.appspot.com/music?lang=ja&level=8#vmsqkq
  9. https://blockly-games.appspot.com/music?lang=ja&level=9#89oz5h
  10. https://blockly-games.appspot.com/music?lang=ja&level=10#68mji5
    • レベル10では自由に作れるのでカノンのコード進行をハ長調で作りました

参考

Blockly Games
https://blockly-games.appspot.com/?lang=ja

Blockly Games : About
https://blockly-games.appspot.com/about?lang=ja

Latest post

Extracting data from Google Sheets with regular expressions

Introduction Regular expressions are a powerful tool that can be used to extract data from text.  In Google Sheets, regular expressions ca...