0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Googleフォーム+GASでお問い合わせ内容をBacklogのチケットに登録する

Last updated at Posted at 2021-03-14

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編集画面を開きます。

スクリーンショット 0003-03-14 12.52.38.png

完成形

コピペしていただき、「******」の箇所と「{ワークスペースID}」をご自身の環境に合うように入力していただければ使用可能です。

ファイルは全部で5つ作成しています。
・コード.gs(メイン)
・api.gs
・enums.gs
・backlog.gs
・slack.gs

全てをコード.gsに記述してもいいですが、メンテナンスを考えて分けることにしました。

※Logger.logは不要であれば削除してください。

コード.gs

コード.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を作成した際に発行されるトークンです。

api.gs

/**
 * 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.gs
/**
 * 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.gs
/**
 * 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で使用している定数です。

enums.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

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

各メソッドの実行方法

実行する関数名を指定し、「実行」をクリック。

スクリーンショット 0003-03-14 14.25.55.png

実行結果からenums.gsの「******」を埋める

画面下部の実行ログにレスポンスデータが表示されます。
IDは「123324.0」と小数点がついていますが、enums.gsに記入する際は、整数部分のみでOKです。

GASのトリガーの設定を行う

サイドメニュの時計マークをクリック
「トリガーを追加」をクリック

スクリーンショット 0004-02-11 2.01.58.png

設定は下記でOKです。

スクリーンショット 0004-02-11 2.03.22.png

まとめ

以上、長くなりましたが、Googleフォームのお問い合わせから、Bocklogのチケット作成 + Slack通知のGASでした。

Skackのbot作成手順は割愛していますが、下記の記事が参考になります。
https://note.com/kawamura_/n/nede5b0f71353

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?