Slack の次世代プラットフォームで抽選アプリを作ってみた
この記事はユアマイスター アドベントカレンダー 2022の 4 日目の記事です
こんにちは、ユアマイスターでエンジニアをしています itsuo です。
ユアマイスターにジョインしてはや 1 年となりました。
Slack の次世代プラットフォーム
現在(2022-12-04) Slack の次世代プラットフォームは β 版です。
仕様は変更される可能性があります。
弊社では コミュニケーションツールとして Slack を使用しています。
かなり活用されていて、色々とカスタマイズされています。
そんな中で ユーザの中で誰かを選びたいというニーズが出てきました。
(次の発表者を決めるとか幹事を選ぶとか、、)
アプリが無いかと探したところ Shufflet というのがあったのですが、ユーザグループしか指定できず、ゲストアカウントの方を入れることが Slack の仕様としてできないのでイマイチでした。
なので、先ごろオープン β として発表された Slack の次世代プラットフォームでサクッと作ってみたいと思います。
環境構築
前提条件
次世代プラットフォームで開発・公開をするには以下が必須となっています。
- Paid Plan でないと動かすことができない
- そのうちできるようになるといいですね
- Slack ワークスペースの管理者による Beta プログラムへの参加許可が必要
- こういうときにどれだけ管理者と良い関係が保たれているかが試されます
Slack CLI を導入 & login
https://api.slack.com/future/quickstart#install-cli
こちらは上記の内容をやればいいのでサラッと流します。
project を作成
slack create shuffle_app
として作成します。
template は blank starter project
を選択
デフォルトで .vscode フォルダができて deno の設定をしていてくれるのはありがたいですね。
処理の流れ
処理の流れとしては
- Link Trigger となっている URL を入力
- Workflow が起動 ステップを実行していく
- Step1, DataStore からデータを取得, データが有れば input のデフォルト値として使用する
- Step2, form からのデータを DataStore に保存
- Step3, form からデータを受け取り、抽選処理
- Step4, 結果を表示
となります。
今まで API で通信してやっていたことを slack run
でローカル環境で実行できるのはかなり楽です。
つまずき Points!
workflow
,trigger
,function
,datastore
などの細かな説明は他の有用な記事に任せ、
開発時につまずいたところを pick up していきます。
/
コマンドとして設定できない
FAQにあるように 現状ではできません。
あくまで link trigger で workflow を構築せよということのようです。
(現在 shortcut menu に出るように実装中のようですが、、)
構築した Link Trigger は 一度実行すると 上部の ワークフロー
の中に入る仕様です。(ピン留めアイテムの横)
form は workflow の一番最初に設定しないといけない
これが意外と面倒くさい
OpenForm
はフィールドの設定をするだけで、promise なオブジェクトを受け付けてくれません。例えば 各 input のデフォルト値を datastore から取得して設定ということが現状ではできなさそうです。
そのため、これを実現するためには 同一の Function の中でデータを取得し 昔ながらの Block Kit で form を構築しなければなりません。 TypeScript の恩恵も受けられず、リファレンスとエラーログとのにらめっこが続きました。
import { getUsers } from "../libs/getUser.ts"
export default SlackFunction({
xxxFunctionDefinition,
async({inputs, client }) =>{
const users = await getUsers() // ← ここでuserを取得
await client.vies.open({
interactivity_pointer: inputs.interactivity.interactivity_pointer,
view: {
type: "modal",
// ........
blocks:[
{
type: "input",
block_id: "section1",
element:{
type: "multi_users_select",
action_id: "selected_users",
initial_users: users, // ← ここに反映
}
}
// .......
]
}
})
}
})
Block Kit で取得したデータをどのように workflow に返すか?
Block Kit で設定した form は 非同期で呼び出されます。 await をつけていますが、チュートリアルにあるように return {completed: false}
と返してそのステップが終了するのを抑制しています。
では、どのようにして step を終了したら良いんでしょうか? modal
の部分を見ていたので かなり悩みました。
単純に 後続のaddBlockActionsHandler
で取得した値を返してしまうと エラーと判断され step が中断してしまいます。
で正解は addBlockActionsHandler
内で client.functions.completeSuccess
を呼び出すことでした。
(Block Kitの方にさらっと書いてあるだけなので発見するのに時間がかかりました。。)
export default SlackFunction(
xxxFunctionDefinition,
async() => {
await client.view.open({
// .......
})
return { completed: false }
}
).addViewSubmissionHandler({
"callback_id_handler",
async ({ view: { state: { values } }, client, body }) => {
await client.functions.completeSuccess({
function_execution_id: body.function_data.execution_id,
outputs: { some_data: values.some_section.some_field.value }
})
})
結構レスポンスが遅い
β 版なので改善されるのかもしれませんが、各ステップの実行速度はかなり遅いです。
止まっている?と思うほど。1つのステップに 約3〜4秒かかり、今回のステップは4つあるので 16 秒ほどかかります。
Deno は早いんじゃないの?? と思いますが、今後の改善で爆速になることでしょう。
感想
色々と文句的なことを言ってきましたが、今回の workflow は 色々と調べながらも 4 時間ほどで組み上がりました。 DX は非常によく、慣れてくれば本当にサクッとアプリが組めそうです。
良き点
- TypeScript のコード補完最高!(まだ完全に補完されるわけではないですが)
- 自前でサーバ用意しなくていい!
以前は何かしらのサーバを用意し、それと Slack API でやり取りしていました。これが無いだけでもこちらで開発する価値があるとおもいます。加えてローカルで開発できるのもかなり良い感じです。(以前は port forward などで 外部と local をつなげていた) - DataStore!
ちょっとデータを保存したいときにこれが用意されているのは素晴らしいですね。 - やりたいことはだいたい網羅されている
Trigger も各種あるし、そのトリガーも動的に設定できます。加えて Build-in Functions で Slack でのやりたいことはほぼできそうです。
form で時間を指定し、なにかデータを保存して 時間になったら保存したデータを元に実行みたいなことが 簡単に実現できます。
作ったものは こちらにおいてあるので、使ってもらえれば幸いです。