next generation Slack platformで障害対応のフローを統一&効率化した【実装編】
はじめに
こんにちは、さざなみです。この記事は前編のnew Slack platformで障害対応のフローを統一&効率化した【問題解決編】に続く後編の【実装編】です。
next generation Slack platformを使ったコードの事例を知りたい、何ができるのか知りたい方を対象にしています。next generation Slack platformは2022年の9月にリリースされたものであり、現在ベータ版です。そのため、今後のアップデート次第で本記事の内容とは動作が異なる可能性があります。
注意:以下のコードは2023年5月のものです。
公式ページにおいて、new Slack platformという表記からnext generation Slack platformに変わっていました。同じものを指します。
next generation Slack platform とは
クラウド上で動作する2022年9月にリリースされた新しいSlack Appです。
LambdaやGASなどを利用せずに実行することが可能で、用意されたCLIを使って開発環境実行、デプロイなどの操作をコマンドで簡単に実行することができます。
また、データストアも無料で利用可能で、Denoの簡単なコード実装で保存や更新などを行うことができます。
基本構造と振る舞い
Triger
ワークフローを開始する方法とタイミングを定義します。
今回作成した障害対応ボットの場合、報告開始のコマンドやショートカットボタンを指します。
Workflow
Triggerから呼び出され、FunctionをStepとして組み合わせてやりたいことの流れを実装します。
障害対応ボットの報告開始ワークフローの場合、フォーム表示ステップ、メッセージ送信ステップ、チャンネル作成ステップなどを呼び出しています。
Function
Workflowから呼び出されるStep(Function)の中身の実装が記述されている部分です。入力と出力を持ちます。
datastore
DynamoDBのようなデータストアです。CRUDの標準的なデータベース操作を行うことができます。
実装
障害の発見時にそれを報告するワークフローの作成を例に解説します。
完成物
環境構築
まず最初にSlackで用意されているCLIを使うためにインストールする必要があります。
curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash
Triggerの実装
TriggerはSlack上でのどのような操作を起点にしてワークフローを実行させるかを記述するものです。
今回はスラッシュコマンドやショートカットのクリックで利用できるボットを作成したいのでLink Triggerを作成しました。
slack trigger create --workflow "#/workflows/start_form" --interactivity
import { Trigger } from "deno-slack-api/types.ts";
import IncidentStartFormWorkflow from "../workflows/incident_start_form_workflow.ts";
const incidentStartFormTrigger: Trigger<typeof IncidentStartFormWorkflow.definition> = {
type: "shortcut",
name: "インシデント報告開始",
description: "インシデント報告を始める",
workflow: "#/workflows/incident_start_form_workflow",
inputs: {
interactivity: {
value: "{{data.interactivity}}",
},
channel: {
value: "{{data.channel_id}}",
},
userId: {
value: "{{data.user_id}}",
},
},
};
export default incidentStartFormTrigger;
Workflowの実装
今回実装するWorkflowの必要な機能は以下です。ワークフローでは以下のものを順番にコードで記述していきます。
- 障害発生を報告するフォームが現れる。
- タイトル
- メンション
- 何が起こっていますか
- 予想規模
- 新しい障害発生対応チャンネルを作成するか(yes, no)
- 入力後の作成ボタンを押すと、実行したチャンネルでbotから報告メッセージが送られる。
- 報告者にしか見えないメッセージで障害報告書とgithubのissueの新規作成をお願いする旨を送信する。
- datastoreに入力された内容を保存する。
- 「チャンネルを作成する」にyesと回答していた場合、障害専用の障害対応チャンネルを作成し、関係者を招待する。
0. 定義
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { IncidentStartFormFunctionDefinition } from "../functions/incident_start_form_function.ts";
import { IncidentAddDatastoreFunctionDefinition } from "../functions/incident_add_datastore_function.ts";
import { IncidentEndWorkflowFunctionDefinition } from "../functions/incident_end_workflow_function.ts";
import { SCALE_TEXTS } from "../constant/index.ts"
/**
* ワークフローの定義
*/
const IncidentStartFormWorkflow = DefineWorkflow({
callback_id: "incident_start_form_workflow",
title: "incident start form",
description: "troubleshooting workflow",
input_parameters: {
properties: {
interactivity: {
type: Schema.slack.types.interactivity,
},
channel: {
type: Schema.slack.types.channel_id,
},
userId: {
type: Schema.slack.types.user_id
}
},
required: ["interactivity"],
},
});
1. 障害発生を報告するフォームが現れる
フォームを表示させる関数はSlackで最初から用意されているためそれを使いました。
Slackで用意されている関数は以下のページにまとめられています。
- open_form フォームを開く
- send_message メッセージを送信
- add_user_to_usergroup ユーザーをグループに追加
- create_channel チャンネル作成 など...
/**
* 入力フォームを開く
*/
const inputForm = IncidentStartFormWorkflow.addStep( // 作成ボタンを押した時のデータがinputFormに格納される
Schema.slack.functions.OpenForm,
{
title: "障害対応 起票フォーム",
interactivity: IncidentStartFormWorkflow.inputs.interactivity,
submit_label: "作成",
fields: {
elements: [{
name: 'title',
title: 'タイトル',
type: Schema.types.string,
},{
name: "mention",
title: "メンション",
type: Schema.types.array,
items: {
type: Schema.slack.types.user_id,
},
},{
name: 'description',
title: '何が起こっていますか',
type: Schema.types.string,
long: true,
}, {
name: "scale",
title: "予想規模",
type: Schema.types.string,
choices: [
{ value: "S: 動作不全でユーザーに著しい不利益があるバグ", title: "S: 動作不全でユーザーに著しい不利益があるバグ" },
{ value: "A: 一部動作不全でユーザ不利益があるバグ", title: "A: 一部動作不全でユーザ不利益があるバグ" },
{ value: "B: 機能は動作するがユーザー不利益があるバグ", title: "B: 機能は動作するがユーザー不利益があるバグ" },
{ value: "C: 機能は動作していないがユーザー不利益がないバグ", title: "C: 機能は動作していないがユーザー不利益がないバグ" },
{ value: "D: バグとは言えないが気になるもの", title: "D: バグとは言えないが気になるもの" },
{ value: "わからない", title: "わからない" }
],
enum: ["s", "a", "b", "c", "d", "uncertain"],
default: "uncertain",
},{
name: "create_channel",
title: "新しい障害対応チャンネルを作成する",
type: Schema.types.boolean,
default: true,
},],
required: ['title','description', 'scale', 'create_channel'],
},
},
);
2. 入力後の作成ボタンを押すと、実行したチャンネルでbotから報告メッセージが送られる
1で入力された内容をメッセージとして送信する。
/**
* 入力されたフォームの内容からbotが送信するメッセージを作成する
*/
const incidentStartFormFunctionStep = IncidentStartFormWorkflow.addStep(
IncidentStartFormFunctionDefinition,
{
title: inputForm.outputs.fields.title,
mention: inputForm.outputs.fields.mention,
description: inputForm.outputs.fields.description,
scale: inputForm.outputs.fields.scale,
userId: IncidentStartFormWorkflow.inputs.userId,
},
);
/**
* botでメッセージを送信
*/
const sendIncidentStartMessageInIncidentChannel = IncidentStartFormWorkflow.addStep(Schema.slack.functions.SendMessage, {
channel_id: IncidentStartFormWorkflow.inputs.channel,
message : incidentStartFormFunctionStep.outputs.report,
});
3. 報告者にしか見えないメッセージで障害報告書とgithubのissueの新規作成をお願いする旨を送信する。
Schema.slack.functions.SendEphemeralMessage
を使うと以下のようなメッセージを送れる
/**
* 起票者のみに以下のことを促すephemeral messageを送信
* - kintone記載(必須)
* - issue作成(任意)
*/
IncidentStartFormWorkflow.addStep(Schema.slack.functions.SendEphemeralMessage, {
channel_id: IncidentStartFormWorkflow.inputs.channel,
user_id: IncidentStartFormWorkflow.inputs.interactivity.interactor.id,
message: `<@${ IncidentStartFormWorkflow.inputs.interactivity.interactor.id }>\n
以下二つを作成・記入していただき、上スレッドでURLの共有をお願いいたします。:ham: \n
- <https://xxxx.com/xxxx|kintone記載(必須)>\n
- <https://github.com/Lancers/xxxx/issues/new?assignees=&labels=BUG,CRE&template=basic-template.md&title=|issue作成(任意)>\n
`,
});
4. datastoreに入力された内容を保存する。
/**
* datastoreにデータ追加
*/
IncidentStartFormWorkflow.addStep(IncidentAddDatastoreFunctionDefinition, {
title: inputForm.outputs.fields.title,
report: incidentStartFormFunctionStep.outputs.report,
message_link: sendIncidentStartMessageInIncidentChannel.outputs.message_link,
});
5. 「チャンネルを作成する」にyesと回答していた場合、障害専用の障害対応チャンネルを作成し、関係者を招待する。
内容
-
incident_yyyymmdd_title
のチャンネルを作成 - botで障害の内容のメッセージ送信
- 報告者・mentionされた人・botをチャンネルに追加
workflow内で条件分岐の処理が使えなかったため、引数がtrueの場合はそれ以降の処理を終了させる関数を作成して条件分岐のような振る舞いを実装した。
/**
* チャンネルを作成しない場合、これ以降のステップを実行しないためのfunctionを実行する
*
* この実装になった経緯
* ワークフロー内でif文を使うことができない。
* そのため、function内で実行中のワークフローを終了させる関数を作成して条件分岐のようにしている
*
*/
IncidentStartFormWorkflow.addStep(IncidentEndWorkflowFunctionDefinition, {
end_workflow_flg: inputForm.outputs.fields.create_channel,
})
/**
* チャンネルを作る
*/
const createChannelStep = IncidentStartFormWorkflow.addStep(Schema.slack.functions.CreateChannel, {
channel_name : `incident_${String(new Date().getFullYear()) + String(('00' + (new Date().getMonth() + 1)).slice(-2)) + String(('00' + new Date().getDate()).slice(-2))}_${inputForm.outputs.fields.title}`,
is_private : false,
});
/**
* botでメッセージを送信
*/
const sendIncidentStartMessageInCreatedChannel = IncidentStartFormWorkflow.addStep(Schema.slack.functions.SendMessage, {
channel_id : createChannelStep.outputs.channel_id,
message : incidentStartFormFunctionStep.outputs.report,
});
/**
* チャンネルにユーザーを追加する
*/
IncidentStartFormWorkflow.addStep(Schema.slack.functions.InviteUserToChannel, {
channel_ids : [createChannelStep.outputs.channel_id],
user_ids : incidentStartFormFunctionStep.outputs.inviteUsers,
});
Functionの実装
カスタムFunctionの一部を紹介します。(export default部分のみ)
datastoreにデータを追加するFunction
export default SlackFunction(IncidentAddDatastoreFunctionDefinition, async ({ inputs, client }) => {
let completed = true;
const uuid = crypto.randomUUID();
const started = new Date()
.toLocaleDateString("ja-JP", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
.replaceAll(/\/|:| /g, '')
const response = await client.apps.datastore.put({
datastore: "incidents",
item: {
id : uuid,
title : inputs.title,
report : inputs.report,
message_link : inputs.message_link,
started : started,
closed : null
},
});
if (!response.ok) {
console.log(`エラーになりました: ${response.error}`);
console.log(response.errors);
completed = false;
} else {
console.log(`データ追加しました: ${response.item}`);
}
return { outputs: { completed } };
},
boolの引数によってワークフローを終了させるFunction
export default SlackFunction(IncidentEndWorkflowFunctionDefinition, ({ inputs }) => {
// end_workflow_flgがfalseの場合以降のワークフローをスキップする(停止させる)
if (!inputs.end_workflow_flg) {
return { completed: false };
}
return { outputs: { }};
});
datastoreを使えるようにする
datastoreのスキーマの定義を実装します。
import { DefineDatastore, Schema } from "deno-slack-sdk/mod.ts";
export const IncidentsDatastore = DefineDatastore({
name : "incidents",
primary_key : "id",
attributes : {
id : { type: Schema.types.string },
title : { type: Schema.types.string },
report : { type: Schema.types.string },
message_link : { type: Schema.types.string },
started : { type: Schema.types.string },
closed : { type: Schema.types.string },
},
});
Manifest
manifestにはこのbotがSlack上で動作するために必要な情報と設定を記載します。
botのアイコン、使用するworkflow, datastore、botに付与する権限など
export default Manifest({
name: "incident_report_app",
description:
"create a incident report",
icon: "assets/icon.png",
workflows: [IncidentStartFormWorkflow, IncidentCloseWorkflow, IncidentCloseFormWorkflow],
outgoingDomains: ["cdn.skypack.dev"],
datastores: [IncidentsDatastore],
botScopes: ["commands", "chat:write", "chat:write.public", "datastore:read", "datastore:write", "channels:manage", "groups:write",],
});
デプロイ
slack deployコマンドを実行します。割愛しますが、初回のデプロイ時やトリガーとそれに紐づく新しいワークフローを実装したときは、デプロイ後にトリガーの追加もする必要があります。
slack deploy
Tips
datastoreに格納されたデータを確認したい
incident-bot % slack datastore query '{"datastore": "datastore_name"}'
複数人で開発、デプロイしたい
共同開発者として追加する必要があります。
slack collaborator add [開発に参加してもらう方のメールアドレス]
その他のコマンド
incident-bot % slack collaborator -h
Manage app collaborators
USAGE
$ slack collaborator <subcommand> [flags]
ALIASES
collaborator, collaborators, owners
SUBCOMMANDS
add Add a collaborator to your app
list List all collaborators of an app
remove Remove a collaborator from an app
おわりに
問題解決編記事の投稿から1ヶ月ほど実装編の記事を書くのが遅れてしまい、その間にnext generation Slack platformもめちゃくちゃ進化していてとても焦っていますっ。最近ものすごい速度でアップデートされているslack APIなので、実装する際には公式のドキュメントをまず参照するのが良いと思います。これを実装し始めた9ヶ月前は公式にもほとんど情報がなく手探りで実装していましたが、今ではコードの例も含めてかなり丁寧に書かれている印象でしたので、とても参考になるのではと思います。
最新の情報は以下で見ることができます。
Discussion