🧑‍💻

[業務改善]GASを使用したslackでの質問数集計自動化

2021/09/24に公開
3

結論

GASを使用し、slackに来る質問の集計を自動化しました。
(実際には、「GASによる集計」と「スプレッドシートの関数による分析」を行っていますが、スプレッドシートの情報は載せることができないため、GASについての説明のみをしています。)

背景

業務において、slackを通じて受講生からの質問対応を行っています。
従来はその質問を集計・分析をする際に、すべてのslackの質問投稿を手動でスプレッドシートにコピペをし、集計を行っていました。
多い時では週に120件ほどの質問がくるため、slackからスプレッドシートへのコピペを120回繰り返す必要がある状況でした。
これは業務効率化の余地がたくさんあると考え、GASを使用して自動化を行いました。

行ったこと

GASについては全く触れたことがなかったですが、他部署ではGASでslackと連携してデータを取得していると聞き、今回の事例もGASを使用すれば自動化できると考えて調べ始めました。
そもそもGASとは何か、といったところからの検索であったため時間はかかりましたが、Google検索すればほとんどの知識はキャッチアップすることができたため、都度検索しながら進めていきました。
最終的には、日次で起動して、所定のチャンネルに投稿された前日分の質問を全て取得し、スプレッドシートに転記させるところまでを自動で行うプログラムを作成しました。
作成にかかった時間は8~10時間ほどです。

また、プログラムを作成する過程で、現在は手動のため集計できていなかった項目についても自動で取得できることに気づきました。
例えば、質問投稿に記載されているカリキュラムのURLを集計することでどのカリキュラムにおける質問が多いのか、といったデータであったり、月ごと・週ごと・曜日ごと・日ごと・時間帯ごと・受講生ごとなどかなり細分化されたデータを取得することも可能にしました。

結果

従来の手動で行う方法では、担当者が毎月16時間程度で(質問が多い月ではもっと多くの時間をかけて)集計を行なっていたため、その時間をなくすことができました。
また、これまでは担当者が集計を行った後でないと分析結果を確認できなかったですが、日次でよりリアルタイムの分析を可能にしました。

さらに、追加で取得したデータにより、これまでは集計・分析できていなかった項目も取得できるようになり、カリキュラムやサービスの質を改善する土台を作りました。

コード

以下に実際のコードを貼り付けます。
slackのapitokenやチャンネル情報などの値は一部書き換えています。

GASのトリガー設定にて、毎日朝8時ごろにmainFunctionを実行する設定をしております。

//-------------------------------------//
//-------------- 事前処理 --------------//
//-------------------------------------//
// slack用変数定義
const slack_token = '~~slack-apiのtoken~~';  // slack-apiで発行したtoken
const channel_id = '~~データ取得元のslackチャンネルID~~';  // チャンネルID(URLの末尾)
const slack_url = "https://slack.com/api/conversations.history";  // チャンネルから投稿を取得するための設定

// スプレッドシート用変数定義
const sheet_url = '~~集計結果を転記するシートURL~~'  // 質問数集計シートのURL
const sheet_name = "質問集計シート";                           // 転記するシート名
const spread_sheet = SpreadsheetApp.openByUrl(sheet_url);   // GoogleSpreadsheetのAPIを叩いて、質問数集計シートを開く
const sheet = spread_sheet.getSheetByName(sheet_name);      // 開いた質問集計シートのうち、「質問集計シート」という名前のシートを開く

// 日時用
const today = new Date();  // 実行した当日の日付を取得

//------------------------------------//
//-------------- 主処理 ---------------//
//------------------------------------//
const mainFunction = () => {
  const prev_date = getPrevDate();                       // ①前回日時を取得
  const slack_messages = getSlackHistories(prev_date);   // ②slackからデータを取得
  writeToSpreadsheet(slack_messages);                    // ③スプレッドシートに転記
};

//---------- ①前回日時を取得 ----------//
const getPrevDate = () => {
  prev_date = sheet.getRange('I1').getValue();  // I1セルの情報を取得
  return prev_date;
}

//------- ②slackからデータを取得 -------//
const getSlackHistories = (prev_date) => {
  // スプレッドシートの前回日付の00:00:00からのデータを取得
  const oldest = getUnixTime(prev_date);

  // 前日の23:59:59までのデータを取得
  const today_date = today.getDate();
  const yesterday = new Date();
  yesterday.setDate(today_date - 1);
  yesterday.setHours(23);
  yesterday.setMinutes(59);
  yesterday.setSeconds(59);
  const latest = getUnixTime(yesterday);

  // データ取得用の設定
  const payload = {
    "token": slack_token,   //Slack AppのToken
    "channel": channel_id,  //ChannelのID
    "oldest": oldest,       //この日時から
    "latest": latest,       //この日時まで
    "inclusive": true,      //oldestとlatestを含めるか true, false
    "count": 1000           //取得する件数 1〜1000(とりあえず1000件にしておけばこれ以上くることはないだろうという想定)
  };

  const options = {
    'method': 'post',
    'payload': payload
  };
  
  // 投稿データを取得
  const response = UrlFetchApp.fetch(slack_url, options);   // slackのAPIを叩いて投稿データを取得
  const data = JSON.parse(response);                        // JSON形式に変換
  const messages = data['messages'];                        // 投稿データのみに絞る
  return messages;
}

// Unix時間に変換
const getUnixTime = (dateTime) => {
  const date = new Date(dateTime);
  const milsec = date.getTime();
  const sec = milsec / 1000;
  const time = sec.toString();
  return time;
}

//------- ③スプレッドシートへ転記 -------//
const writeToSpreadsheet = (slack_messages) => {
  const messages_number = slack_messages.length;   // 要素の数を取得(要素数分ループ処理をする)
  let last_row = sheet.getRange(1, 1).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();    // 現在埋まっているセルの最終行

  // 古い順になるようにソート 
  const sorted_slack_messages = slack_messages.sort(function(a, b) {
    if (a.ts < b.ts) {
        return -1;
    } else {
        return 1;
    }
  });

  // 1レコードずつ転記
  let array_data = '';    // ループ時に使用する変数を先に定義
  for (let i = 0; i < messages_number; i++) {
    array_data = sorted_slack_messages[i];

    // 質問以外は除外する
    let topic = array_data['text'].search('チャンネルのトピックを設定しました');  // チャンネルのトピックを設定した場合は除外
    let question = array_data['text'].search('■カリキュラムURL')             // 「■カリキュラムURL」という文字列がある場合は質問だと判断 
    if (topic !== -1 || question === -1){
      continue;
    };

    // UnixTimeから日時に変換
    let date_time = getDateTime(array_data['ts']);
    let date = date_time.slice(0, 10);
    let time = date_time.slice(11, 19);

    // 質問内容からカリキュラムだけを抜き出す
    let curriculum = getCurriculumNumber(array_data['text']);

    // 転記するセルを取得
    last_row += 1;
    let user_cell = 'A' + `${last_row}`;
    let date_cell = 'B' + `${last_row}`;
    let time_cell = 'C' + `${last_row}`;
    let text_cell = 'D' + `${last_row}`;
    let curriculum_cell = 'E' + `${last_row}`;

    // セルに転記
    sheet.getRange(user_cell).setValue(array_data['user']);
    sheet.getRange(date_cell).setValue(date);
    sheet.getRange(time_cell).setValue(time);
    sheet.getRange(text_cell).setValue(array_data['text']);
    sheet.getRange(curriculum_cell).setValue(curriculum);
  }

  // 最終更新日付を更新
  const today_year = today.getFullYear();
  const today_month = today.getMonth() + 1;
  const today_date = today.getDate();
  const write_date = `${today_year}` + `/` + `${today_month}` + `/` + `${today_date}`;
  sheet.getRange('I1').setValue(write_date);
};

// 標準時間に変換
const getDateTime = (unixtime) => {
  const date = new Date(unixtime * 1000);
  return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd/HH:mm:ss');
};

// カリキュラムの末尾4桁を取得
const getCurriculumNumber = (message) => {
  let curriculum_num = '';
  let curriculum = message.match(/curriculums...../);   // 正規表現でカリキュラムのURLを抽出

  if (curriculum != null) {
    curriculum_num = curriculum[0].slice(12);    // 末尾4桁を取得(12文字目以降を取得)
  };

  return curriculum_num;
};

Discussion

huahua

参考にさせていただきます。

javascriptのコードにハイライトが利いているとより嬉しいです。

```js
hoge
```

mizukimizuki

ご指摘いただきありがとうございます。
修正しましたので、ぜひご確認ください。

huahua

見やすくなりました!ありがとうございます!