LANG SELRCT

コードを書く場所

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