GMOアドマーケティングA.Yです。
このエントリーは、GMOアドマーケティング Advent Calendar 2018【12/6】 の記事を加筆修正したものです。
背景
社内ではBacklogとJIRAというふたつのチケット管理ツールを使っています。
Backlogはとても使いやすいチケット管理ツールですが、唯一の欠点は、時間管理機能が貧弱ということ・・・。
日毎の時間集計をすることができないんです。いつ、誰が何をしていたか、工数集計をすることができません。
かといって、いちいちふたつのチケット管理ツールを開いて、JIRAに時間を入力させるのもちょっとなぁ、と思って時間連携ツールを作ることにしました。
やりたいこと
前提:Backlogに当日の作業時間が入力されていること
1. Backlogから時間を取得する
2. 対応するJIRAチケットを検索する(Backlogの課題キーをJIRAのカスタムフィールド「Backlog課題番号」に持っている前提)
3. 検索にヒットしたら、対応するJIRAのチケットに時間を記録する
4. 検索にヒットしない場合、(Backlogの課題キーをJIRAのカスタムフィールド「Backlog課題番号」にセットしてチケットを作成
実装
それでは、GASで実装していきます。 **と記載されているところは、環境ごとに設定してください。
Main.gs
メインとなるクラス。ここで上記の「やりたいこと」に書いてある処理を順次呼んでいきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/* * Google Spread Sheet上の実行ボタン押すと実行される。メンバーには帰りがけにポチッと押して貰う想定。 * (Google Spread Sheetのwebhook機能は、社内のIP制限でつかえなかった) */ function main() { try { // 対象の課題一覧をBacklogから取得する var issuelist = getBacklog_Issues(); for (var i = 0; i < issuelist.length; i++) { // JIRAの連携チケットを検索して、(Backlog-keyをキーに) var jira_key = findJIRA(issuelist[i]); if (jira_key === undefined) { // チケットがない場合は作成する。 jira_key = createJIRA(issuelist[i]); } // 時間を記載 addWorklog(jira_key, issuelist[i]); } } catch (e) { var error = e; Logger.log("message:" + error.message + "\nfileName:" + error.fileName + "\nlineNumber:" + error.lineNumber + "\nstack:" + error.stack); } } |
GetBacklog.gs
Backlogから課題を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// backlog関連パラメタ var BACKLOG_URL = "https://*****.backlog.jp"; var API_KEY_PARAM = "?apiKey=******************"; var COUNT = 100; /** * Backlog課題一覧の取得 * @returns {undefined} */ function getBacklog_Issues() { // 当日を検索対象 var start_date = new Date(); var until_date = new Date(); // シートで選ばれた人を処理対象 var spreadsheet = SpreadsheetApp.openById('**********'); var sheet = spreadsheet.getSheetByName('**'); var value = sheet.getRange("**").getValue(); var backlog_user_id = value.substring(value.indexOf(":") + 1, value.length); // APIキーでBacklog認証&取得(課題一覧) // 抽出条件:直近1日で更新、かつ開発チームメンバーが更新したもの var result = UrlFetchApp.fetch(BACKLOG_URL + "/api/v2/issues" + API_KEY_PARAM + "&projectId[]=*****" + "&updatedSince=" + Utilities.formatDate(start_date, 'Asia/Tokyo', 'yyyy-MM-dd') + "&updatedUntil=" + Utilities.formatDate(until_date, 'Asia/Tokyo', 'yyyy-MM-dd') + "&assigneeId[]=" + backlog_user_id + "&count=" + COUNT); if (result.getResponseCode() !== 200) { return false; } return JSON.parse(result.getContentText()); } |
ProcessJira.gs
JIRAをBacklog課題キーをつかって検索し、ない場合はJIRAのチケット作成、ある場合はBacklogに記入された時間をJIRAに連携します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
/** * BacklogUserID : JIRAUserIDのマッピング */ function get_jira_userid(issue) { var userMapping = { '***backlogのユーザID***': '**JIRAのユーザID***', // 人数分設定してください。 }; for (var key in userMapping) { if (key === issue["assignee"]["mailAddress"]) { jira_usrid = userMapping[key]; } } return jira_usrid; } /** * ユーザ認証情報を返す * @returns token */ function get_jira_user_token(jira_usrid) { // https://ja.confluence.atlassian.com/cloud/api-tokens-938839638.html if (jira_usrid === 'admin') { //adminユーザだけ、token生成時はIDをメールアドレスに詰め替え jira_usrid = '**JIRAのユーザID**'; var pw = '***JIRAのpwトークン****'; // ユーザ数だけ追加が必要 } else if (jira_usrid === '**JIRAのユーザID2**') { var pw = '***JIRAのpwトークン2****'; } // ユーザIDがユーザID+ドメインだったので、ここで付加しています var token = Utilities.base64Encode(jira_usrid + '@**メールアドレスのドメイン**' + ':' + pw); return token; } /* * JIRA検索 * @param {type} issue * @returns jira_key */ function findJIRA(issue) { var id = '****'; // botユーザ var pw = '****'; var token = Utilities.base64Encode(id + ':' + pw); var options = { contentType: "application/json", headers: {"Authorization": " Basic " + token} }; // ここでは一例として「Backlog課題番号」というカスタムフィールドに、Backlog課題キーをセットしている前提です。 var url = 'https://*****.atlassian.net/rest/api/2/search/?jql=Backlog課題番号 ~' + issue["issueKey"]; var response = UrlFetchApp.fetch(url, options); var jobj = JSON.parse(response); if (jobj['total'] !== 0) { var jira_key = jobj['issues'][0]['key']; Logger.log('found JIRA key:' + jira_key); } return jira_key; } /* * JIRA作成 * @param {type} issue * @returns key */ function createJIRA(backlog_issue) { var id = '****'; // botユーザ var pw = '****'; var token = Utilities.base64Encode(id + ':' + pw); var jira_usrid = get_jira_userid(backlog_issue); var data = { project: {key: '***'}, issuetype: {name: 'タスク'}, priority: {name: '中'}, summary: backlog_issue["summary"], description: backlog_issue["description"], timetracking: {originalEstimate: backlog_issue["estimatedHours"]}, customfield_*****: backlog_issue["issueKey"], assignee: {name: jira_usrid}, }; var fields = {fields: data}; var payload = JSON.stringify(fields); var options = { method: 'post', payload: payload, contentType: 'application/json', headers: {'Authorization': ' Basic ' + token}, }; var response = UrlFetchApp.fetch('https://*****.atlassian.net/rest/api/2/issue/', options); var jobj = JSON.parse(response); var key = jobj['key']; Logger.log('created JIRA key:' + key); return key; } /** * 作業時間をJIRAに追記 * @param {type} jira_key * @param {type} bakclog_issue * @returns none */ function addWorklog(jira_key, bakclog_issue) { var jira_usrid = get_jira_userid(bakclog_issue); var token = get_jira_user_token(jira_usrid); if (bakclog_issue["actualHours"]) { var dt = new Date(); // JIRAが受け取れる形式にTimestampを整形してあげる。時差も修正する dt.setHours(dt.getHours() + 9); var t_started = dt.toISOString().slice(0, -1) + "+0900"; var data = { // きちんと時間単位もつける(h=hours) timeSpent: bakclog_issue["actualHours"] + "h", started: t_started, }; var payload = JSON.stringify(data); var options = { method: "post", payload: payload, contentType: "application/json", headers: {"Authorization": " Basic " + token}, }; // 当日の実績登録 UrlFetchApp.fetch('https://*****.atlassian.net/rest/api/2/issue/' + jira_key + '/worklog' , options); Logger.log('worklog add completed. target JIRA key:' + jira_key); } } |
地味にハマったこと
- JIRAのREST-APIはv2とv3があります。v3のほうはいまだベータ版であり、エラー・メッセージが間違っていたり、ざっくりしすぎていることがあります。僕の場合自分のリクエストがどう間違っているかわからずとてもハマりました。
-
JIRAのapiドキュメントはときどきリンクが間違っていて、v2のドキュメントを見ているはずが、v3のドキュメントにリンクされていたりします。注意してください。URLで見分けましょう。こちらはv2 https://developer.atlassian.com/cloud/jira/platform/rest/v2/
-
JIRAのapiドキュメントには、Exampleという項目がありrequestパラメータ例が書いてありますが、フォーマット定義から自動生成されています。そのままrequest投げても動きません。あくまでもイメージです(笑)
しかしこのときの僕は知りませんでした。あんなに楽な方法があったなんて。。。
次回に続く。
マイブームは、ペーパードライバー卒業・天体観測・アマチュア無線・BCL受信・カセットテープデッキのメンテ修理・登山とか。
写真は飼っている文鳥のおつゆちゃん。