はじめに
こちらはSlack Advent Calendar 2019の21日目の記事となります。
今回は、仕事を通じて作成したグループ分けのbotについて詳しく書いていこうと思います。
当初は「悪用厳禁! Slack活用における情報収集 黒魔術」というタイトルで、Slackを導入している企業や組織での情報収集の小ネタについて書こうと思いましたが、転職して日が浅いのでブラックなジョークは止めておこう(※違法ではないです)という保守的思考が働いたのと、どうせならエンジニアらしいことを優先して書きたいと考えたためお蔵入りとなりました。
そちらの方も、今後機会があればコラム的な感じで書いていきたいと思います(リアクション頂ければ書くためのモチベーションが高まります)。
背景・目的
作るに至った理由
元々は前職での”社内のシャッフルランチのグループ分け”を補助するために、個人的に開発したものです。
グループ分けを簡単に行うことができるWebサービスはもちろん存在していましたが、ポチポチする時間が長く工数が掛かったり、メンバーの抜け漏れが多いという課題がありました。
そこで、コマンド一発でグループ分けを実行するスクリプトを一度書いてしまって、業務負荷を減らせないかと考えたことが背景にあります。
”いい感じ”とは?
自前で実装した理由の一つに、「剰余をグループにしない」というものがありました。
例を挙げて少し詳しく説明します。全員で27人のチームがあり、ランチのためにチームを5つのグループに分けたいとしましょう。この場合「6人グループを2つと、5人グループを5つ」がいわゆる”いい感じ”に当たります。
しかし、様々なグループ分けのコードをググって探してみると、「6人グループが4つと、3人グループが1つ」というような、余りで構成されたグループが出来上がってしまうものが多くありました。
これでは各グループで均等にメンバーが割り振られず残念なランチになってしまうので、そのような”余りものグループを作らない”という実装を行いました。
ソースコード
※社内ランチの社内呼称が”クアトロランチ”だったため、リポジトリ名もそれにあやかる形になっております
実装
依存関係・バージョン
- Ruby:2.5.5(そこまで気にしなくて大丈夫)
- slack-ruby-client
- dotenv
- csv
スクリプト
以下のスクリプトをコマンドラインから実行することで、グループ分けを行います。
シャッフルをしている部分は、27行目のmembers.shuffle
メソッドの部分です。あと、個人的な嗜好によりメソッドを細かい責務で分けておりますので、読みづらい方も居るかと思います。
require 'slack-ruby-client'
require 'dotenv'
require 'csv'
Dotenv.load
today = Date.today.strftime("%Y/%m/%d")
groups, members = [], []
numbers_of_group = ENV['NUMBERS_OF_GROUP'].to_i
comment = ENV['COMMENT'].to_s
channel = ENV['CHANNEL'].to_s
def setup_slack
set_slack_api_token
client = Slack::Web::Client.new
return client
end
def set_slack_api_token
Slack.configure do |conf|
conf.token = ENV['SLACK_API_TOKEN']
end
end
def load_members(members)
read_csv(members)
members.shuffle
end
def read_csv(members)
CSV.foreach('data/members.csv') do |member|
members << member
end
end
def create_groups(numbers_of_group, groups, members)
numbers_of_group.times { create_group(groups) }
generate_csv_file(members, numbers_of_group, groups)
end
def create_group(groups)
empty_group = Array.new
groups << empty_group
end
def generate_csv_file(members, numbers_of_group, groups)
grouped_members_csv = CSV.generate do |csv|
split_members(members, numbers_of_group, groups, csv)
end
save_csv_file(grouped_members_csv)
end
def split_members(members, numbers_of_group, groups, csv)
members.each_with_index do |member, i|
number = i % numbers_of_group
group = groups[number]
assign_member_into_groups(group, number+1, member[0], csv)
end
push_groups_into_csv_file(csv, groups)
end
def assign_member_into_groups(group, number, member, csv)
group << number unless group.include?(number)
group << member
end
def push_groups_into_csv_file(csv, groups)
groups.each do |group|
csv << group
end
return csv
end
def save_csv_file(grouped_members_csv)
File.open('data/grouped_members.csv', 'w') do |file|
file.write(grouped_members_csv)
end
end
def send_slack_api_with_csv(client, today, comment, channel)
client.files_upload(
channels: channel,
as_user: true,
file: Faraday::UploadIO.new('data/grouped_members.csv', 'text/csv'),
title: "#{today} quattro groups",
filename: 'grouped_members.csv',
initial_comment: comment
)
end
client = setup_slack
members = load_members(members)
create_groups(numbers_of_group, groups, members)
send_slack_api_with_csv(client, today, comment, channel)
コード:https://github.com/f-teruhisa/quattro-bot/blob/master/script/slack.rb
.envファイル
以下の .env
ファイルに設定情報を追記し、ルートディレクトリに格納しておきます。
SLACK_API_TOKEN=
NUMBERS_OF_GROUP=
CHANNEL=
COMMENT=
設定する情報はそれぞれ以下の通りです。
envファイルのため、文字列をクォートで囲ったりする必要はありません。
SLACK_API_TOKEN=後述するSlackのAppを作成し取得するAPIトークン
NUMBERS_OF_GROUP=グループの数
CHANNEL=Slackのチャンネル(#は省略)
COMMENT=Slack通知する際に加えたい文章
ちなみに、COMMENT=Slack通知する際に加えたい文章
に関しては、改行を含む場合のみシングルクォーテーションで囲むことで、改行を含んだメッセージを表現することができます。
csv
このスクリプトを実行するにあたり、以下の2つのcsvファイルを活用します。
members.csv
data
フォルダに、元データとなるグループ分けする対象のmember.csv
ファイルを格納します。以下のサンプルデータの通り、1つのデータを1行1カラムで格納してください。
Fukumoto
Tanaka
Yamamoto
Inoue
Aida
Ueda
Suzuki
Sato
Nakamura
Fujioka
Ito
Hara
Ohara
Yamasaki
Kawai
Kondo
※元が社内ランチ用のプログラムなため
member
という名前になっています
grouped_members.csv
こちらは、スクリプトを実行した後に自動生成されるcsvファイルです(よって最初は存在しないファイルです)。
元データをグループ分けした結果が、grouped_members.csv
として吐き出されます。Slackにはこのcsvファイルをメッセージに添付してPOSTするという形を取っております。以下のサンプルデータは、先程のmember.csv
を5グループに分ける設定をし実行してみた結果です。
1,Kawai,Yamamoto,Nakamura,Inoue
2,Tanaka,Yamasaki,Ueda
3,Fukumoto,Hara,Ohara
4,Aida,Fujioka,Ito
5,Sato,Suzuki,Kondo
便宜上”グループ○○(数字)”という指定ができコミュニケーションが円滑に行われるように、各グループに番号を振っています。また、番号を振ることで、番号を先頭とした序列が各グループ内で暗黙的に作られるように設計しました。
実行手順
基本的にはGithubに乗せています...が、英語なのとSlackの操作までは記載しておらず、若干不親切なのでこちらで詳細に記載します。
「SLACK_API_TOKENの取得」と「ワークスペースへのアプリケーション導入」の2つがゴールとなります。
Custom IntegrationsからSlack botを作成
Slackで開発できるbotにはいくつか種類があり、設定が複雑なので以下より画面を追って説明していきます。
今回は「Custom Integrations」からbotを開発していきます。
ちなみに、以下のような画面です。
上記の「Bots」をクリックすることで、Slackに登録するbotを設定していくことが可能です。
ちなみに、Slackのワークスペースが分かる場合は、以下のURLから直接遷移することができます。
https://{Slackのワークスペース名}.slack.com/apps/manage/custom-integrations
SLACK_API_TOKENを取得
「Bots」をクリックすると、以下のような画面が出てきます。
「新規作成」と「編集」のどちらでも、SLACK_API_TOKENは取得できます。
「新規作成」の場合はbotの名前を入力した後に、「編集」の場合は直接以下の画面に遷移するので、この画面でSLACK_API_TOKENを取得できます。後ほどenvファイルに書き込むので、コピーしておきましょう。
リポジトリをcloneする
冒頭に紹介したリポジトリを、ローカル環境の任意のフォルダにcloneしてください。
$ git clone https://github.com/f-teruhisa/quattro-bot.git
.envファイルに設定情報を記載
前半に紹介した.env
ファイルにSLACK_API_TOKEN含む各設定情報を追記し保存してください。グループの数やメッセージの内容は任意のもので構いません。
csvファイルに元データを格納
data/members.csv
に、前半に紹介した通り、グループ分けしたいデータを一列に格納し保存してください。
ワークスペースへのアプリケーション導入
作成したbotからSlackの任意のチャンネルに通知されるように、アプリケーションを導入しましょう。
通知を飛ばしたいSlackのチャンネルに入り画面の左上、チャンネル名をクリックするとメニューバーが現れます。
このメニューから「Add an app」をクリックし、作成したbotを導入します。
「Add an app」をクリックすると以下のような画面が表示されるので、先程設定したbotの名前で検索すると表示されるはずです。選択して「Add」を選びましょう。
これで、Slack側でもbotから通知を受けるための準備が整いました。
コマンド実行
さて、ここまでで準備は終わりです。
以下のコマンドを実行して、グループ分けとSlack通知を行いましょう。
$ ruby script/slack.rb
ちなみに、実行には依存gemが必要ですがGemfileにまとめておりますので、そちらを参考にしてgemのインストールも合わせて行ってください。以下のコマンドを一度に実行すれば、インストールが完了するかと思います。
$ gem install dotenv\
csv\
slack-ruby-client
Slack通知
設定した情報が正しければ、以下のようにSlackに通知が飛んでくると思います。
一度設定してしまえば、あとはコマンドを実行するだけなので、グループ分けに悩まずに済みます。
※実際に、仕事でアップデートを担当するgemを分けた時に使った通知メッセージ
今後考えていること
それなりに使う機会に恵まれたbotですが、以下のような課題感もあるので、今後ヒマがあれば徐々に対処していきたいです。
定期運用に向いていない
今回のbotはインスタントなグループ分けには重宝するのですが、部署や名前などの属性は考慮していないため、普通にガンガン衝突します。そのため、(偶然ですが)同じヒトやモノと何度も同じグループになることが多々あります。
衝突を防ぐためには、属性値の考慮や過去の結果を格納するDBを用意するなどの工夫が考えられます。
また、コマンドを叩かないとグループ分けやSlackへの通知が行われないため、実行者が忘れたり寝坊すると死にます。
これは、後にも説明するGoogle App Script(GAS)への移行やLamda化などで対応が可能であると考えておりmす
GUIで実行できない
工数を考慮しGUIは実装しなかったのですが、ターミナル操作を行うため非エンジニアのメンバーが利用するハードルは高いです(一応セットアップと実行するコマンドを教えれば事足りますが)。
この辺は軽めのUIを作って、herokuにデプロイすることでブラウザでの操作を行う拡張ができるでしょう。
csvの管理工数
社員情報など、マスタ情報がスプレッドシートやエクセルで管理され、氏名が縦に並んでいる(≒まんまコピペしてmember.csv
に格納できる)ことを前提にcsvからデータを取り出す処理を書いていますが、「新入社員など新しいデータが必要な際に追加を忘れ、ナチュラルにハブってしまう」可能性があります。
これは、「(前職で)社内にRubyエンジニアが多くメンテナンスしやすいだろう」という安易な理由でRubyを選定したことがやや裏目に出てしまいました。
マスタデータをスプレッドシートで管理している会社も多い(前職はそうでした)ので、GASで実装するほうがシンプルに保てたと感じます(Git管理しにくい、スプレッドシートに依存するというデメリットはありますが)。また、GASではサーバレスに定期実行を行うことも可能なので、先に上げた”定期運用に向いていない”という課題にも対処できます。
さいごに
最後までお付き合い頂きありがとうございました。
筆者はこのbotスクリプトの開発が、人生初のいわゆる”コーポレート・エンジニアリング”だったのですが、自分の書いたスクリプトを何度も使ってもらい、実際に価値を生んでいる瞬間を何度も見ることができるのは、エンジニアとしても成功体験であったと振り返っています。
これからも「イケてないなあ...この作業」というプロセスを見かけたら、積極的にハックしていけるような精神を大事にエンジニアとして成長していきたいと思いました。
みなさんもSlackというすばらしいチャットツールを最大限に活用し、ときにはハックしてみると面白い経験ができるかもしれません。ぜひ試してみてください。