gas
spreadsheet
jira
PORTDay 18

JIRAのチケット番号をspreadsheetに記載するgasを書いてみた

やりたいこと

弊社では開発の進捗はJIRAで管理、現場からの開発依頼はスプレッドシート、開発と現場のコミュニケーションツールはslackを使っています。

ディレクターはJIRAをスプレッドシートを行ったり来たり、slackに開発の進捗を報告したりしてめんどくさいので、
①JIRAのチケットが発行された、またかチケットのステータスが完了した際にスプレッドシートに情報を転記。
②開発内容をslackんい投稿する。
をGoogle Apps Script(以下gas)で自動化しました。

そのためにやったことは以下のことです。
①JIRAからスプレッドシートにチケットデータ送る(JIRAのwebhookを利用)
②チケットデータをスプレッドシートに転記(gasを利用)
③slackにスプレッドに転記したデータを投稿(incoming webhookを利用)
になります。順に説明していきます。

JIRA→spreadSheet→slack
FireShot Capture 3 - アイコン - draw.io_ - https___www.draw.io_.png (244.7 kB)

①JIRAのwebhookの作成する

webhook作成は超簡単です。
システム>詳細_Webフック>Webフックの作成をクリックします。
今回はJIRAチケット作成時、ステータス完了の更新時にスプレッドシートにデータを送りたいので、課題の作成済、更新済にチェックマークをつけて、保存します。
※課題関連イベントにproject = hogehoge と記載すると特定のプロジェクトのデータのみを送ることができます。
スクリーンショット 2017-12-11 16.32.01.png (121.7 kB)

ああああ.png (298.0 kB)

webhookから送られるデータは以下のようなjson形式になっています。

jirawebhook.json
{
    "changelog": {
        "id": "id", 
        "items": [
            {
                "field": "assignee", 
                "fieldId": "assignee", 
                "fieldtype": "jira", 
                "from": null, 
                "fromString": null, 
                "to": "name", 
                "toString": "name"
            }, 
            {
                "field": "priority", 
                "fieldId": "priority", 
                "fieldtype": "jira", 
                "from": null, 
                "fromString": null, 
                "to": "3", 
                "toString": "Medium"
            }, 
            {
                "field": "reporter", 
                "fieldId": "reporter", 
                "fieldtype": "jira", 
                "from": null, 
                "fromString": null, 
                "to": "name", 
                "toString": "name"
            }, 
            {
                "field": "ステータス", 
                "fieldId": "status", 
                "fieldtype": "jira", 
                "from": null, 
                "fromString": null, 
                "to": "1", 
                "toString": "Open"
            }
        ]
    }, 
    "issue": {
        "fields": {
            "aggregateprogress": {
                "progress": 0, 
                "total": 0
            }, 
            "aggregatetimeestimate": null, 
            "aggregatetimeoriginalestimate": null, 
            "aggregatetimespent": null, 
            "assignee": {
                "accountId": "hoge", 
                "active": true, 
                "avatarUrls": {
                    "16x16": "hoge", 
                    "24x24": "hoge", 
                    "32x32": "hoge", 
                    "48x48": "hoge"
                }, 
                "displayName": "mail", 
                "emailAddress": "mail", 
                "key": "name", 
                "name": "name", 
                "self": "hoge", 
                "timeZone": "Asia/Tokyo"
            }, 
            "attachment": [], 
            "comment": {
                "comments": [], 
                "maxResults": 0, 
                "startAt": 0, 
                "total": 0
            }, 
            "components": [], 
            "created": "datetim", 
            "creator": {
                "accountId": "hoge", 
                "active": true, 
                "avatarUrls": {
                    "16x16": "hoge", 
                    "24x24": "hoge", 
                    "32x32": "hoge", 
                    "48x48": "hoge"
                }, 
                "displayName": "name", 
                "emailAddress": "mail", 
                "key": "name", 
                "name": "name", 
                "self": "hoge", 
                "timeZone": "Asia/Tokyo"
            }, 
            "customfield_10900": null, 
            "description": null, 
            "duedate": null, 
            "environment": null, 
            "issuelinks": [], 
            "issuetype": {
                "avatarId": 10308, 
                "description": "雑作業", 
                "iconUrl": "hoge" 
                "id": "10300", 
                "name": "雑作業", 
                "self": "hoge", 
                "subtask": false
            }, 
            "labels": [], 
            "lastViewed": "2017-12-11T16:26:07.383+0900", 
            "priority": {
                "iconUrl": "hoge", 
                "id": "3", 
                "name": "普通", 
                "self": "hoge"
            }, 
            "progress": {
                "progress": 0, 
                "total": 0
            }, 
            "project": {
                "avatarUrls": {
                    "16x16": "hoge", 
                    "24x24": "hoge", 
                    "32x32": "hoge", 
                    "48x48": "hoge"
                }, 
                "id": "id", 
                "key": "hoge", 
                "name": "hoge", 
                "self": "hoge"
            }, 
            "reporter": {
                "accountId": "hoge", 
                "active": true, 
                "avatarUrls": {
                    "16x16": "hoge", 
                    "24x24": "hoge", 
                    "32x32": "hoge", 
                    "48x48": "hoge"
                }, 
                "displayName": "name", 
                "emailAddress": "mail", 
                "key": "name", 
                "name": "name", 
                "self": "hoge", 
                "timeZone": "Asia/Tokyo"
            }, 
            "resolution": null, 
            "resolutiondate": null, 
            "security": null, 
            "status": {
                "description": "担当者が作業を開始できる状態を表します。", 
                "iconUrl": "hoge", 
                "id": "1", 
                "name": "オープン", 
                "self": "hoge", 
                "statusCategory": {
                    "colorName": "blue-gray", 
                    "id": 2, 
                    "key": "new", 
                    "name": "To Do", 
                    "self": "hoge"
                }
            }, 
            "subtasks": [], 
            "summary": "てすと", 
            "timeestimate": null, 
            "timeoriginalestimate": null, 
            "timespent": null, 
            "timetracking": {}, 
            "updated": "2017-12-11T16:26:07.184+0900", 
            "versions": [], 
            "votes": {
                "hasVoted": false, 
                "self": "hoge", 
                "votes": 0
            }, 
            "watches": {
                "isWatching": true, 
                "self": "hoge", 
                "watchCount": 0
            }, 
            "workratio": -1
        }, 
        "id": "id", 
        "key": "key", 
        "self": "hoge"
    }, 
    "issue_event_type_name": "issue_created", 
    "timestamp": 1512977167230, 
    "user": {
        "accountId": "id", 
        "active": true, 
        "avatarUrls": {
            "16x16": "hoge", 
            "24x24": "hoge", 
            "32x32": "hoge", 
            "48x48": "hoge"
        }, 
        "displayName": "name", 
        "emailAddress": "mail", 
        "key": "name", 
        "name": "name", 
        "self": "hoge", 
        "timeZone": "Asia/Tokyo"
    }, 
    "webhookEvent": "jira:issue_created"
}

正直、長っ!!って感じです。さて、次はこのデータをスプレッドシートにカキカキしていきましょう。

②Google App Scriptの作成する

まずはコードを書くためのgasのファイルを作成します。
Google driveにアクセスして新規→その他→Google Apps Scriptをクリックします。(Google Apps Scriptがない方はアプリを追加からインストールしましょう。

これから作成したファイルにgasをカキカキしていきます。
っっっっっt.png (92.6 kB)

gas.js
function doPost(response) {
  //jira webfookのdataを取得
  var json = response.postData.contents;
  var data = JSON.parse(json);

  var jiraNumber = data.issue.key; // チケットIDを取得
  var title = data.issue.fields.summary; // summaryを取得
  var status = data.issue.fields.status.statusCategory.name;  // 解決状況を取得
  var developlistId = data.issue.fields.customfield_[hoge];  // 開発依頼番号取得
  var issueCreated = data.webhookEvent; // チケット作成フラグ

  //スプレッドシートを取得
  var sheets = SpreadsheetApp.openById('spreadsheetId');
  var sheet = sheets.getSheetByName("sheetName");
  var sheetData = sheet.getDataRange().getValues();
  var sheetLastRow = sheet.getLastRow();

  //スプレッドシートで取得する列番号(配列なので実列番号-1)今回は開発IDとタイトルを取得しています
  var developIdColumn = 0;
  var developTitleColumn = 0;

  //スプレッドシートで書き込む列番号。今回はチケットID, 検証環境のリリース日、リリースフラグに書き込みます
  var jiraNumberColumn = 0;
  var stagingReleaseDayColumn = 0;
  var stagingReleaseFlagColumn = 0;

  //本日の曜日を取得。弊社では火、金に検証環境にリリースするので曜日を比較するために本日の曜日を取得しています
  var today = new Date();
  var dayOfTheWeek = today.getDay();

  //slack投稿用変数
  var message = "";

  //jira作った場合、jira完了した場合
  if(status == "完了"){
      for(var j=0;j<sheetLastRow;j++){ //スプレッド最終行までループまわす
        if(developlistId == sheetData[j][developIdColumn]){  //jiraの開発依頼番号とスプレッドの開発依頼番号が等しい場合
          var stagingReleaseDay = stagingReleaseDayfnc(dayOfTheWeek); //検証環境のリリース日を取得
          var writeRowNumber = j + 1; //スプレッドシート入力用の行番号を格納
          sheet.getRange(writeRowNumber,stagingReleaseDayColumn).setValue(stagingReleaseDay); //スプレッドシートにjiraで取得した内容を書き込む
          sheet.getRange(writeRowNumber,stagingReleaseFlagColumn).setValue(1);
          message = "開発依頼ID: " + String(sheetData[j][developIdColumn]) + "\n" + sheetData[j][developTitleColumn] + "の開発が完了しました。" //slackに送るメッセージ作成
          slackpost(message); //slack送信
        }
      }
   } else if(issueCreated == 'jira:issue_created' ){
      for(var i=0;i<sheetLastRow;i++){ //スプレッド最終行までループまわす
        if(developlistId == sheetData[i][developIdColumn]){ //開発依頼番号があった場合
                 var writeRowNumber = i + 1; //スプレッドシート入力用の行番号を格納
          sheet.getRange(writeRowNumber, jiraNumberColumn).setValue(jiraNumber); //スプレッドシートにjiraで取得した内容を書き込む
          message = "開発依頼ID: " + String(sheetData[j][developIdColumn]) + "\n" + sheetData[j][developTitleColumn] + "の開発が開始しました。"; //slackに送るメッセージ作成
          slackpost(message); //slack送信
        }
      }
   }

}

//検証環境のリリース日を出力する関数
function stagingReleaseDayfnc(dayOfTheWeek) {
  //当日の日付を格納
  var today = new Date();

  //翌日の日付を格納
  var tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate()+1);
  var formatTomorrow = Utilities.formatDate(tomorrow,"JST","yyyy/MM/dd");

  //2日後の日付を格納
  var twoDaysLater = new Date();
  twoDaysLater.setDate(twoDaysLater.getDate()+2);
  var formattwoDaysLater = Utilities.formatDate(twoDaysLater,"JST","yyyy/MM/dd");

  //3日後の日付を格納
  var threeDaysLater = new Date();
  threeDaysLater.setDate(threeDaysLater.getDate()+3);
  var formatthreeDaysLater = Utilities.formatDate(threeDaysLater,"JST","yyyy/MM/dd");

  //検証環境のリリース日を出力
  if(dayOfTheWeek == 0){
    return twoDaysLater
  }else if(dayOfTheWeek == 1){
    return tomorrow
  } else if(dayOfTheWeek == 2){
    return threeDaysLater
  } else if(dayOfTheWeek == 3){
    return twoDaysLater
  } else if(dayOfTheWeek == 4){
    return tomorrow
  } else if(dayOfTheWeek == 5){
    return threeDaysLater
  } else if(dayOfTheWeek == 6){
    return twoDaysLater
  }
}


 //slackへの投稿する関数
function slackpost(message) {

  var postUrl = 'hoge'; //Incoming WebHooksのWebhook URLをコピペする
  var username = 'jirabot';  // 通知時に表示されるユーザー名

  //slackで送るのもを格納
  var jsonData =
      {
        "username": username,
        "text":message
      };

  var payload = JSON.stringify(jsonData); //jsonデータに変換

  //送り方とかもろもろの設定を記入
  var options =
      {
        "method" : "post",
        "contentType" : "application/json",
        "payload" : payload
      };

  UrlFetchApp.fetch(postUrl, options);  //postUrlにデータを送る
}

③slackにスプレッドに転記したデータを投稿する

webhookの設定
slackでデータを送信したいチャネルに移動し、Add an appをクリック
スクリーンショット 2017-12-14 15.04.16.png (61.4 kB)

Get Essential Appsをクリック
スクリーンショット 2017-12-14 15.04.49.png (260.8 kB)

検索でincoming WebHooksを検索してクリック
add configuration

Add Configurationをクリック
add configuration

投稿したいチャネルを選択してAdd Incoming WebHooks integrationをクリック
スクリーンショット 2017-12-14 15.05.29.png (238.5 kB)

iconなどを適当に変更してSave Settingsをクリック
名称未設定 2.png (204.4 kB)

動かしてみた

JIRAでステータスを完了にしてみる
名称未設定 2.png (202.9 kB)

無事slackに投稿されました
スクリーンショット 2017-12-14 15.15.54.png (43.7 kB)