SmartHR Tech Blog

SmartHR 開発者ブログ

Slack Deno SDKで楽々ワークフロー開発 —— 設定もコードで! 実行はSlack Platformで!

こんにちは!SmartHRでコーポレートエンジニアをしている加治です。

昨年、Slackから「Slack Deno SDK」が発表されました。DenoとはNode.jsの作者によって新たに作成されたJavaScriptのランタイムです。Slack Deno SDKの登場により、Denoを用いたSlackアプリ開発がこれまで以上に容易になりました。今回はSlack Deno SDKを用いてシンプルなカスタムワークフローステップを作ってみたので共有したいと思います。

想定読者

Slackアプリを取り巻く環境の歴史は結構長く、様々な変遷を辿っています。すべてを解説することはできないので、下記の読者を想定して記述しています。

  • Slackアプリを書いたことがある
  • Slackワークフロービルダーを使ってワークフローを作ったことがある
  • Slack APIを利用したことがある

Slack Deno SDKを選ぶ理由

次にSlack Deno SDKを選んだ理由について解説します。

Slack Deno SDKは比較的最近リリースされたライブラリです。まだ馴染みのない方もいると思うので、Slack Deno SDKを利用することのメリットを交えつつ選ぶ理由について解説します。

Slack CLI が便利

Slack Deno SDKを用いてアプリを開発する場合、Slack CLI を用いてアプリの作成、実行、デプロイをすべて行うことができます。

https://tools.slack.dev/Slack CLI/tools.slack.dev

Slack Platformを利用できる

Slack Platformとは、Slackが提供しているSlackアプリを動かすためのインフラです。実態はAWS上に構築されており、Denoの実行環境が用意されているとのことです。従来の環境では、Slackアプリを動かす環境を自前で用意する必要がありました。Slack Deno SDKを使えば、Slackが用意してくれた専用の環境でアプリを実行できます。

api.slack.com

こちらのインフラは今のところ、Slackの有料プランに課金していれば、追加の料金不要で利用できます。

Slackアプリのスコープがコード上に記述できる

今までのSlackアプリ開発では、アプリのスコープはWebブラウザから設定する必要がありました。しかし、Slack Deno SDKでは manifest.ts に必要なスコープを記述することでアプリのスコープに反映することができます。

アプリ作成等の手間がSlack CLIによって軽減され、インフラ構築の手間がなくなり、スコープ管理をコード上で行えるメリットは、Slack Deno SDKを選択する大きな理由になると言えるでしょう。

カスタムワークフローステップを作る

SmartHRのコーポレートエンジニアはSlackアプリをRuby on Railsで実装しています。その際、Slackとのやり取りにはサードパーティのSlack API Clientを活用するのが近道です。しかし、ワークフローステップの開発はBoltなどSlack製のライブラリを用いて作成するのが主流です。 Rubyでも実装できなくはないですが、ライブラリの拡張等開発コストが高くなることが見込まれたため、開発に着手していませんでした。

一方で、Slack Deno SDKではカスタムワークフローステップの実装がサポートされています。先述のようなメリットも踏まえると、これを利用しない手はありません。

カスタムワークフローステップの設計

カスタムワークフローステップは、入力と出力を持つことができます。それぞれ、他のワークフローステップとの間でデータをやりとりするのに使用できます。今回は、入力としてグループメンションのIDを受け取り、出力としてグループメンションに所属しているユーザーうち1名のユーザーIDを出力するワークフローステップを作成します。

Slackアプリを作成する

Slack CLI を利用して、Slackアプリを作成しましょう。

$ slack create sample-app
? Select an app: Automation app Custom steps and workflows
? Select a language: Deno Slack SDK Deno

📂 Created a new Slack project
   Cloning template slack-samples/deno-starter-template
   To path ~/src/sample-app

📦 Installed project dependencies
   Added sample-app/.slack
   Added sample-app/.slack/.gitignore
   Added sample-app/.slack/config.json
   Found sample-app/slack.json
   Updated config.json manifest source to local
   Cached dependencies with deno cache import_map.json

🧭 Explore the documentation to learn more
   Read the README.md or peruse the docs over at api.slack.com/automation
   Find available commands and usage info with slack help

📋 Follow the steps below to begin development
   Change into your project directory with cd sample-app/
   Develop locally and see changes in real-time with slack run
   When you're ready to deploy for production with slack deploy

manifest.tsで必要なスコープを定義する

先述の 「Slack CLI が便利 」でも記述した通り、Slack Deno SDK製のSlackアプリでは、スコープをmanifest.ts に定義、管理します。Slackアプリのスコープ設定もgit管理が可能になるので、変更を追跡しやすくなります。 今回は下記のような形に編集しました。

export default Manifest({
  name: "mention-steps",
  description: "メンションにまつわるCustomWorkflowStep",
  icon: "assets/icon.jpg",
  functions: [SampleMemberFunction],
  outgoingDomains: [],
  botScopes: ["commands", "chat:write", "chat:write.public", "usergroups:read"],
});

functionsを記述する

カスタムワークフローステップは、先ほど作成したSlackアプリの functions ディレクトリ内に sampleMember.ts を作成し記述します。ここで記述されている input parametersoutput parameters がそれぞれワークフローステップの入力と出力に対応します。

import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const SampleMemberFunction = DefineFunction({
  callback_id: "sample_member_function",
  source_file: "functions/sampleMember.ts",
  title: "グループメンションのメンバーからランダムに1人選ぶ",
  description: "グループメンションのメンバーからランダムに1人選びます。グループメンションのIDを設定すると、ユーザのIDを返します。",
  input_parameters: {
    properties: {
      group_mention_id: {
        type: Schema.types.string,
      },
    },
    required: ["group_mention_id"],
  },

  output_parameters: {
    properties: {
      sample_member: {
        type: Schema.slack.types.user_id
      },
    },
    required: ["sample_member"],
  },
});

export default SlackFunction(
  SampleMemberFunction, async ({ inputs, client }) => { const groupMentionId = inputs.group_mention_id;

    const usergroupsUsersResponse = await client.usergroups.users.list({
      usergroup: groupMentionId,
    });

    if (!usergroupsUsersResponse.ok) {
      throw new Error(
        `Failed to fetch users for usergroup ${groupMentionId}`,
      );
    }

    const sampleMember = sampleMemberFrom(usergroupsUsersResponse.users);

    return {
      outputs: {
        sample_member: sampleMember,
      },
    };
  },
);

function sampleMemberFrom(members: Array<string>) {
  if (members.length === 0) {
    throw new Error("No members available to sample from.");
  }
  return members[Math.floor(Math.random() * members.length)];
}

Slack Platformで実行する

まずは開発環境でSlackアプリを実行してみましょう。

$ slack dev
? Select an app Create a new app
? Choose a team sandbox TXXXXXXXX

🔔 If you leave this team, you can no longer manage the installed apps
   Installed apps will belong to the team if you leave the workspace


⚡ Listing triggers installed to the app...
   There are no triggers installed for the app

⚡ Create a trigger
   Searching for trigger definition files under 'triggers/*'...
   Found 1 trigger definition file

? Choose a trigger definition file: Do not create a trigger
✨ jun.kaji of Sandbox
Connected, awaiting events

これで実行中になりました。次は実際にこのカスタムワークフローステップを使ってみます。

Slack Workflow Builderから設定する

では早速、実装したStepをワークフローに設定してみましょう。Slack Workflow Builderを実行し、適当な起動トリガーを設定して、作成したステップを選択します。

Slack Workflow Builderで作成したステップを選択

次に、先程の sampleMember.ts 内で設定したinput parameterを入力します。これは他のSlack Workflow Builderで利用可能なSlack ワークフローステップと同様に、前段のワークフローステップから受け取る事もできます。

作成したカスタムワークフローステップの設定

今回は、前段のワークフローステップから受け取るのではなく、手動でグループメンションのIDをコピーしてきて入力しましょう。Slackの会話内でグループメンションをクリックして、コピーするのが手軽ですね。

グループメンションのIDを取得

「保存する」を押すと次のステップを設定できます。今回は次のステップに「メッセージを送信する」を選択して、こちらの項目を選びます。前段以前で実行されるワークフローステップの出力が指定可能です。

カスタムワークフローステップの実行結果をメッセージに含める

下記のように設定してみます。

メッセージのサンプル

あとは適当な名前をつけて、ワークフローを公開してみましょう。

ワークフローを実行してみる

今回はURLからワークフローを起動できるように設定しました。そのURLを適当なチャンネルに投稿するとこのようにワークフローの実行ボタンが表示されます。

作成したワークフロー

「ワークフローを開始」をクリックすると、下記のようにランダムに選ばれた人にメンションされますね。

ワークフロー実行結果

Slack Platformにデプロイする

さて、想定通り動作しているので、今度はワークフローステップをデプロイしてみましょう。

$ slack deploy
? Select an app Create a new app
? Choose a team sandbox TXXXXXXXXXX

🔔 If you leave this team, you can no longer manage the installed apps
   Installed apps will belong to the team if you leave the workspace

📚 App Manifest
   Creating app manifest for "sample-steps" in "Sandbox"

🏠 App Install
   Installing "sample-steps" app to "Sandbox"
   Error updating app icon: open assets/icon.jpg: no such file or directory assets/icon.jpg
   Finished in 2.2s

⚡ Listing triggers installed to the app...
   There are no triggers installed for the app

⚡ Create a trigger
   Searching for trigger definition files under 'triggers/*'...
   Found 1 trigger definition file

? Choose a trigger definition file: Do not create a trigger

🎁 App packaged and ready to deploy
   0.022MB was packaged in 0.8s

🚀 sample-steps deployed in 3.3s
   Dashboard:  https://slack.com/apps/A08PQQTC9FC
   App Owner:  jun.kaji (UXXXXXXXXXX)
   Workspace:  Sandbox (TXXXXXXXXXX)

🌩  Visit Slack to try out your live app!
   When you make any changes, update your app by re-running slack deploy
   Review the current activity logs using slack activity --tail

💌 We would love to know how things are going
   Share your development experience with slack feedback

これで、今度は下記のステップを選択すれば同じように利用できます。

デプロイしたカスタムワークフローステップ

過去にSlackアプリを作成、保守された経験のある方なら共感してもらえると思いますが、開発用のSlackアプリを別で作成して開発、本番用に設定を反映して…という手順が不要になっていてとても楽になりました。

作成したカスタムワークフローステップを誰でも利用できるようにする

ここで作成したカスタムワークフローステップは、現時点ではこのアプリのオーナーとコラボレータにしか利用できません。コラボレータも slack コマンドで追加可能ですが、ワークスペース全体で自由に使ってもらいたい場合、全員をコラボレータに追加するのは流石に避けたいところです。そこで、下記のコマンドでワークスペースに参加しているメンバーが使えるようにアクセス権を変更します。

$ slack app
🏘️  Apps
   smarthrsandbox:
      App  ID: AXXXXXXXXXX
      Team ID: TXXXXXXXXXX
      Status:  Installed
   smarthrsandbox (local):
      App  ID: AXXXXXXXXXX
      Team ID: TXXXXXXXXXX
      User ID: UXXXXXXXXXX
      Status:  Installed

$ slack function access --name sample_member_function
? Select an app AXXXXXXX sandbox TXXXXXXXXXX
? Who would you like to have access to your function? everyone

👥 Function 'sample_member_function' can be added to workflows by the following users:
   everyone in the workspace

先ほど作成したアプリのプロジェクトディレクトリで、 slack app コマンドを実行してApp IDを確認します。その後、 slack function access --name CALLBACK_ID を実行します。CALLBACK_ID は先程の sampleMember.ts 上で定義したもので、1アプリの中でユニークなIDを付与します。複数のアプリ間で重複するケースもあるので、App IDを控えておき、そちらのアプリを選択してデプロイしましょう。

設計・実装時に考慮したこと

ワークフローステップには様々な機能を実装できます。とはいえ、1つのワークフローステップに多くの仕事をさせるのは避けたほうが良いでしょう。

ワークフロービルダーで設定できるステップや、サードパーティからリリースされているカスタムコネクターの仕様から分かる通り、カスタムワークフローステップでは「小さな単一の機能を担う」ものを実装するのが良さそうです。そのような実装方針をとることで、他のステップと連携しやすく、再利用性の高いものが実装できると考えます。

いわゆるUNIX哲学に基づいて実装するのが良さそうと考えています。

ja.wikipedia.org

今回作成したステップくらいシンプルな仕組みなら、もしかしたら将来的にSlack公式のワークフローステップでリリースされるかもしれません。しかし、個々のユースケースに特化したワークフローステップの作り方を知っておくことは、一般向けの機能が埋めきれないラストワンマイルを埋める手段として有用であろうと考えています。

We Are Hiring!

SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です!

少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう!

hello-world.smarthr.co.jp