Slackで初歩的な質問ができるチャンネルを作ったら大反響だった話

こんにちは。奥村です。

本日は、タイトルの通りですが
Slackで初歩的な質問ができるチャンネルを作ったのでその話をします。

内容としては

  • はじめた経緯
  • はじめるために準備したもの
    • 匿名の質問をSlackに投稿するGoogleフォーム
    • 質問に対して匿名で回答するGoogleフォーム
    • 過去の質問・回答をスプレッドシートに集めるGAS
  • やってみてどうだったか?

というものになります。

以下がイメージです

質問

f:id:AdwaysEngineerBlog:20180816202604p:plain

回答

f:id:AdwaysEngineerBlog:20180816210147p:plain:w450

経緯

私自身が初歩的な質問を誰かにしようとした時に誰に聞けばいいのか少し悩んでしまったことから始まります。
「初歩的な質問をして煽られないかな?」と、当時の私が思ってしまったのです。

分からないことがあった時、質問をするのに多少のハードルがあるかと思います。
それが初歩的な質問ならなおハードルが高くなると思います。

初歩的な質問ウェルカム!ウェルカム!という雰囲気があれば心理的安全性も生まれ、誰に聞いても問題ない環境になると考えました。
そうして、まずは場所作りだな!となり、「今さら聞けない質問チャンネル」というチャンネルを作成しました。

初歩的な質問ウェルカム!ウェルカム!という雰囲気があったとしても、「本当はどう思ってるんだろう」と考えてしまう人も少なからずいると思います。
そうして、匿名で質問できた方がいいのではないかと思い、匿名で質問ができるフォームを作りました。

というのがチャンネルや周辺のツール・スクリプト群を作成した経緯です。

はじめるために準備したもの

業務内容的にスプレッドシートの管理をすることがよくあり、周辺のツール群は勉強も兼ねてGASで作ることにしました。

必要なもの

  • Slack
    • チャンネル × 1
  • Google フォーム × 2
  • Google Spread Sheet × 1
  • GAS × 1

作るもの

  • 匿名の質問をSlackに投稿するGoogleフォーム
  • 質問に対して匿名で回答するGoogleフォーム
  • 過去の質問・回答をスプレッドシートに集めるGAS

事前準備

これから作成するGASに「incomming web hookのURL」と「SlackのAccess Token」が出てきますので、事前に取得しておきます。 (上記の方法は割愛させて頂きます)


匿名の質問をSlackに投稿するGoogleフォーム

まずは、Googleフォームを作成します。 f:id:AdwaysEngineerBlog:20180816202432p:plain

  • 「質問」という項目を作り、段落にしておきます。
    • 段落としておくと、質問内で改行ができるので便利です。

メニューからスクリプトエディタを開くとGASの編集ページに移動しますのでそれを編集します。

function sendToSlack(message, channel) {
  var url = "ここにIncomming Web Hook のURLを張り付ける";
  var data = { "channel" : channel, "username" : "匿名の質問", "text" : message, "icon_emoji" : ":speak_no_evil: " };
  var payload = JSON.stringify(data);
  var options = {
    "method" : "POST",
    "contentType" : "application/json",
    "payload" : payload
  };
  UrlFetchApp.fetch(url, options);
}

function onFormSubmit(e){
  var question = e.response.getItemResponses()[0].getResponse();
  var message = "--- NEW ---\n" + question + "\n" ;
  sendToSlack(message, "#ここに送信するチャンネルを名前を指定");
}

保存したらトリガーを設定します。

f:id:AdwaysEngineerBlog:20180816202128p:plain

このようにすると、フォームがサブミットされたときにGASが実行されます。

一度質問を送ってみます。

f:id:AdwaysEngineerBlog:20180816202604p:plain

このように、指定したチャンネル宛てに質問が投稿されます。


質問に対して匿名で回答するGoogleフォーム

こちらも同じくGoogle フォームを作成します。 f:id:AdwaysEngineerBlog:20180816202936p:plain

  • 「質問のリンク」を作り、記述式にしておく
    • どの質問に回答するかをリンクの貼り付けで選択します。
  • 「匿名での回答」を作り、段落式にしておく
    • 質問へのリプライとして匿名でメッセージが投稿されます。

両方の項目とも入力必須にしておきます。

こちらも同じく、スクリプトエディタを開き編集していきます。

SlackのAPIを使いやすくしたライブラリを使わせて頂きます。 事前にライブラリに追加しておきましょう。

qiita.com

function sendSlack(link, answer, token) {
  var slackApp       = SlackApp.create(token);
  var channelId      = link.split("/")[4]
  var rawTimestamp   = link.split("/")[5].slice(1)
  var commaIndex     = rawTimestamp.length - 6
  var timestamp      = rawTimestamp.slice(0, commaIndex) + "." + timestampPlane.slice(commaIndex);
  var options = {
    username: "匿名の回答",
    thread_ts: timestamp
  }
  slackApp.postMessage(channelId, answer, options);
}

function onFormSubmit(e){
  var slackAccessToken = 'ここに取得したSlackのAccess Tokenを記入;
  var itemResponse = e.response.getItemResponses();
  for (var i = 0; i < itemResponse.length; i++){    
    var title          = itemResponse[i].getItem().getTitle();
    var postedMessage  = itemResponse[i].getResponse();
    switch (title) {
      case "質問のリンク":
        link = postedMessage;
        break;
      case "匿名の回答":
        answer = postedMessage;
        break;
      default:
        break;
    }
  }
  sendSlack(link, answer, slackAccessToken);
}

Slack APIのPostMessage のオプションで、「thread_ts(スレッドのタイムスタンプ)」を指定してPostすると、スレッドのリプライとして認識されるので、その方法でリプライしています。

こちらも同じく、フォームがサブミットされたときのトリガーを設定しましょう。

一度、質問へ回答してみます。

まず、質問のリンクを取得します。
メッセージのメニューから「Copy Link」でメッセージのリンクが取得できます。 f:id:AdwaysEngineerBlog:20180816205617p:plain f:id:AdwaysEngineerBlog:20180816205623p:plain:w300

補足: ここで取得できるURLは、

https://チームid.slack.com/archives/チャンネルID/タイムスタンプ

となっています。

フォームから送信されると、

f:id:AdwaysEngineerBlog:20180816210147p:plain:w450

と、匿名で回答できていることがわかります。


過去の質問・回答をスプレッドシートに集めるGAS

過去の質問・回答をスプレッドシートに書き込むGASを作ります。
作成後に定期実行し、自動でスプレッドシートがアップデートされるようにします。

  1. スプレッドシートを作成します。
  2. スプレッドシートのIDを取得します。
    • URLの 「https://docs.google.com/spreadsheets/d/ここの部分がIDになります/edit#gid=0」
  3. スプレッドシートの一行目にカラム名を入れます。
    • 「channel_id, timestamp, message, question, thread_ts」としました。
  4. GASを新規で作成します。

GASを編集します。

  • シート名はこの例では「sheet1」としています。(日本語だとデフォルトで「シート1」になっていると思います。)
function getSlackMessage(channelId, timestamp, slackAccessToken) {
  var slackApp = SlackApp.create(slackAccessToken);
  var options = {
    oldest: timestamp
  }
  var messages = slackApp.channelsHistory(channelId, options)['messages'];
  messages.pop()
  var messagesLength = messages.length;
  var addtionalRecord = []
  for (var i = 0; i < messagesLength; i++) {
    if (messages[i]['subtype'] == 'channel_join' || messages[i]['subtype'] == 'channel_leave') {
      continue;
    }
    var thread_ts = ""
    var messageText = messages[i]['text'];
    var messageTimestamp = messages[i]['ts']
    var question = false
    if (!messages[i].hasOwnProperty('thread_ts')) {
      question = true
    } else if (messages[i].hasOwnProperty('reply_count')) {
      question = true
    }
    if (messages[i].hasOwnProperty('thread_ts')) {
      thread_ts = messages[i]['thread_ts'];
    }
     addtionalRecord.unshift([channelId, messageTimestamp, messageText, question, thread_ts])
  }
  return addtionalRecord;
}

function setSpreadsheet (sheet, additionalRecord) {
    for (var recordIndex = 0; recordIndex < additionalRecord.length; recordIndex++) {
      sheet.appendRow(additionalRecord[recordIndex]);
  }
}

function addRecord() {
  var channelId        = "取得する先のチャンネルIDを記入"
  var slackAccessToken = 'ここに取得したSlackのAccess Tokenを記入';
  var spreadsheet      = SpreadsheetApp.openById('ここにスプレッドシートのIDを記入');
  var sheet            = spreadsheet.getSheetByName('sheet1');
  
  var lastRow              = sheet.getLastRow();
  var lastColumn           = sheet.getLastColumn();
  var lastRecord           = sheet.getRange(lastRow, 1, 1, lastColumn);  
  var lastRecordTimestamp  = "" + lastRecord.getValues()[0][1] + ""
  
  setSpreadsheet(sheet, getSlackMessage(channelId, lastRecordTimestamp, slackAccessToken))
}

function firstTimeRecord() {
  setSpreadsheet(SpreadsheetApp.openById('ここにスプレッドシートのIDを記入').getSheetByName('sheet1') ,getSlackMessage('取得する先のチャンネルIDを記入', 'タイムスタンプ', 'ここに取得したSlackのAccess Tokenを記入'))
}

このスクリプトでは、
一番最後の行のタイムスタンプを基準にSlack APIにGETするため、最初のレコードの登録は手動で行う必要があります。
そのため、firstTimeRecord関数を作成しています。
最初のレコードの2つまえのタイムスタンプを取得して、getSlackMessage関数の第2引数に入れ、実行するとレコードが追加されます。

addRecord関数を定期実行するように設定すれば、自動でメッセージを取得してくれるようになります。

時間主導のトリガーをセットしましょう。 f:id:AdwaysEngineerBlog:20180816215846p:plain

自動で更新されるのを待ってみましょう。

  • 3つの質問があります

f:id:AdwaysEngineerBlog:20180816220705p:plain:w500

  • 一つ目の質問に回答してみましょう

f:id:AdwaysEngineerBlog:20180816220657p:plain:w400

  • 二つの回答を投稿してみます

f:id:AdwaysEngineerBlog:20180816220841p:plain:w400

  • これが

f:id:AdwaysEngineerBlog:20180817123410p:plain:w600

  • 自動実行によってこうなります。

f:id:AdwaysEngineerBlog:20180817123348p:plain:w600

あとはこのデータを自由に使うだけですね。
フィルタを使ってソートや絞り込みをするだけでも見やすくなると思います。

やってみてどうだったか

タイトルにもある通り、思っていたより反響がありました!
口コミでチャンネルの存在が広がり、チャンネル作成1週間後にはエンジニア・デザイナーグループの2/3の人が参加しているほどになりました。

当初想定していた、技術的な質問だけでなく、会社のルールや取り組みについての質問まで投稿されるようになりました。
目安箱状態です。

良い回答には、チャンネルの参加者が「グッドボタン」のリアクションを押し、グッドボタンの多かった回答には「ベストアンサー」のリアクションがつくという流れが自然に出来上がっていました。

また、匿名で質問・回答を受け付けている分、たまに過激とも捉えれる投稿がされることがあります。
そういった質問・回答には、チャンネルの参加者が「お茶」のリアクションをつけるようにしています。
場が乱れそうだなと思ったら言及することも必要かもしれません。

「匿名で質問できるチャンネル」であり「今さら聞けない質問チャンネル」でもある。現在はこのような状態となっています。

まとめ

質問するときにおいて、
聞くか迷ってる、誰に聞くか迷っている時間が一番もったいないと考えています。

また、「初歩的な質問に対する回答 = 基礎的な知識」と考えると、多くの人が共通の基礎的な知識を目にすることになります。 このチャンネルによって、組織のスキル底上げに繋がることを期待しています。

最後までご覧頂きありがとうございました。