LANG SELRCT

コードを書く場所

2019年9月14日土曜日

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);
  }
}


実行結果

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


2019年9月12日木曜日

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

2019年9月9日月曜日

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;
  }
}





2019年9月8日日曜日

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


2019年9月7日土曜日

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

2019年9月6日金曜日

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

JIRAのカスタムフィールドの説明を更新したい


フィールドの追加は以下の記事で書きました。
JIRAのフィールドを追加、削除したい

今回は、一度追加したフィールドの説明を変更する方法を書いていきます。


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


STEP2:フィールドをクリック


STEP3:アクション > フィールドを編集を選択


STEP4:対象のカスタムフィールドを見つけて「編集」をクリック


STEP5:説明を変更して更新する

説明を変更する前

説明を変更して「更新」

説明が更新されます


課題作成時のフィールドの説明も変更されます




補足1

STEP3のフィールド画面で右端の鉛筆アイコンからもSTEP4へ遷移します


補足2

カスタムフィールドの編集からだと説明の更新が反映されません。
理由はよくわかりませんが、最近この壁にぶつかって調べて書いたのがこの記事です。

更新されないケースの道も書き残しておきます

STEP4でそのまま検索ボックスに検索せずに「カスタムフィールド」をクリックして


対象のフィールドを見つけて「…」から「編集」を選択し


説明を変更しても

変更前

変更後

この説明は更新されるけれど


課題作成時のフィールドの説明は更新されない


参考

フィールド設定の追加、編集、削除
https://ja.confluence.atlassian.com/adminjiracloud/adding-editing-and-deleting-a-field-configuration-844500796.html

フィールドの説明を変更するには
https://ja.confluence.atlassian.com/adminjiracloud/changing-a-field-configuration-844500798.html

JIRAのカスタムフィールドの説明を変更しても画面に反映されない
https://www.ricksoft.jp/document/pages/viewpage.action?pageId=172425257

2019年9月5日木曜日

Googleドキュメント内のテキストを置換したい replaceText


DocumentApp.getActiveDocument().getBody()
でBodyを取得して

.replaceText(searchPattern, replacement)
で置換できるようです。



ドキュメント内に hello world という文字列があって


コード.gsの
body.replaceText("hello", "Hello")
でhello を Hello に置換できます。



コード.gs
function myFunction() {
  var doc = DocumentApp.getActiveDocument()
  var body = doc.getBody();
  body.replaceText("hello", "Hello");
}
意訳
この機能がやること
ドキュメントを取得して
Bodyを取得して
hello を Hello に置換する



補足

  • 正規表現も使えるようです
  • 一致するすべてのsearchPatternがreplacementで置換されます
    • すべて置換


ドキュメント内に
hello world
hello world2
という文字列があるとき

コード.gsの
body.replaceText("hello", "Hello");
body.replaceText("hello.*", "Hi");
に書き換えて実行すると以下のようになります。


参考

replaceText(searchPattern, replacement)
https://developers.google.com/apps-script/reference/document/text#replaceText(String,String)

Class Body
https://developers.google.com/apps-script/reference/document/body
The Body may contain ListItem, Paragraph, Table, and TableOfContents elements. For more information on document structure, see the guide to extending Google Docs.

2019年9月1日日曜日

Blockly Games Mazeをやってみた


Blockly GamesのMaze(迷路)をやってみました。

レベルが10まであって、ゴールまでの道をブロックをつなげて導いていくゲーム。
レベルが上がるごとに難しくなっていきます。


自分で試したい人は以下のリンクは開かないでください

ゴールへ導く方法はいくつかあると思いますが、自分がクリアしたブロックを共有できるようなので残しておきました。


Blockly Games Mazeをクリアしたブロックの組み合わせ
(自力で解きたい人は開かないでください)

  1. https://blockly-games.appspot.com/maze?lang=ja#3m64bp
  2. https://blockly-games.appspot.com/maze?lang=ja&level=2&skin=0#daume6
  3. https://blockly-games.appspot.com/maze?lang=ja&level=3&skin=0#cmhyhx
  4. https://blockly-games.appspot.com/maze?lang=ja&level=4&skin=0#ci6g6f
  5. https://blockly-games.appspot.com/maze?lang=ja&level=5&skin=0#qaygga
  6. https://blockly-games.appspot.com/maze?lang=ja&level=6&skin=0#86r6y7
  7. https://blockly-games.appspot.com/maze?lang=ja&level=7&skin=0#jj5fy9
  8. https://blockly-games.appspot.com/maze?lang=ja&level=8&skin=0#zvdz2d
  9. https://blockly-games.appspot.com/maze?lang=ja&level=9&skin=0#drmenb
  10. https://blockly-games.appspot.com/maze?lang=ja&level=10&skin=0#xi848n



参考

Blockly Games
https://blockly-games.appspot.com/

2019年8月31日土曜日

GitHubのプルリクの説明にテンプレートを作る


プルリクエストの説明にテンプレートを作れることを知ったのでやってみました。

手順
  1. テンプレートを使いたいリポジトリを開く
  2. pull_request_template.mdファイルを作成する
  3. テンプレートを作って保存する
  4. masterにマージする
  5. プルリクを作る
  6. テンプレートに説明を書く


手順の2, 3のキャプチャを残しておきます

pull_request_template.mdファイルテンプレートを作る

これをmasterにマージすると、次回プルリク作成時からコメントにテンプレートが表示されるようになりました。


参考

リポジトリ用のプルリクエストテンプレートの作成
https://help.github.com/ja/articles/creating-a-pull-request-template-for-your-repository

zendesk APIでグループの情報を取得したい


一覧の取得と各グループの情報を取得したくてリファレンスから情報を得ました。
  1. グループの一覧を取得する
    https://EXAMPLE.zendesk.com/api/v2/groups.json
  2. 特定のグループの情報を取得するhttps://EXAMPLE.zendesk.com/api/v2/groups/360005843012.json


1. グループの一覧を取得する
https://EXAMPLE.zendesk.com/api/v2/groups.json

レスポンス
{
  "groups": [{
    "url": "https://EXAMPLE.zendesk.com/api/v2/groups/360005923112.json",
    "id": 360005923112,
    "name": "sales",
    "deleted": false,
    "created_at": "2019-08-31T04:42:10Z",
    "updated_at": "2019-08-31T04:42:10Z"
  }, {
    "url": "https://EXAMPLE.zendesk.com/api/v2/groups/360005922532.json",
    "id": 360005922532,
    "name": "support-engineer",
    "deleted": false,
    "created_at": "2019-08-31T02:29:57Z",
    "updated_at": "2019-08-31T02:32:43Z"
  }, {
    "url": "https://EXAMPLE.zendesk.com/api/v2/groups/360005843012.json",
    "id": 360005843012,
    "name": "サポート",
    "deleted": false,
    "created_at": "2019-08-31T02:25:25Z",
    "updated_at": "2019-08-31T02:25:30Z"
  }],
  "next_page": null,
  "previous_page": null,
  "count": 3
}


2. 特定のグループの情報を取得する
https://EXAMPLE.zendesk.com/api/v2/groups.json

レスポンス
{
  "group": {
    "url": "https://EXAMPLE.zendesk.com/api/v2/groups/360005843012.json",
    "id": 360005843012,
    "name": "サポート",
    "deleted": false,
    "created_at": "2019-08-31T02:25:25Z",
    "updated_at": "2019-08-31T02:25:30Z"
  }
}


参考

Support API Groups
https://developer.zendesk.com/rest_api/docs/support/groups

zendeskで指定した担当者のチケットをAPIで取得したい


指定した担当者のチケットを取得する例を書きました。

https://EXAMPLE.zendesk.com/api/v2/search.json?page=1&query=created>2019-01-01%20type:ticket%20assignee:386276247732



リクエスト
https://EXAMPLE.zendesk.com/api/v2/search.json?
page=1&
query=created>2019-01-01%20
type:ticket%20
assignee:386276247732
意訳
各自のzendeskの結果をJSONで返す
結果の1ページ目
created>2019-01-01
チケット
担当者の名前やIDなど


assigneeについて(チケットの検索)
割り当てられたエージェントまたはその他のエンティティ。「none」、「me」、ユーザー名(フルネームまたは一部)、メールアドレス、ユーザーID、または電話番号を指定できます。


関連記事

zendeskで特定日以降のチケットをAPIで取得したい
zendeskで指定したグループのチケットをAPIで取得したい


参考

チケットの検索
https://support.zendesk.com/hc/ja/articles/203663206-%E3%83%81%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E6%A4%9C%E7%B4%A2

zendeskで特定のテキストを説明に含むチケットをAPIで取得したい


「テスト」が説明に含まれるチケットを取得する例を書きました。

https://EXAMPLE.zendesk.com/api/v2/search.json?page=1&query=created%3E2019-01-01%20type:ticket%20description:テスト



リクエスト
https://EXAMPLE.zendesk.com/api/v2/search.json?
page=1&
query=created%3E2019-01-01%20
type:ticket%20
description:テスト
意訳
各自のzendeskの結果をJSONで返す
結果の1ページ目
created>2019-01-01
チケット
説明およびコメント内のテキスト


関連記事

zendeskで特定日以降のチケットをAPIで取得したい
zendeskで指定したグループのチケットをAPIで取得したい


参考

チケットの検索
https://support.zendesk.com/hc/ja/articles/203663206-%E3%83%81%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E6%A4%9C%E7%B4%A2

zendeskで指定したタグのチケットをAPIで取得したい


supportタグのついたチケットを取得する例を書きました。

https://EXAMPLE.zendesk.com/api/v2/search.json?page=1&query=created%3E2019-01-01%20type:ticket%20tags:support



リクエスト
https://EXAMPLE.zendesk.com/api/v2/search.json?
page=1&
query=created%3E2019-01-01%20
type:ticket%20
tags:support
意訳
各自のzendeskの結果をJSONで返す
結果の1ページ目
created>2019-01-01
チケット
タグ


関連記事

zendeskで特定日以降のチケットをAPIで取得したい
zendeskで指定したグループのチケットをAPIで取得したい


参考

チケットの検索
https://support.zendesk.com/hc/ja/articles/203663206-%E3%83%81%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E6%A4%9C%E7%B4%A2

zendeskで指定したグループのチケットをAPIで取得したい



https://EXAMPLE.zendesk.com/api/v2/search.json?page=1&query=created%3E2019-01-01%20type:ticket%20group:GROUPNAME



リクエスト
https://EXAMPLE.zendesk.com/api/v2/search.json?
page=1&
query=created%3E2019-01-01%20
type:ticket%20
group:GROUPNAME
意訳
各自のzendeskの結果をJSONで返す
結果の1ページ目
created>2019-01-01
チケット
グループ名


関連記事

zendeskで特定日以降のチケットをAPIで取得したい


参考

チケットの検索
https://support.zendesk.com/hc/ja/articles/203663206-%E3%83%81%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E6%A4%9C%E7%B4%A2