Googleフォームのお問い合わせから、Backlogのチケットを作成するGASを作成しました。
【構築したフロー】
1.Googleフォームで作成したお問い合わせフォームからお問い合わせ実行
2.GASで問い合わせ内容を受け取り、Backlogのチケット作成APIを叩く
3.チケット作成APIよりBacklogでチケットが作成され、Slackに通知がいく
Googleフォームを作成
下記を参考にフォーム作成
https://www.g-workspace.jp/googleworkspace-reference/forms/create-form/
フォームの項目
・依頼者名
・件名
・タスクの種類
・詳細
・添付資料
・対応期日
BacklogのapiKeyを取得
apikeyの発行方法は下記のリンクを参考
https://support-ja.backlog.com/hc/ja/articles/360035641754-API%E3%81%AE%E8%A8%AD%E5%AE%9A
ここで発行したkeyをGASで使用し、Boacklog APIを叩きます。
※注意※
apiKeyを取得したユーザがBacklogのチケット作成者になります。
Baclogのチケットを作成するGASを作成
作成したGoogleフォーム編集画面のメニューから「< > スクリプトエディタ」を選択し、GAS編集画面を開きます。
完成形
コピペしていただき、「******」の箇所と「{ワークスペースID}」をご自身の環境に合うように入力していただければ使用可能です。
ファイルは全部で5つ作成しています。
・コード.gs(メイン)
・api.gs
・enums.gs
・backlog.gs
・slack.gs
全てをコード.gsに記述してもいいですが、メンテナンスを考えて分けることにしました。
※Logger.logは不要であれば削除してください。
コード.gs
/**
* Googleフォームからの送信受付
*
* param {json} フォーム入力内容
* return void
*/
function doForm(e){
Logger.log('1.フォームからの送信を受け取り')
var body = createPostBody();
// 依頼者
var user = null;
var question = {
'number' : null,
'title' : null,
'answer' : null
};
var files = [];
// フォームの入力内容を取得
const formItems = e.response.getItemResponses();
for ( var i = 0; i < formItems.length; i++ ){
// 項目No
question.number = formItems[i].getItem().getIndex();
// 項目タイトル
question.title = formItems[i].getItem().getTitle();
// 項目への回答
question.answer = formItems[i].getResponse();
Logger.log('Response #%s to the question "%s" was "%s"',
( question.number + 1 ).toString(),
question.title,
question.answer);
switch ( question.number ) {
case 0 : // 依頼者名
user = question.answer;
break;
case 1 : // 件名
body.summary = question.answer;
break;
case 2 : // タスクの種類:
switch (question.answer) {
case '*******': // フォームの選択肢1の内容に書き換え必要
body['categoryId[]'] = CATEGORISE[0];
break
case '*******': // フォームの選択肢2の内容に書き換え必要
body['categoryId[]'] = CATEGORISE[1];
break
case '*******': // フォームの選択肢3の内容に書き換え必要
body['categoryId[]'] = CATEGORISE[2];
break
case '*******': // フォームの選択肢4の内容に書き換え必要
body['categoryId[]'] = CATEGORISE[3];
break
case '*******': // フォームの選択肢5の内容に書き換え必要
body['categoryId[]'] = CATEGORISE[4];
break
case '*******': // // フォームの選択肢6の内容に書き換え必要
body['categoryId[]'] = CATEGORISE[5];
break
}
break;
case 3 : // 詳細
body.description = '依頼者:' + user + '\n' + question.answer;
break
case 4 : // 添付資料
files = question.answer;
break
case 5 : // 対応期日
if ( question.answer)
body.dueDate = question.answer;
break
}
}
Logger.log('2.送信内容');
Logger.log(body);
// backlogチケット作成
Logger.log('3.チケット作成');
response = createBacklogTicket(body);
Logger.log(response.getContentText());
// Slack送信
Logger.log('4.Slack送信');
const issueUrl = BASE_URL + '/view/' + JSON.parse(response.getContentText()).issueKey;
const slackMsg = '\nお問い合わせがありました!\n\n' + issueUrl + '\n\n' + '【件名】\n' + body.summary + '\n\n' + '```' + body.description + '\n\n期日:'+ body.dueDate + '```';
sendSlack(slackMsg);
Logger.log('5.ファイル数:' + files.length);
// 添付ファイルがある場合
if( files.length > 0) {
Logger.log('5-1.ファイルをBacklogへ送信開始');
/*
* 以下の処理について補足
* Backlogは添付ファイル名が日本語の場合、文字化けしてしまう。
* 対策として、renameFileメソッドで一旦ファイル名を「file_{issuelKey}_{number}.mineType」」に変換しチケットに登録
* 元のファイル名は対象チケットにコメントとして登録する
*/
// ファイルを添付する対象チケットのissueKeyを取得
const issueKey = JSON.parse(response.getContentText()).issueKey;
const data = sendFilesToBacklog(files, issueKey);
Logger.log('5-2.ファイルをBacklogへ送信完了');
// ファイル名をチケットのコメントに送信する
var comment = {
'content': ''
};
// 添付ファイルの数だけ、コメントにファイル名を追加
for ( var i = 0; i < data.ids.length; i++ ) {
comment['attachmentId[' + i + ']'] = data.ids[i].toString();
comment['content'] = comment['content'] + data.names[i] + '\n';
}
Logger.log('6.課題へコメントを送信');
Logger.log(comment);
addFileNameToBacklogComment(comment, issueKey);
Logger.log('7.完了');
}
}
/**
* backlogへのリクエストパラメータを作成するメソッド
* return body {obj} // backlogリクエストパラメータ
*/
function createPostBody(){
var body = {
'projectId': PROJECT_ID, // BacklogのプロジェクトID
'summary': '',
'description': '',
'issueTypeId': TASK, // Backlogのプロジェクトの課題タイプのID
'priorityId': MIDDLE, // Backlogのプロジェクトの優先度ID
'startDate': getDate(),
'notifiedUserId[0]': USER1, // チケット作成を知らせたいユーザのID
'notifiedUserId[1]': USER2, // 同上
'notifiedUserId[2]': USER3, // 同上
}
return body;
}
// 日付取得
function getDate() {
var date = new Date();
return Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy-MM-dd');
}
api.gs
apiに関係する情報を全てまとめています。
API_KEYはBacklogで取得したkeyをコピペします。
BOT_ACCESS_TOKENは、SlackのBotを作成した際に発行されるトークンです。
/**
* Backlog
* apiKey
*/
const API_KEY = '***********************************'
/**
* Slack
* token
*/
const BOT_ACCESS_TOKEN = '*********************************************';
/**
* Backlog
* API URL
*/
const API_KEY_URL = '?apiKey=' + API_KEY;
const BASE_URL = 'https://{ワークスペースID}.backlog.com'; // ご自身のBacklogのURL
// プロジェクト取得 Method:GET
const GET_PROJECT = '/api/v2/projects';
// 課題を作成 Method:POST
const CREATE_ISSUE = '/api/v2/issues';
// 自分自身のアカウント情報を取得
const GET_MY_ACCOUNT_INFO = '/api/v2/users/myself';
// ユーザ情報取得
const GET_USERS = '/api/v2/users';
// 添付ファイルの送信
const SEND_FILE = '/api/v2/space/attachment';
// 課題のコメント追加
const ADD_COMMENT = '/api/v2/issues/:issueIdOrKey/comments';
/**
* Slack
* API URL
*/
const POST_SLACK_MESSAGE = 'https://slack.com/api/chat.postMessage';
backlog.gs
Backlogのチケット作成、ファイル送信等のメソッドをまとめたファイルです。
/**
* backlogチケット作成
* params body {obj} // リクエストパラメータ
* return response {HTTPResponse}
*/
function createBacklogTicket(body){
var options = {
headers:{
'Accept': 'application/json',
},
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
payload: body,
muteHttpExceptions:false
}
// 課題を作成
var response = UrlFetchApp.fetch(BASE_URL + CREATE_ISSUE + API_KEY_URL, options);
return response
}
/**
* 課題にコメントを追加
* params comment {string} // 課題に記入するコメント
* params key {作成されたチケットのキー} //
* return response {HTTPResponse}
*/
function addFileNameToBacklogComment(comment, key){
var options = {
headers:{
'Accept': 'application/json',
},
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
payload: comment,
muteHttpExceptions:false
}
// コメントを追加
Logger.log(UrlFetchApp.getRequest(BASE_URL + '/api/v2/issues/'+ key + '/comments' + API_KEY_URL, options));
var response = UrlFetchApp.fetch(BASE_URL + '/api/v2/issues/'+ key + '/comments' + API_KEY_URL, options);
return response
}
/**
* ファイルをBacklogへ送信
* params files {array} // GoogleDriveのファイルID
* params issueKey {作成されたチケットのキー} // KK_TS_HD_〇〇
* return result {obj} // Backlog側に送信したファイルIDとファイル名
*/
function sendFilesToBacklog(files, issueKey){
var names = [];
var ids = [];
for ( var i = 0; i < files.length; i++ ) {
var file = DriveApp.getFileById(files[i]);
// ファイル名生成・格納
const fileName = issueKey + '_' + ( i + 1 );
names.push(fileName + ' → ' + file.getName().split(' - ')[0]);
const blob = renameFile(file, fileName);
var options = {
headers:{
'Accept': 'application/json',
},
method: 'POST',
payload: {
'file': blob
},
muteHttpExceptions:false
}
// 添付ファイル送信
response = UrlFetchApp.fetch(BASE_URL + SEND_FILE + API_KEY_URL, options).getContentText();
ids.push(JSON.parse(response).id);
}
// Backlog側に送信したファイルIDとファイル名
const result = {
'ids': ids,
'names': names
}
return result
}
/**
* ファイル名を「file_{issuelKey}_{number}.mineType」に変換するメソッド
* params file {obj}
* params fileName {string} // file_:issuelKey_:No
* return blob {obj} blobオブジェクト
*/
function renameFile(file, fileName){
var blob = file.getBlob();
const tmpName = blob.getName();
const index = tmpName.lastIndexOf('.');
const mineType = tmpName.substr(index);
return blob.setName(fileName + mineType);
}
slack.gs
Slackに関係するメソッドをまとめたファイルです
/**
* Slackへのメッセージ送信
*
* param {string} 送信メッセージ
* return void
*/
function sendSlack(message){
const messageOptions = {
"method" : "post",
"contentType": "application/x-www-form-urlencoded",
"payload" : {
"token": BOT_ACCESS_TOKEN,
"channel": CHANNEL_ID,
"text": message
// グループメンバーにメンションを付けたい場合は下記を使用
// "text": '<!subteam^' + GROUP_ID + '>' + message
}
};
UrlFetchApp.fetch(POST_SLACK_MESSAGE, messageOptions);
}
enums.gs
コード.gsやその他ファイルに、ハードコードでユーザIDやプロジェクトID等を記入してもいいのですが、可読性を高めるためと、メンテナンスコストを減らすため、定数としてまとめたファイルです。
「*******」をご自身の環境に書き換えて使ってください。
※定義した定数は、コード.gsで使用している定数です。
/**
* Backlog
*/
// チケット作成を通知したいアカウントのID (後述のAPIで取得)
const USER1 = '******';
const USER2 = '******';
const USER3 = '******';
// プロジェクト情報
const PROJECT_KEY = '*******'; // Backlogのプロジェクト画面で確認可能
const PROJECT_ID = '******'; // 後述のAPIで取得
// 種別(後述のAPIで取得)
const TASK = '******';
const REQUEST = '******';
const BUG = '******';
const OTHER = '******';
// カテゴリー(APIで取得)
const CATEGORISE = [
'******', // Backlog側の選択肢1のID
'******', // Backlog側の選択肢2のID
'******', // Backlog側の選択肢3のID
'******', // Backlog側の選択肢4のID
'******', // Backlog側の選択肢5のID
'******', // Backlog側の選択肢6のID
];
// 優先度
const HEIGHT = '*';
const MIDDLE = '*';
const LOW = '*';
/**
* Slack
*/
// チャンネルID
const CHANNEL_ID = '***********';
// グループID (グループにメンションをつけたい場合に使用)
const GROUP_ID = '**********';
Backlog・Slackの各種IDを取得する
ファイル名はなんでもいいですが「utils.gs」とかにしておきます。
enums.gsの「******」の箇所を埋めるためのメソッドを全て記載しています。
コピペしていただければOKです。
utils.gs
/**
* Backlogのプロジェクト情報取得
* PLOJECT_IDを取得
*/
function getProject() {
var options = {
method: 'GET',
contentType: 'application/json',
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch(BASE_URL + GET_PROJECT + '/' + PROJECT_KEY + API_KEY_URL, options);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
/**
* プロジェクトで設定しているカテゴリーを取得
* https://developer.nulab.com/ja/docs/backlog/api/2/get-category-list/#%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%83%BC%E4%B8%80%E8%A6%A7%E3%81%AE%E5%8F%96%E5%BE%97
*/
function getProjectCategory() {
var options = {
method: 'GET',
contentType: 'application/json',
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch(BASE_URL + GET_PROJECT + '/' + PROJECT_KEY + '/categories' + API_KEY_URL, options);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
/**
* Backlogプロジェクトの種別を取得
*/
function getProjectKind() {
var options = {
method: 'GET',
contentType: 'application/json',
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch(BASE_URL + '/api/v2/projects/' + PROJECT_KEY + '/issueTypes' + API_KEY_URL, options);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
/**
* Backlogプロジェクトの優先度を取得
*/
function getProjectPriority() {
var options = {
method: 'GET',
contentType: 'application/json',
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch(BASE_URL + '/api/v2/priorities' + API_KEY_URL, options);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
/**
* Backlogのユーザ一覧を取得
* チケット作成を通知するユーザのIDを取得する
*/
function getBacklogUsers() {
var options = {
method: 'GET',
contentType: 'application/json',
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch(BASE_URL + '/api/v2/users' + API_KEY_URL, options);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
/**
* Backlogの自分自身の情報を取得
* 自分自身のIDを取得する
*/
function getMyself(){
var options = {
method: 'GET',
contentType: 'application/json',
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch(BASE_URL + GET_MY_ACCOUNT_INFO + API_KEY_URL, options);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
/**
* Slackのグループ情報取得
* グループにメンションを送りたい場合に必要なグループIDを取得
*
* param {string} 送信メッセージ
* return void
*/
function getGroupInfo(){
const messageOptions = {
"method" : "get",
"contentType": "application/x-www-form-urlencoded",
"payload" : {
"token": BOT_ACCESS_TOKEN,
}
};
var response = UrlFetchApp.fetch('https://slack.com/api/usergroups.list', messageOptions);
Logger.log('Response:' + response.getResponseCode());
const obj = JSON.parse(response.getContentText());
Logger.log(obj);
}
各メソッドの実行方法
実行する関数名を指定し、「実行」をクリック。
実行結果からenums.gsの「******」を埋める
画面下部の実行ログにレスポンスデータが表示されます。
IDは「123324.0」と小数点がついていますが、enums.gsに記入する際は、整数部分のみでOKです。
GASのトリガーの設定を行う
サイドメニュの時計マークをクリック
「トリガーを追加」をクリック
設定は下記でOKです。
まとめ
以上、長くなりましたが、Googleフォームのお問い合わせから、Bocklogのチケット作成 + Slack通知のGASでした。
Skackのbot作成手順は割愛していますが、下記の記事が参考になります。
https://note.com/kawamura_/n/nede5b0f71353