LANG SELRCT

コードを書く場所

2020年3月29日日曜日

JIRA APIをブラウザで実行したい


以下のURLで
SITENAMEとPROJECT_KEYを各自のものに書き換えて
ブラウザのアドレスバーに入れて開くと
クエリの結果がブラウザで確認できます。

https://SITENAME.atlassian.net/rest/api/2/search?jql=project=PROJECT_KEY


補足

jql=の部分は
JIRAの課題の検索画面でのjqlと同様のようです。

https://SITENAME.atlassian.net/issues/?jql=project%20%3D%20PROJECT_KEY

UrlFetchApp.fetchAllを使って複数のfetchを同時にやって実行時間の短縮を試みる


今回やること
  • UrlFetchApp.fetchAllを試してみる


fetchとfetchAllを比較してみる。
  • それぞれに同じ数のリクエストを送って速度を比較してみる


Google Books APIで任意のキーワードを決めて40件の結果を出力してみる。

今回使うキーワード
  • API
  • Google
  • Chrome

仮説
  • fetchallは並列で処理してくれて、fetch1回分の処理時間で実行できるだろう


検証してみます


UrlFetchApp.fetch
で1つずつ処理する場合

コード.gs
function getBooksFetch() {
  getBooksWithParams1();
  getBooksWithParams2();
  getBooksWithParams3();
}

function getBooksWithParams1() {
  var url = 'https://www.googleapis.com/books/v1/volumes?q=intitle:API&country=JP&langRestrict=ja&maxResults=40&orderBy=newest';
  var json = UrlFetchApp.fetch(url);
  Logger.log(json)
}

function getBooksWithParams2() {
  var url = 'https://www.googleapis.com/books/v1/volumes?q=intitle:Google&country=JP&langRestrict=ja&maxResults=40&orderBy=newest';
  var json = UrlFetchApp.fetch(url);
  Logger.log(json)
}

function getBooksWithParams3() {
  var url = 'https://www.googleapis.com/books/v1/volumes?q=intitle:Chrome&country=JP&langRestrict=ja&maxResults=40&orderBy=newest';
  var json = UrlFetchApp.fetch(url);
  Logger.log(json)
}


結果をログで見ると1.769秒



UrlFetchApp.fetchAll
で同時に複数処理する場合

コード2.gs
function getBooksFetchAll() {
  var url1 = 'https://www.googleapis.com/books/v1/volumes?q=intitle:API&country=JP&langRestrict=ja&maxResults=40&orderBy=newest';
  var url2 = 'https://www.googleapis.com/books/v1/volumes?q=intitle:Google&country=JP&langRestrict=ja&maxResults=40&orderBy=newest';
  var url3 = 'https://www.googleapis.com/books/v1/volumes?q=intitle:Chrome&country=JP&langRestrict=ja&maxResults=40&orderBy=newest';
  var response = UrlFetchApp.fetchAll([url1, url2, url3]);
  Logger.log(response);
}


getBooksFetchAllを実行した結果は0.929秒


結果

今回の検証データでは仮説には及びませんでした。
しかし、約半分くらいの処理時間になりました。

もっと重たい処理を試すと、Googleのサーバに負荷をかけてしまうと思うのでここではやりませんが、今回のような軽くて3つくらいの処理でも確実にfetchAllの方が処理が早いことがわかりました。

同時処理数の上限についてリファレンスに記載が見つけられませんでしたが、QuotasにあるプランごとのURL Fetch callsがそれにあたるかもしれません。


補足

ちなみに認証が必要なAPIをたたく場合はこのように書くと実行できました。

コード3.gs
function getBooksFetchAll() {
  var request1 = {
    'url': 'https://foobar',
    'method' : 'get',
    'contentType: "application/json", 
    'headers': {"Authorization": " Basic " + TOKEN}
  }

  var request2 = {
    'url': 'https://hogefuga',
    'method' : 'get',
    'contentType: "application/json", 
    'headers': {"Authorization": " Bearer " + TOKEN}
  }
  UrlFetchApp.fetchAll([request1, request2]);
}


2020年3月27日金曜日

JIRAでラベルフィールドのサジェストを削除したい


以下のサポートページに書かれていた通りにやるとできました。

How to delete a label in Jira
https://confluence.atlassian.com/jirakb/how-to-delete-a-label-in-jira-297667646.html


どうやるか
  • 対象のラベルが設定されている全ての課題からそのラベルを削除する

それをどうやるか
  • 検索して一括処理でラベルを削除する


それを実際にどうやるかやってみた手順です。


STEP1
  1. 検索画面を開きます
    • https://SITENAME.atlassian.net/issues/
  2. 対象のフィールドで削除したいラベルを指定して検索します
    • 今回の例ではlabelsフィールドのラベル2を指定してみます
    • labels = ラベル2
  3. 右上の「…」を開きます
  4. Bulk change all 87 issue(s) の部分を選択します
    • 87件にラベル2がついているのでそのラベルを削除したい


STEP2
  1. Nextの下のチェックボックスをチェックしてすべて選択します
  2. Nextをクリックします


STEP3
  1. Edit issuesをチェックします
  2. Nextをクリックします


STEP4
  1. Change Labelsをチェックします
  2. 選択肢からFind and remove theseを選択します
  3. 削除したいラベル(今回はラベル2)を指定します

一番下までスクロールしてNextをクリックします。


STEP5
内容を確認して、Confirmをクリックします


STEP6
完了するまで待って、Acknowledgeをクリックします。


これで対象のラベル(ここではラベル2)が全ての課題から削除されました。


以上でラベルをリストから削除することができました。


参考

How to delete a label in Jira
https://confluence.atlassian.com/jirakb/how-to-delete-a-label-in-jira-297667646.html

2020年3月20日金曜日

Google Visualization APIでresponseのcolsに値が入らない場合がある


スプレッドシート側のデータや設定によって、Google Visualization APIで取得したデータの形式が異なるケースがあったので、試したパターンを書き残しておきます。

CASE1のようにすべてのセルを埋めておけば問題ないですが、空のセルがあるとresponseが変わってくるようです。



CASE1
すべてのセルが埋まっている場合は1行目がcolsに入ってきました。
(表示形式はABCを自動、Dを日付に設定)

response
 /*O_o*/
 google.visualization.Query.setResponse({
   "version": "0.6",
   "reqId": "0",
   "status": "ok",
   "sig": "705247912",
   "table": {
     "cols": [{
       "id": "A",
       "label": "midashi",
       "type": "string"
     }, {
       "id": "B",
       "label": "description",
       "type": "string"
     }, {
       "id": "C",
       "label": "tags",
       "type": "string"
     }, {
       "id": "D",
       "label": "created",
       "type": "date",
       "pattern": "yyyy/mm/dd"
     }, {
       "id": "E",
       "label": "",
       "type": "string"
     }],
     "rows": [{
       "c": [{
         "v": "見出し1"
       }, {
         "v": "説明1"
       }, {
         "v": "タグ1"
       }, {
         "v": "Date(2020,2,20)",
         "f": "2020/03/20"
       }, {
         "v": null
       }]
     }, {
       "c": [{
         "v": "見出し2"
       }, {
         "v": "説明2"
       }, {
         "v": "タグ2"
       }, {
         "v": "Date(2020,2,21)",
         "f": "2020/03/21"
       }, {
         "v": null
       }]
     }, {
       "c": [{
         "v": "見出し3"
       }, {
         "v": "説明3"
       }, {
         "v": "タグ3"
       }, {
         "v": "Date(2020,2,22)",
         "f": "2020/03/22"
       }, {
         "v": null
       }]
     }],
     "parsedNumHeaders": 0
   }
 });




CASE2
特定の列(D列)に1つだけ値が入っている場合はcolsに一行目以降の値も入ってきました。
(表示形式はABCを自動、Dを日付に設定)

response
/*O_o*/
google.visualization.Query.setResponse({
  "version": "0.6",
  "reqId": "0",
  "status": "ok",
  "sig": "131156031",
  "table": {
    "cols": [{
      "id": "A",
      "label": "midashi 見出し1 見出し2",
      "type": "string"
    }, {
      "id": "B",
      "label": "description 説明1 説明2",
      "type": "string"
    }, {
      "id": "C",
      "label": "tags タグ1 タグ2",
      "type": "string"
    }, {
      "id": "D",
      "label": "created ",
      "type": "date",
      "pattern": "yyyy/mm/dd"
    }, {
      "id": "E",
      "label": "",
      "type": "string"
    }],
    "rows": [{
      "c": [{
        "v": "見出し3"
      }, {
        "v": "説明3"
      }, {
        "v": "タグ3"
      }, {
        "v": "Date(2020,2,22)",
        "f": "2020/03/22"
      }, {
        "v": null
      }]
    }],
    "parsedNumHeaders": 0
  }
});




CASE3
データ範囲の表示形式をすべて「書式なしテキスト」にした場合はcolsに値は入らず、1行目からrowsに入ってきました。


response
 /*O_o*/
 google.visualization.Query.setResponse({
   "version": "0.6",
   "reqId": "0",
   "status": "ok",
   "sig": "703682130",
   "table": {
     "cols": [{
       "id": "A",
       "label": "",
       "type": "string"
     }, {
       "id": "B",
       "label": "",
       "type": "string"
     }, {
       "id": "C",
       "label": "",
       "type": "string"
     }, {
       "id": "D",
       "label": "",
       "type": "string"
     }, {
       "id": "E",
       "label": "",
       "type": "string"
     }],
     "rows": [{
       "c": [{
         "v": "midashi"
       }, {
         "v": "description"
       }, {
         "v": "tags"
       }, {
         "v": "created"
       }, {
         "v": null
       }]
     }, {
       "c": [{
         "v": "見出し1"
       }, {
         "v": "説明1"
       }, {
         "v": "タグ1"
       }, null, {
         "v": null
       }]
     }, {
       "c": [{
         "v": "見出し2"
       }, {
         "v": "説明2"
       }, {
         "v": "タグ2"
       }, null, {
         "v": null
       }]
     }, {
       "c": [{
         "v": "見出し3"
       }, {
         "v": "説明3"
       }, {
         "v": "タグ3"
       }, {
         "v": "43912"
       }, {
         "v": null
       }]
     }],
     "parsedNumHeaders": 0
   }
 });



CASE4
D列の表示形式を日付に戻すと、CASE2と同じくclosに1行目以外の値も入ってきました。

response
/*O_o*/
google.visualization.Query.setResponse({
  "version": "0.6",
  "reqId": "0",
  "status": "ok",
  "sig": "393464895",
  "table": {
    "cols": [{
      "id": "A",
      "label": "midashi 見出し1 見出し2",
      "type": "string"
    }, {
      "id": "B",
      "label": "description 説明1 説明2",
      "type": "string"
    }, {
      "id": "C",
      "label": "tags タグ1 タグ2",
      "type": "string"
    }, {
      "id": "D",
      "label": "created ",
      "type": "date",
      "pattern": "yyyy/MM/dd"
    }, {
      "id": "E",
      "label": "",
      "type": "string"
    }],
    "rows": [{
      "c": [{
        "v": "見出し3"
      }, {
        "v": "説明3"
      }, {
        "v": "タグ3"
      }, {
        "v": "Date(2020,2,22)",
        "f": "2020/03/22"
      }, {
        "v": null
      }]
    }],
    "parsedNumHeaders": 0
  }
});



CASE5
日付が入るD列に日付以外の値が入っていると、CASE2と同じくclosに1行目以外の値も入ってきました。例では日付の列に-を入れています。

response
/*O_o*/
google.visualization.Query.setResponse({
  "version": "0.6",
  "reqId": "0",
  "status": "ok",
  "sig": "374075250",
  "table": {
    "cols": [{
      "id": "A",
      "label": "midashi 見出し1 見出し2",
      "type": "string"
    }, {
      "id": "B",
      "label": "description 説明1 説明2",
      "type": "string"
    }, {
      "id": "C",
      "label": "tags タグ1 タグ2",
      "type": "string"
    }, {
      "id": "D",
      "label": "created - -",
      "type": "date",
      "pattern": "yyyy/MM/dd"
    }, {
      "id": "E",
      "label": "",
      "type": "string"
    }],
    "rows": [{
      "c": [{
        "v": "見出し3"
      }, {
        "v": "説明3"
      }, {
        "v": "タグ3"
      }, {
        "v": "Date(2020,2,21)",
        "f": "2020/03/21"
      }, {
        "v": null
      }]
    }],
    "parsedNumHeaders": 0
  }
});



参考

Google Visualization API Reference
https://developers.google.com/chart/interactive/docs/reference

2020年3月14日土曜日

GitHub Pagesでウェブサイトのホスティングができる


以下の手順でmaster branchのindex.htmlを表示することができました。
  1. リポジトリを作る
  2. index.htmlを作る
  3. リポジトリのSettingsを開く
  4. GitHub PagesでSourceをmaster branchにする
  5. サイトURLにアクセスする


詳しい制限や使い方については公式サイトの GitHub Pages について に記載されているので、用法、容量を守って利用しましょう。


GitHubアカウントを持っていない場合

GitHubアカウントを作ってリポジトリをクローンしてみる
1. GitHubアカウントを作成する

の手順で作成できると思います。
または、ググったりして作成してみてください。


参考

Azure App Serviceを試してみる


Azureで無料のWeb Appの作成を試した時の備忘録です。

1時間の期限付きでウェブサイトの公開を試すことができました。

ここではHTML5の空のサイトを表示することができた手順を書き残しておきます。


手順

STEP1
https://azure.microsoft.com/ja-jp/services/app-service/
Web Apps を今すぐ試すをクリックします。


STEP2
Web Appを選択して「次へ」をクリックします。


STEP3
言語の選択で「HTML5」を選択して
「HTML5 空のサイト」を選択して
「Create」をクリックします。


STEP4
Web App を利用する のリンク
を開くと、デフォルトのサイトが開きます。


STEP5
「コードをオンラインで編集」をクリックします。
「index.html」を選択すると編集画面が表示されます。


STEP6
右上の「Open Preview」ボタンをクリックするとプレビュー画面が確認できます。



helloというラベルを追加してみたコード

index.html
<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        HTML5 Empty Site<br>
        <label>hello</label>
    </body>
</html>


プレビュー


参考

App Service のドキュメント
https://docs.microsoft.com/ja-jp/azure/app-service/

2020年3月2日月曜日

JIRA APIでステータスカテゴリの最終変更日時を取得したい


JIRAには課題の状態を表す値が2つあります
  1. ステータ
  2. ステータスカテゴリ

今回対象にするのはステータスカテゴリです
  • To Do
  • In Progress
  • Done

現在のステータスカテゴリになった日時を
fields内のstatuscategorychangedateで取得するコードを書きました。


各自で設定する変数
  • ISSUE_URL
  • issueKey
  • スクリプトのプロパティにtokenを入れておく

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

function myFunction(){
  var issueKey = "KEY-1";
  var response = getIssueData(issueKey);
  var jobj = JSON.parse(response);
  var fields = jobj["fields"];
  var statusCategoryChangeDate = formatDate(fields["statuscategorychangedate"]);
  var status = fields["status"]["name"];
  Logger.log([status, statusCategoryChangeDate]);
}

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

function formatDate(datetime) {
  var date = new Date(datetime);
  var formated = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');
  return formated;
}


myFunction()の実行結果の例

以下のようなログが出力されるはずです。

 [Done, 2020/03/02 18:15:43]


ステータス変更の履歴を取得したい場合は

JIRAで複数の課題の変更履歴をシートに書き出したい(ステータス変更だけ)

にコードを書きました。

関連記事

JIRAでAPIトークンを取得したい
JIRA APIでissueの変更履歴を取得する
JIRAのJQLにはstatusとstatusCategoryがある

2020年3月1日日曜日

スプレッドシートで取得する行数を決めて複数回実行する処理を考えてみる


A列に2020/01/01〜2020/01/19までの日付が入ったシートがあって
2行目からスタートして5行ずつA列の値を取得してみる。

ポイント

  • 最後は5行ではなく4行残るので4行を取得する
  • 10回繰り返すが実際は4回で終わるため、5回目以降でエラーが出ないようにする


20行くらいの処理ならこんなことをしなくても良いのだけれど
何千、何万行のデータを処理する時に実行回数を分けたいケースにぶつかって
軽い処理で書いてみたのがこのコードです。



コード.gs
function iterateFunction() {
  var numRows = 5;
  var startRow = 2;
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  
  for(var i = 0; i < 10; i++) {
    myFunction(sheet, startRow, numRows, lastRow);
    startRow = startRow + numRows;
    if(startRow > lastRow) {
      return;// startRowがlastRowより大きくなったらfor文から抜ける
    }
  }
}

function myFunction(sheet, startRow, numRows, lastRow) {
  if(startRow + numRows > lastRow) {// 取得しようとする最終行がデータの行数より大きくなるなら
    numRows = lastRow - startRow + 1;// numRowsを残りの行数で上書きする
  }
  var range = sheet.getRange(startRow, 1, numRows, 1); 
  var values = range.getDisplayValues();
  var dates = values.map(function(array) { return array[0] });
  Logger.log(dates);
}




実行結果




2020年2月29日土曜日

Salesforce APIでCaseを削除したい


今回やること

登録されているCaseを削除する

使うメソッドはDELETE
"method": "DELETE",


事前準備

接続アプリケーションを作成して
アクセストークンなどのデータをスクリプトのプロパティに保存しておく

手順は以下のブログに書きました。

SalesforceでAPIを使うために接続アプリケーションを作成したい
Salesforceで接続アプリケーションを作成したい(Lightningの方で)



コード.gs
function deleteData() {
  var url = getProp("instance_url") + "/services/data/v47.0/sobjects/Case/CASE_ID";
  var response = UrlFetchApp.fetch(url, optionsDelete());
  if(response.getResponseCode() === 401) {
    runRefresh();// アクセストークン切れで401ならトークンを更新する
    response = UrlFetchApp.fetch(url, optionsDelete());
  }
  Logger.log([response, response.getResponseCode()]);
}

function optionsDelete() {
  var options = {
    "method": "DELETE",
    "headers": {
      "Authorization": "Bearer " + getProp("access_token"),
      "Content-Type": "application/json"
    },
    "muteHttpExceptions": true
  }
  return options;
}




参考

レコードを削除する
https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_delete_record.htm

Salesforce APIでCaseを新規作成したい


今回やること

新規でCaseを作成する

作成する内容は以下のように設定しておく
  var obj = {
        "Status": "新規",
        "Reason": "理由を書く",
        "Origin": "電話",
        "Subject": "POSTで作成",
        "Priority": "中",
  }


使うメソッドはPOST
"method": "POST",


事前準備

接続アプリケーションを作成して
アクセストークンなどのデータをスクリプトのプロパティに保存しておく

手順は以下のブログに書きました。

SalesforceでAPIを使うために接続アプリケーションを作成したい
Salesforceで接続アプリケーションを作成したい(Lightningの方で)



コード.gs
function postData() {  
  var url = getProp("instance_url") + "/services/data/v47.0/sobjects/Case";
  var response = UrlFetchApp.fetch(url, optionsPost());
  if(response.getResponseCode() === 401) {
    runRefresh();// アクセストークン切れで401ならトークンを更新する
    response = UrlFetchApp.fetch(url, optionsPost());
  }
  Logger.log([response, response.getResponseCode()]);
}

function createPayloadPost() {
  var obj = {
        "Status": "新規",
        "Reason": "理由を書く",
        "Origin": "電話",
        "Subject": "POSTで作成",
        "Priority": "中",
  }
  var json = JSON.stringify(obj);
  return json;
}

function optionsPost() {
  var options = {
    "method": "POST",
    "headers": {
      "Authorization": "Bearer " + getProp("access_token"),
      "Content-Type": "application/json"
    },
    "payload": createPayloadPost(),
    "muteHttpExceptions": true
  }
  return options;
}

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



参考

レコードを作成する
https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_sobject_create.htm


2020年2月28日金曜日

Gsuiteでカレンダーの共有権限を設定する(管理者)


カレンダーの公開および共有設定 > 共有オプションを設定する
https://support.google.com/a/answer/60765?hl=ja

に書かれていました。

デフォルトでは
「空き時間情報のみ(予定の詳細は非表示)」
になっているので、共有した予定は「予定あり」としか表示されない。


そこを
「すべての情報を共有する(外部ユーザーにカレンダーの変更を許可しない)」
などに変更すると内容も表示されるようになる。

(設定が反映されるまでに最大で24時間かかることがある)


参考

カレンダーの公開および共有設定 > 共有オプションを設定する
https://support.google.com/a/answer/60765?hl=ja

他のユーザーとカレンダーを共有する
https://support.google.com/calendar/answer/37082?hl=ja

2020年2月22日土曜日

canvasで四角形を描く






コード.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}
意訳
この機能がやること
指定したHTMLファイルを表示する




index.html
<!DOCTYPE html>
<html>
<body>
  <canvas id="myCanvas" width="360" height="360"></canvas>
<script>
  createRect();
  function createRect() { 
    var canvas = document.getElementById("myCanvas");
    var context = canvas.getContext("2d");
    var x = 100;
    var y = 100;
    var width = 200;
    var height = 200;
    context.strokeStyle = "blue";
    context.lineWidth = 10;
    context.strokeRect(x, y, width, height);
  }
</script>
</body>
</html>



2020年2月17日月曜日

JIRAで複数の課題の変更履歴をシートに書き出したい(ステータス変更だけ)


JIRAで複数の課題の変更履歴をシートに書き出したい
ではすべての変更履歴を取得しました。

今回はfieldが「status」の場合だけシートに書き出したい。



作成時のステータス取得について

今回は下記の方法で取得しています。

  • 作成時の配列を以下のように用意しておく
    • var firstLine = [key, "status", currentStatus, "-", issueCreated];
  • 変更履歴がない場合
    • 現在のステータスをfromの位置(配列の2番目)に代入します
  • 変更履歴がある場合
    • 最初の変更履歴からfromの値を取得して代入します




コード.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";

/************************************
これを実行する
************************************/
function getIssuesHistories() {
  var keys = ["KT-12", "KT-13", "KT-14"];// 対象の課題キーたち
  var arrays = [];
  for(var i = 0; i < keys.length; i++) {
    var key = keys[i];
    getIssueHistories(key);
  }
}

/************************************
受け取った課題keyをgetHistoriesに渡して結果をシートに書き込む
************************************/
function getIssueHistories(key){
  var response = getHistories(key);
  var jobj = JSON.parse(response);
  var histories = jobj["changelog"]["histories"];
  var issueCreated = returnFormatDate(jobj["fields"]["created"]);
  var currentStatus = jobj["fields"]["status"]["name"];
  var arrays = [];
  var firstLine = [key, "status", currentStatus, "-", issueCreated];// この課題の最初の配列を用意する(changelogは変更履歴なので、作成時の記録は含まれないためここで作っておく)
  for(var i = 0; i < histories.length; i++) {
    var history = histories[i];
    var created = returnFormatDate(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"];
      if(field === "status") {
        arrays.push([key, field, from, to, created]);
      }
    }
  }
  
  // changelogがなければ現在のステータスでよいが、変更された場合は変更前のfromが最初のステータスになるのでここで書き換える
  if(arrays.length >= 2) {// arraysの初期lengthは1で、changelogが増える度に+1となるため、2以上なら
    firstLine[2] = arrays[1][2];// 最初の配列の2番目である"from"を書き換える
  }
  
  arrays.push(firstLine);// 最初の行をarrays配列に追加する
  setValuesToSheet(arrays);
}

/************************************
履歴を取得して返す
************************************/
function getHistories(key) {
  var options = {
    contentType: "application/json",
    headers: {"Authorization": " Basic " + getProp("token")}
  };
  var url = ISSUE_URL + key + '?expand=changelog';
  var response = UrlFetchApp.fetch(url, options);
  return response;
}


/************************************
シートに書き込む
************************************/
function setValuesToSheet(arrays){
  var sheet = SpreadsheetApp.openByUrl(ss_url).getSheets()[0];
  var last_row = sheet.getLastRow();
  var start_row = last_row + 1;
  var start_col = 1;
  var num_rows = arrays.length;
  var num_cols = arrays[0].length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(arrays);
}


/************************************
日付をフォーマットして返す
************************************/
function returnFormatDate(date) {
  return Utilities.formatDate(new Date(date), 'Asia/Tokyo', 'yyyy/MM/dd/HH:mm:ss');
}


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




関連記事

JIRAで複数の課題の変更履歴をシートに書き出したい

2020年2月16日日曜日

JIRAで複数の課題の変更履歴をシートに書き出したい


JIRAで特定の課題の変更履歴をシートに書き出したい
では1つの課題の履歴を取得して書き出しました。

今回は複数の課題を取得して書き出してくてやってみました。
課題を判別できるよう、A列にkey列を追加しています。



コード.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";

/************************************
これを実行する
************************************/
function getIssuesHistories() {
  var keys = ["KT-12", "KT-13", "KT-14"];
  var arrays = [];
  for(var i = 0; i < keys.length; i++) {
    var key = keys[i];
    getIssueHistories(key);
  }
}

/************************************
受け取った課題keyをgetHistoriesに渡して結果をシートに書き込む
************************************/
function getIssueHistories(key){
  var response = getHistories(key);
  var jobj = JSON.parse(response);
  var histories = jobj["changelog"]["histories"];
  var arrays = [];
  for(var i = 0; i < histories.length; i++) {
    var history = histories[i];
    var created = returnFormatDate(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"];
      arrays.push([key, field, from, to, created]);
    }
  }
  setValuesToSheet(arrays);
}

/************************************
履歴を取得して返す
************************************/
function getHistories(key) {
  var options = {
    contentType: "application/json",
    headers: {"Authorization": " Basic " + getProp("token")}
  };
  var url = ISSUE_URL + key + '?expand=changelog';
  var response = UrlFetchApp.fetch(url, options);
  return response;
}


/************************************
シートに書き込む
************************************/
function setValuesToSheet(arrays){
  var sheet = SpreadsheetApp.openByUrl(ss_url).getSheets()[0];
  var last_row = sheet.getLastRow();
  var start_row = last_row + 1;
  var start_col = 1;
  var num_rows = arrays.length;
  var num_cols = arrays[0].length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(arrays);
}


/************************************
日付をフォーマットして返す
************************************/
function returnFormatDate(date) {
  return Utilities.formatDate(new Date(date), 'Asia/Tokyo', 'yyyy/MM/dd/HH:mm:ss');
}


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



関連記事

JIRAで特定の課題の変更履歴をシートに書き出したい

JIRAで特定の課題の変更履歴をシートに書き出したい


特定の課題でどのフィールドが何から何にいつ変更されたかシートに書き出したい。



事前準備
  1. スクリプトのプロパティに「token」という名で値を保存しておく
  2. 以下の値を書き換える
    • var ISSUE_URL
    • var ss_url 
    • var key 



コード.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";

/************************************
これを実行する
************************************/
function getIssueHistories(){
  var key = "KT-12";
  var response = getHistories(key);
  var jobj = JSON.parse(response);
  var histories = jobj["changelog"]["histories"];
  var arrays = [];
  for(var i = 0; i < histories.length; i++) {
    var history = histories[i];
    var created = returnFormatDate(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"];
      arrays.push([field, from, to, created]);
    }
  }
  setValuesToSheet(arrays)
}

/************************************
履歴を取得して返す
************************************/
function getHistories(key) {
  var options = {
    contentType: "application/json",
    headers: {"Authorization": " Basic " + getProp("token")}
  };
  var url = ISSUE_URL + key + '?expand=changelog';
  var response = UrlFetchApp.fetch(url, options);
  return response;
}


/************************************
シートに書き込む
************************************/
function setValuesToSheet(arrays){
  var sheet = SpreadsheetApp.openByUrl(ss_url).getSheets()[0];
  var last_row = sheet.getLastRow();
  var start_row = last_row + 1;
  var start_col = 1;
  var num_rows = arrays.length;
  var num_cols = arrays[0].length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(arrays);
}


/************************************
日付をフォーマットして返す
************************************/
function returnFormatDate(date) {
  return Utilities.formatDate(new Date(date), 'Asia/Tokyo', 'yyyy/MM/dd/HH:mm:ss');
}


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



関連記事

JIRAでAPIトークンを取得したい
JIRA APIでissueの変更履歴を取得する
setValuesを使う話

Google Apps ScriptのHtml ServiceでスクリプトのURLを返したい


ScriptApp.getScriptId()
でスクリプトIDを取得できるのでそれを利用します。


スクリプトのアドレスバーを見てみるとこうなっています。
https://script.google.com/d/Script ID/edit?usp=drivesdk


Script IDの位置にScriptApp.getScriptId()で取得した値を入れてHtml側に返します。


今回のコード.gsでは、スクリプトファイルをHtml側から開けるようにしています。



コード.gs
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile("index");
}

function returnScriptUrl(){
  var scriptId = ScriptApp.getScriptId();
  var scriptUrl = "https://script.google.com/d/" + scriptId + "/edit?usp=drivesdk";
  return scriptUrl;
}




index.html
<!DOCTYPE html>
<html>
  <body>

  <a id="scriptUrlLink" target="_blank">Script URL</a>

<script>

getScriptUrl();

function getScriptUrl() {
  google.script.run
  .withFailureHandler(onFailure)
  .withSuccessHandler(onSuccess)
  .returnScriptUrl();
}

function onSuccess(scriptUrl) {
  document.getElementById("scriptUrlLink").href = scriptUrl;
}

function onFailure(e) {
  alert([e.message, e.stack]);
}

</script>

  </body>
</html>




2020年2月12日水曜日

JIRAのワークフローで使わないステータスを選択できなくしたい


選択できなくするためには削除する方法と選択肢に表示しない方法があるらしい。

ステータスの削除方法を調べてみると、アクティブなワークフローではできないようなことが書かれていました。ちょっと手間なので今回はやらない。
https://ja.confluence.atlassian.com/jirakb/cannot-add-transitions-or-delete-steps-in-draft-workflows-203392961.html


今回は、ステータス変更の選択肢に表示しない方法として、トランジションの削除をした手順を書いていきます。


実現した手順

STEP1: プロジェクト設定を選択します


STEP2: ワークフローを選択します


STEP3: アクションの鉛筆アイコンを選択します


STEP4: 対象のトランジションを選択して、トランジションを削除を選択します



STEP5: 削除します


STEP6: 下書きを公開します


これでステータスの選択肢に表示されなくなりました。


参考

ステータスフィールド値の定義
https://ja.confluence.atlassian.com/adminjiraserver/defining-status-field-values-938847108.html

下書きワークフローでトランジションを追加したりステップを削除したりすることができない
https://ja.confluence.atlassian.com/jirakb/cannot-add-transitions-or-delete-steps-in-draft-workflows-203392961.html

2020年2月11日火曜日

JIRA APIで指定した課題にコメントを追加したい


公式リファレンスを参考に、以下のコード.gsでいけました。



事前準備
  • 「BASE_URL」と「issueKey」は各自の値を設定します
  • スクリプトのプロパティに「token」という名でbase64Encodeしたトークンを入れておきます



tokenについては別途こちらにも書きました



コード.gs
var BASE_URL = "https://SITENAME.atlassian.net";
var issueKey = "KT-8";

function addComment() {
  var data = {
    "body": {
      "type": "doc",
      "version": 1,
      "content": [
        {
          "type": "paragraph",
          "content": [
            {
              "text": "added comment",
              "type": "text"
            }
          ]
        }
      ]
    }
  }

  var options = {
    method: 'post',
    contentType: 'application/json',
    headers: {'Authorization': ' Basic ' + getToken()},
    payload: JSON.stringify(data)
  }
  var url = BASE_URL + '/rest/api/3/issue/' + issueKey + "/comment";
  UrlFetchApp.fetch(url, options);
}

function getToken() {
  return PropertiesService.getScriptProperties().getProperty('token');
}





補足

上記は REST API v3 の場合で
v2 の場合は以下のコード2.gsでいけました。


コード2.gs
var BASE_URL = "https://SITENAME.atlassian.net";
var issueKey = "KT-8";

function addComment() {
  var data = {
        "body": "added comment v2"
      }

  var options = {
    method: 'post',
    contentType: 'application/json',
    headers: {'Authorization': ' Basic ' + getToken()},
    payload: JSON.stringify(data)
  }
  var url = BASE_URL + '/rest/api/2/issue/' + issueKey + "/comment";
  UrlFetchApp.fetch(url, options);
}

function getToken() {
  return PropertiesService.getScriptProperties().getProperty('token');
}



参考

Add comment v3
https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-issue-issueIdOrKey-comment-post

Add comment v2
https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-comment-post

Macのモデル別の技術仕様を知りたい


Mac のポートを調べる のページにモデル一覧があり
それぞれのモデルごとの技術仕様を確認できました。

Mac Pro
MacBook Pro
MacBook Air
iMac
Mac mini


参考

Mac のポートを調べる
https://support.apple.com/ja-jp/HT201736

2020年2月9日日曜日

LINEの通知をバイブレーションで受け取りたい(Android Pixel 3a)


Pixel 3aで設定した手順です。

  1. ホームで右上にある歯車をタップする
  2. 設定が開くので通知をタップする
  3. 通知の設定項目を確認する
  4. 通知設定を開いて設定する
  5. メッセージ通知を開いて設定する
  6. 詳細設定を開いて設定する



キャプチャも残しておきます。


STEP1:ホームで右上にある歯車をタップする


STEP2:設定が開くので通知をタップする


STEP3:通知の設定項目を確認する


STEP4:項目内の「通知設定」を開いて設定する


STEP5:同じく項目内の「メッセージ通知」を開いて設定する


STEP6:折りたたまれている「詳細設定」を開いて設定する



補足

音をなしにするには

「音」をタップするとこんな画面になるので「1回のみ」をタップした


開いた画面で「マイサウンド」を選択して


「なし」を選択して「保存」した



2020年2月8日土曜日

「コミュニティ コネクタを使ってみる」をやってみる


データスタジオについてよく理解していないときに、とりあえず以下の公式ラボをやった備忘録。

コミュニティ コネクタを使ってみる
https://developers.google.com/datastudio/connector/get-started?hl=ja

かかった時間:2時間くらい(ブログを書きながら)



最終的に保存したコード

コード.gs
function getAuthType() {
  var response = { type: 'NONE' };
  return response;
}

function getConfig(request) {
  var cc = DataStudioApp.createCommunityConnector();
  var config = cc.getConfig();
  
  config.newInfo()
    .setId('instructions')
    .setText('Enter npm package names to fetch their download count.');
  
  config.newTextInput()
    .setId('package')
    .setName('Enter a single package name')
    .setHelpText('e.g. googleapis or lighthouse')
    .setPlaceholder('googleapis');
  
  config.setDateRangeRequired(true);
  
  return config.build();
}

function getFields(request) {
  var cc = DataStudioApp.createCommunityConnector();
  var fields = cc.getFields();
  var types = cc.FieldType;
  var aggregations = cc.AggregationType;
  
  fields.newDimension()
    .setId('packageName')
    .setType(types.TEXT);
  
  fields.newMetric()
    .setId('downloads')
    .setType(types.NUMBER)
    .setAggregation(aggregations.SUM);
  
  fields.newDimension()
    .setId('day')
    .setType(types.YEAR_MONTH_DAY);
  
  return fields;
}

function getSchema(request) {
  var fields = getFields(request).build();
  return { schema: fields };
}

function responseToRows(requestedFields, response, packageName) {
  // Transform parsed data and filter for requested fields
  return response.map(function(dailyDownload) {
    var row = [];
    requestedFields.asArray().forEach(function (field) {
      switch (field.getId()) {
        case 'day':
          return row.push(dailyDownload.day.replace(/-/g, ''));
        case 'downloads':
          return row.push(dailyDownload.downloads);
        case 'packageName':
          return row.push(packageName);
        default:
          return row.push('');
      }
    });
    return { values: row };
  });
}

function getData(request) {
  var requestedFieldIds = request.fields.map(function(field) {
    return field.name;
  });
  var requestedFields = getFields().forIds(requestedFieldIds);

  // Fetch and parse data from API
  var url = [
    'https://api.npmjs.org/downloads/range/',
    request.dateRange.startDate,
    ':',
    request.dateRange.endDate,
    '/',
    request.configParams.package
  ];
  var response = UrlFetchApp.fetch(url.join(''));
  var parsedResponse = JSON.parse(response).downloads;
  var rows = responseToRows(requestedFields, parsedResponse, request.configParams.package);

  return {
    schema: requestedFields.build(),
    rows: rows
  };
}



appsscript.json
{
  "dataStudio": {
    "name": "npm Downloads - From Codelab",
    "logoUrl": "https://raw.githubusercontent.com/npm/logos/master/npm%20logo/npm-logo-red.png",
    "company": "Codelab user",
    "companyUrl": "https://developers.google.com/datastudio/",
    "addonUrl": "https://github.com/google/datastudio/tree/master/community-connectors/npm-downloads",
    "supportUrl": "https://github.com/google/datastudio/issues",
    "description": "Get npm package download counts.",
    "sources": ["npm"]
  }
}



1. Intro
Community Connectors is a feature for Data Studio that lets you use Apps Script to build connectors to any internet accessible data source. 
Apps Scriptを使って、ネット上のデータソースへのコネクタを構築できるらしい。


このラボで学習できること
  • Google Data Studio Community Connectorの仕組み
  • Google Apps Scriptを使用してコミュニティコネクタを構築する方法
  • Data Studioでコミュニティコネクタを使用する方法

2. Quick Survey

ここは各自でラジオボタンを選択して次へ

3. Overview of Community Connectors
npmでパッケージを公開し、日次でパッケージのダウンロード数を追跡したい。
このコードラボでは、npmパッケージダウンロードカウントAPIからこのデータを取得するコミュニティコネクタを構築する。
次に、Data Studioでそのコミュニティコネクタを使用して、このデータを視覚化するダッシュボードを構築できる。

4. Community Connector Workflow
基本的なコミュニティコネクタでは、次の4つの関数を定義する。
  • getAuthType()
  • getConfig()
  • getSchema()
  • getData()

5. Set up your Apps Script project
Apps Scriptで新規プロジェクトを作成する。
プロジェクト名を「My Codelab Connector」にして保存する。

6. Define getAuthType()
getAuthType()を書く。

コード.gs
function getAuthType() {
  var response = { type: 'NONE' };
  return response;
}

7. Define getConfig()
getConfig()関数のresponseでユーザーに表示する構成オプションを定義できる。
設定画面では、以下のフォーム要素で情報提供やユーザー入力を取得したりできる。
  • TEXTINPUT
  • TEXTAREA
  • SELECT_SINGLE
  • SELECT_MULTIPLE
  • CHECKBOX
  • INFO

INFO要素でユーザーへ指示を提供できる。
TEXTINPUT要素でユーザーから入力パッケージ名を取得できる。
それらのフォーム要素をconfigParamsキーの下にグループ化する。
接続しているAPIはパラメーターとして日付を必要とする。
getConfig()応答でdateRangeRequiredをtrueに設定し、すべてのデータ要求で日付範囲を提供するようにData Studioに指示する。
データソースがパラメーターとして日付を必要としない場合は省略できる。

コード.gsにgetConfig()関数を追加してみる。

コード.gs
function getConfig(request) {
  var cc = DataStudioApp.createCommunityConnector();
  var config = cc.getConfig();
  
  config.newInfo()
    .setId('instructions')
    .setText('Enter npm package names to fetch their download count.');
  
  config.newTextInput()
    .setId('package')
    .setName('Enter a single package name')
    .setHelpText('e.g. googleapis or lighthouse')
    .setPlaceholder('googleapis');
  
  config.setDateRangeRequired(true);
  
  return config.build();
}


8. Define getSchema()
getSchema()関数で、コネクターの選択された構成に関連付けられたスキーマを取得する。
getSchema()によって提供されたresponseに基づいて、ユーザーにフィールド画面を表示し、コネクター内のすべてのフィールドをリストする。
以下を含むスキーマの各フィールドに関する特定のメタデータを提供する。
  • フィールドの名前
  • フィールドのデータ型
  • 意味情報

詳細についてはgetSchema()およびFieldリファレンスを確認する。

コネクタのスキーマは固定されており、次の3つのフィールドが含まれている。
  • パッケージ名
    • ユーザーが提供するnpmパッケージの名前
  • ダウンロード
    • npmパッケージのダウンロード数
    • ダウンロード数の日付

getSchema()関数をコード.gsに追加してみる

コード.gs
function getFields(request) {
  var cc = DataStudioApp.createCommunityConnector();
  var fields = cc.getFields();
  var types = cc.FieldType;
  var aggregations = cc.AggregationType;
  
  fields.newDimension()
    .setId('packageName')
    .setType(types.TEXT);
  
  fields.newMetric()
    .setId('downloads')
    .setType(types.NUMBER)
    .setAggregation(aggregations.SUM);
  
  fields.newDimension()
    .setId('day')
    .setType(types.YEAR_MONTH_DAY);
  
  return fields;
}

function getSchema(request) {
  var fields = getFields(request).build();
  return { schema: fields };
}


9. Define getData() : Part 1
getData()によって提供される応答に基づいて、ダッシュボードでグラフをレンダリングおよび更新する。
以下の場合もgetData()が呼ばれる。
  • ユーザーがダッシュボードにグラフを追加する
  • ユーザーがチャートを編集する
  • ユーザーがダッシュボードを表示する
  • ユーザーは、関連するフィルターまたはデータコントロールを編集する
  • Data Studioにはデータのサンプルが必要です

リクエストオブジェクトの構造

{
  configParams: object,
  scriptParams: object,
  dateRange: {
    startDate: string,
    endDate: string
  },
  fields: [
    {
      name: Field.name
    }
  ]
}

getData()関数からのリクエストの例
{
  configParams: {
    package: 'jquery'
  },
  dateRange: {
    endDate: '2017-07-16',
    startDate: '2017-07-18'
  },
  fields: [
    {
      name: 'day',
    },
    {
      name: 'downloads',
    }
  ]
}


10. Define getData() : Part 2
getData()responseでは、要求されたフィールドにスキーマとデータの両方を提供する必要があり、コードを3つのセグメントに分割する。
  1. 要求されたフィールドのスキーマを作成する
  2. APIからデータを取得して解析する
  3. 解析されたデータを変換し、要求されたフィールドをフィルタリングする

getData()の構造
function getData(request) {

  // TODO: Create schema for requested fields
  
  // TODO: Fetch and parse data from API
  
  // TODO: Transform parsed data and filter for requested fields

  return {
    schema: <filtered schema>,
    rows: <transformed and filtered data>
  };
}


11. Define getData() : Part 3
getData()をコード.gsに書く。

コード.gs
function responseToRows(requestedFields, response, packageName) {
  // Transform parsed data and filter for requested fields
  return response.map(function(dailyDownload) {
    var row = [];
    requestedFields.asArray().forEach(function (field) {
      switch (field.getId()) {
        case 'day':
          return row.push(dailyDownload.day.replace(/-/g, ''));
        case 'downloads':
          return row.push(dailyDownload.downloads);
        case 'packageName':
          return row.push(packageName);
        default:
          return row.push('');
      }
    });
    return { values: row };
  });
}

function getData(request) {
  var requestedFieldIds = request.fields.map(function(field) {
    return field.name;
  });
  var requestedFields = getFields().forIds(requestedFieldIds);

  // Fetch and parse data from API
  var url = [
    'https://api.npmjs.org/downloads/range/',
    request.dateRange.startDate,
    ':',
    request.dateRange.endDate,
    '/',
    request.configParams.package
  ];
  var response = UrlFetchApp.fetch(url.join(''));
  var parsedResponse = JSON.parse(response).downloads;
  var rows = responseToRows(requestedFields, parsedResponse, request.configParams.package);

  return {
    schema: requestedFields.build(),
    rows: rows
  };
}


12. Update manifest
Appsスクリプトエディターで、[表示]> [マニフェストファイルを表示]を開く。


appscript.jsonファイルを書き換えて保存する。

appsscript.json
{
  "dataStudio": {
    "name": "npm Downloads - From Codelab",
    "logoUrl": "https://raw.githubusercontent.com/npm/logos/master/npm%20logo/npm-logo-red.png",
    "company": "Codelab user",
    "companyUrl": "https://developers.google.com/datastudio/",
    "addonUrl": "https://github.com/google/datastudio/tree/master/community-connectors/npm-downloads",
    "supportUrl": "https://github.com/google/datastudio/issues",
    "description": "Get npm package download counts.",
    "sources": ["npm"]
  }
}


これで最初のコミュニティコネクタを構築し、テストドライブの準備ができた。

13. Test your connector in Data Studio

ステップ1:
[公開]> [マニフェストから展開]を選択する。

ステップ2:
Latest Version (Head) をクリックするとリンクが表示されます。


ステップ3:
リンクをクリックして表示された画面で「承認」をクリックします。

Enter a single package name に「lighthouse」を入力して
「接続」をクリックします。


「レポートを作成」をクリックします。


「レポートに追加」をクリックします。


手順通り期間のグラフと期間の選択を配置する。



それで最初のコミュニティコネクタをテストできました。


14. Next steps


参考

コミュニティ コネクタを使ってみる
https://developers.google.com/datastudio/connector/get-started?hl=ja

Connect and visualize all your data in Data Studio
https://codelabs.developers.google.com/codelabs/community-connectors/#0




最新の投稿

JIRA APIをブラウザで実行したい

以下のURLで SITENAMEとPROJECT_KEYを各自のものに書き換えて ブラウザのアドレスバーに入れて開くと クエリの結果がブラウザで確認できます。 https://SITENAME.atlassian.net/rest/api/2/search?jql=p...