こんにちは、コーポレートシステム部 Corporate ITグループのあおしょーこと青松です。 普段はSREチームのDevOpsエンジニアとして社内の業務改善を担当しています。
ありがたいことに弊社では急成長とともに従業員も増えつつあるのですが、それと同時に社内には多くのペインが散見されるようになりました。
その中の一つに社員全員が利用する勤怠申請がありまして、この度SREチームが改善することになりましたので今回はそちらについてお話していきたいと思います。
目次
改善後の申請
いきなりですが、まずは改善後の申請方法をご覧いただきましょう。
今回の改善で勤怠申請用のSlackアプリを作成しました。
Slackアプリのホームから申請フォームを開き、フォーム内の各項目に入力して送信すると自分が所属する勤怠チャンネルに申請情報を飛ばすことができます。
その勤怠チャンネルに投稿されたメッセージ内に「確認」ボタンがあり、承認者がそれを押すことで承認が行われるような流れとなっています。
またアプリ内の「メッセージ」タブから申請履歴も確認できます。
これによって自分が過去にどのような申請を行ったのか遡って確認できるようになっています。
以降の章ではなぜこのような改善を行うことになったのか、またどのような技術を使用して実現したのかについて説明します。
改善に至った経緯
続きまして、以前の申請方法をご覧いただきましょう。
元々は各部門・グループごとに勤怠専用のSlackチャンネルがあり、それら各チャンネルからワークフロー経由で申請フォームを開いて申請を飛ばしていました。
このワークフローはチャンネルと1対1になっているためチャンネルの数だけワークフローがありました。
ここで勤怠関連の作業を行っている労務としての課題がありました。
まず、当時勤怠専用のチャンネルは60前後あったためその数分のワークフローを変更する必要があった点です。
そして、申請された各勤怠のデータをチェックして行っている作業があるのですがそのデータがスプレッドシートで管理されていたことです。
しかも、そのデータの取得方法はGASを実行して各チャンネルの過去の投稿を取得してスプレッドシートへ反映する構成でした。
これらの作業に時間がかかったり入力ミスが発生するとのことで、ここに大きなペインがあることが分かりました。
またその他の課題として
- 労務ではエンジニアが不在のためシステム化などの対策が取りづらい
- 過去の自分の申請が検索しづらい
- ワークフロービルダーで作成できるフォームでは制限があり、例えば日付などは手動入力しなければならないため、人によってフォーマットに差が出てしまう
などがあり、今回は私たちSREチームが立ち上がって改善することになりました。
意識したこと
コーポレートシステム部では「EX(Employee Experience)をシンプルにする」というミッションを掲げています。
これを基にユーザ(社員)にとって使いやすいか?ということを意識して対応方法を考えました。
労務の課題を解決したとしても実際に利用する社員にとって使いづらいものであれば意味がありません。
今回、実現するにあたって「勤怠チャンネルを一つにまとめる」「スラッシュコマンドからフォームを開く」などを検討しましたが、最終的にApp Homeを使うことにしました。
SlackではBlock KitというJSON形式で定義してUIを表現できるフレームワークがあります。
それを使ってSlackアプリのホーム上でインタラクティブにやりとり出来るものがApp Homeです。
ユーザ体験の観点からApp Homeを使うことは決めたのですがその他についてはあまり大きく変えないように意識しました。
例えば「勤怠チャンネルを廃止してアプリだけで完結するようにする」「フォームの内容を大幅に変更する」などの構想はありましたが、一度に盛り込みすぎるとユーザにとって混乱を招く原因となりかねないためです。
技術的なお話
まずはシステム構成図をご覧ください。
今回はSREチームで使い慣れているAPIGateway + Lambdaという構成を選択しました。
AWS SAMを使うことで開発〜デプロイがお手軽にできて開発に専念できますし、何よりコストが抑えられるという理由が大きかったです。
しかし、後述しますがLambdaを始めとしたFaaS環境でSlackアプリを作る場合は注意しなければならないこともあります。
Slack Bolt
今回は構成図内の「勤怠申請処理」でSlack BoltというSlack公式のフレームワークを利用することにしました。
開発にかかる余計な手間を省くことができるという意味もあるのですが、何よりLazy Listenersという機能を利用できる点が大きかったです。
まず前提として、Slackでは3秒以内に200(OK)のHTTPレスポンスを返さないとタイムアウトするという仕様があります。いわゆる3秒ルールというやつです。
しかし、FaaSでは基本的にHTTPレスポンスを返したあとにプロセスを実行することができないため、とりあえず3秒以内にレスポンスしてその後に処理するみたいなことが出来ません。
つまり、何もせずFaaSでSlackアプリを開発しようとすると3秒以内に処理を終えなければならないという縛りが発生してしまいます。
その上で今回はFaaSであるAWS Lambdaを利用することを決めていたためこの問題と向き合う必要がありました。
そこでその問題を解決する機能がそう、Lazy Listenersです。
この機能は内部的に同一のLambda関数を非同期でInvokeしてそのLambda関数で処理を行う機能なのですが、これによってHTTPレスポンスを返したあともプロセスを実行し続けることができるようになり、FaaS環境においても3秒以上の処理を実現することができるようになるというものです。
今回はこの機能を使いたかったためにSlack Boltの導入を決めました。*1
App Home
SlackではBlock KitというJSON形式で定義してUIを表現できるフレームワークがあります。
それを使ってSlackアプリのホーム上でインタラクティブにやりとりができるものがApp Homeです。
今回作った勤怠申請アプリの実際のホーム画面はこんな感じです。
ちなみにSlackアプリではホームを開くとEvent Subscriptionsで指定したエンドポイントにリクエストが飛びます。
そこでBlock Kit Builderを使って生成したJSONを指定してviews_publish
すれば画面が切り替わります。
詳しくはサンプルコードを参考にしてみてください。
ちなみにボタンやセレクトメニューなどのアクションについてはInteractivity & ShortcutsのInteractivityで指定したエンドポイントにリクエストが飛びます。
サンプルコードでは割愛していますがlisteners/__init__.py
でapp.action(action_id)
という定義をすれば、そのaction_idと一致する処理が実行されます。
app.py
import os from slack_bolt import App from listeners import register_listeners from slack_bolt.adapter.aws_lambda import SlackRequestHandler app = App( process_before_response=True, token=os.getenv('SLACK_TOKEN'), signing_secret=os.getenv('SLACK_SIGNING_SECRET') ) register_listeners(app) def lambda_handler(event, context): slack_handler = SlackRequestHandler(app=app) return slack_handler.handle(event, context)
listeners/__init__.py
from listeners.app_home_opened import app_home_opened_callback def register_listeners(app): app.event('app_home_opened')( ack=lambda ack: ack(), lazy=[app_home_opened_callback] )
listeners/app_home_opened.py
from logging import Logger def app_home_opened_callback(client, event, logger: Logger): try: client.views_publish( user_id=event['user'], view={ 'type': 'home', 'blocks': [ { 'type': 'section', 'text': { 'type': 'mrkdwn', 'text': 'App Homeのテストです' } }, { 'type': 'actions', 'elements': [ { 'type': 'button', 'text': { 'type': 'plain_text', 'text': 'フォームを開く' }, 'style': 'primary', 'action_id': 'open_button_id' } ] } ] } ) except Exception as ex: logger.error(ex)
申請履歴
元々は各部署の勤怠チャンネルで各自が申請を投稿するという流れだったため、過去の投稿を検索しづらいという課題があったのですが、その課題を解決すべく今回の対応で申請履歴を実現することにしました。
Slackアプリではユーザと同じくDM(ダイレクトメッセージ)を利用できるのですが、今回は申請情報をアプリからユーザに対してDMとして飛ばすことで履歴を実現しました。
DMを利用するためにはApp Home → Show TabsにあるMessages TabをONにする必要があります。
また、今回はユーザからアプリに対してメッセージの送信をさせたくなかったため、Allow users to send Slash commands and messages from the messages tabのチェックはOFFにしています。
送信するコードのサンプルです。
BlocksはAppHomeと同様にBlock Kit Builderで生成できます。
response = client.conversations_open(users=body['user']['id']) channel_id = response['channel']['id'] client.chat_update( ts=ts, channel=channel_id, blocks=json.dumps(message_blocks) )
勤怠データの管理
改善に至った経緯にも記載していますが、元々はGASを実行して各勤怠チャンネルからデータを吸い上げてスプレッドシート上で管理していました。
しかし、スプレッドシートだと労務担当者が作業しづらいという課題もあって今回はKintoneにデータを蓄積するようにしました。
Kintoneではアプリやレコードに対してアクセス権を付与できるため、労務担当者にだけ閲覧や編集権限を付与して、その他の社員には公開しないみたいなこともできます。
このKintoneへのデータ蓄積もアプリ化して申請を一本化したことで得られたメリットと言えます。
勤怠データの活用
蓄積したデータを活用しなければ意味がない ということで当日の勤怠を投稿する機能も開発しました。
この機能はKintoneに蓄積したデータを利用して自分の所属する勤怠チャンネル内で誰が休むのかを投稿する機能で毎朝9:30(一部は9:00)に投稿されます。
グループごとに分けて表示していますので、自分のグループやチームのメンバーで誰が休みなのかを一目で把握できます。
この投稿によってマネージャーがその日の状況をひと目で把握できるようになったため例えば不調気味の人の対して早期フォローができたり、勤怠チャンネルを遡る手間が省けたなどの声をいただきました。
最後に
規模もそこそこ大きくて他部署と連携しながら進めたため多少時間はかかりましたが、リリースしてみなさんの反応を見ている限りだと割と好評でした。
ユーザからの声
- 申請履歴のおかげで過去の申請をすぐに確認できるようになった
- チーム内の勤怠を把握できるようになって便利
管理者(労務担当者)からの声
- 修正箇所が一つに集約されたことで新規勤怠チャンネルの追加時やフォーム内容の変更時において作業時間や作業ミスが減った
- 日付について入力ではなくカレンダーから選択できるようになり、ユーザからの申請ミスが減ったことでそれに付随するやりとりなどが減った
- 勤怠データが正規化されているため検索や集計が柔軟かつ容易に実施できるようになった
などの声があがっており、実現できて良かったと思いました。
とはいえこれで終わりではなくまだまだ勤怠周りの課題はありますのでさらなる改善を目指していきたいと思います。
そしてコーポレートシステム部全体としてもたくさんの課題があります。 それらを共に解決していく仲間を募集していますので興味がおありでしたらぜひ採用情報もご覧いただけますと幸いです。
*1:Lazy Listenersは現時点でPythonしか対応していないためご注意下さい。