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

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518144/923b2ec0-a2fd-edce-2aec-fca56553b0ab.png)

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

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

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

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


###コード.gs

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

```javascript: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のチケット作成、ファイル送信等のメソッドをまとめたファイルです。

```javascript: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に関係するメソッドをまとめたファイルです

```javascript: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で使用している定数です。

```javascript: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
```javascript: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518144/1a438d96-1387-b701-150b-376180504b3b.png)

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

## GASのトリガーの設定を行う
サイドメニュの時計マークをクリック
「トリガーを追加」をクリック

![スクリーンショット 0004-02-11 2.01.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518144/2b3a204c-5c0b-5d90-a520-210a4ceb4856.png)

設定は下記でOKです。

![スクリーンショット 0004-02-11 2.03.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518144/d1e8dac2-250c-360e-21ba-7d137b14eb63.png)


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

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