Bolt for JSでワークフロービルダーのオリジナルステップを実装する

## はじめに
Bolt for JSでは、Slackのワークフロービルダーで使えるWorkflow Stepsを実装することができます。（以下、Custom Stepと呼びます。）

~~[リファレンス](https://slack.dev/bolt-js/concepts#steps)がありますが日本語化がまだのため、~~私が実装した経験から解説していきます。
日本語化されました！
https://slack.dev/bolt-js/ja-jp/concepts#steps

## 事前設定
前提としてSlackアプリを作成するのですが、アプリの作成は様々な記事があがっているのでそちらを参照してください。

Custom Stepを作成するために必要な設定は以下の2点です。

### ワークスペースでWorkflow stepsの利用を許可する

ワークスペースの設定 > 権限 > アプリからのワークフロービルダーステップ
「ワークフロービルダーで、このワークスペースにインストールされたアプリのステップを表示する」にチェック。

![設定1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193977/d4b24baf-8086-c55c-d580-d5f16315eaf9.png)

### アプリのWorkflow Steps
アプリのWorkflow Stepsの「Turn on workflow steps」をONの状態にします。
ON状態にすると、`workflow.steps:execute`のスコープが必ず追加されるため、アプリのReinstallが促されます。
Reinstallしておきましょう。

![設定2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193977/d4efdda2-b858-ad84-7def-89611bc1a179.png)

Stepsの「Add Step」から追加するStepを登録します。

![設定3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193977/28d4dd3b-50dd-2cd4-48a0-9cb40b6fcd5c.png)

- Step name
    - 追加するCustom Stepの名前
- Callback ID
    - あとで実装するCallback IDと一致させる

> Create up to 10 custom steps for your app to be used within Workflow builder.

Custom Stepは1つのアプリに対して10こまでしか実装できないみたいですね。

## 実装していく
実装の基本の形は

```app.js
const { App, WorkflowStep } = require('@slack/bolt');

const app = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  token: process.env.SLACK_BOT_TOKEN,
});

const wsEdit = ({ ack, step, configure }) => {};
const wsSave = ({ ack, view, update }) => {};
const wsExecute= ({ step, complete, fail, client }) => {};

const ws = new WorkflowStep('custom_step', {
  edit: wsEdit,
  save: wsSave,
  execute: wsExecute,
});
app.step(ws);
```

です。
`'custom_step'`の部分が事前設定にあるCallback IDと一致するようにします。


edit(`wsEdit`), save(`wsSave`), execute(`wsExecute`)のそれぞれの役割とサンプルコードを解説します。

### edit(wsEdit)
ワークフロービルダーで、Custom Stepを追加したり、編集したりするときに実行されます。
カスタムステップの設定をするモーダルを定義し、開きます。

<details><summary>サンプルコード</summary><div>

```app.js
const wsEdit = ({ ack, step, configure }) => {
  await ack();
  
  const blocks = [
    {
      type: 'input',
      block_id: 'title_input',
      element: {
        type: 'plain_text_input',
        action_id: 'title',
      },
      label: {
        type: 'plain_text',
        text: 'タイトル',
      }
    },
    {
      type: 'input',
      block_id: 'message_input',
      element: {
        type: 'plain_text_input',
        action_id: 'message',
        multiline: true,
      },
      label: {
        type: 'plain_text',
        text: 'メッセージ',
      },
    }
  ];

  const { inputs } = step;
  const title = inputs.title;
  const message = inputs.message;

  if (title !== void 0) {
    blocks[0].element.initial_value = title.value;
  }
  if (message !== void 0) {
    blocks[1].element.initial_value = message.value;
  }

  await configure({ blocks });
};
```

</div></details>

順に説明します。

```javascript
await ack();
```

応答です。とりあえず返しておきましょう。

```javascript
const blocks = [
  // 省略
];
```

モーダルに表示するBlockを定義します。
[Block Kit Builder](https://app.slack.com/block-kit-builder/)を使うと便利です。（Modal Previewで！）

```javascript
const { inputs } = step;
const title = inputs.title;
const message = inputs.message;

if (title !== void 0) {
  blocks[0].element.initial_value = title.value;
}
if (message !== void 0) {
  blocks[1].element.initial_value = message.value;
}
```

この部分は、編集モードで開いたときの初期値を定義しています。
これがないと、編集で開いたときに前回保存した内容が表示されません。

`inputs`はこの後にでてくるsave(`wfSave`)で定義しています。
（この部分はなんかもっとうまいやり方があるかもしれません。）

```javascript
await configure({ blocks });
```

最後に`configure`に`blocks`を渡すことで、モーダルが表示されます。

### save(wsSave)
Custom Stepで、表示されたモーダルの「保存する」をクリックした時に実行されます。
モーダルからの値を受け取って、利用します。

<details><summary>サンプルコード</summary><div>

```app.js
const wsSave = async ({ ack, view, update }) => {
  await ack();

  const { values } = view.state;
  const title = values.title_input.title;
  const message = values.message_input.message;
  const inputs = {
    title: { value: title.value },
    message: { value: message.value },
  };

  const outputs = [
    {
      type: 'text',
      name: 'title',
      label: 'タイトル',
    },
    {
      type: 'text',
      name: 'message',
      label: 'メッセージ',
    },
  ];

  await update({ inputs, outputs });
}
```

</div></details>

順に説明します。

```javascript
await ack();
```

応答です。とりあえず返しておきましょう。

```javascript
const { values } = view.state;
const title = values.title_input.title;
const message = values.message_input.message;
const inputs = {
  title: { value: title.value },
  message: { value: message.value },
};
```

`view.state.values`にはモーダルに入力した値がわたってきます。

それをもとに`inputs`を構成します。
それぞれ`value`に値を設定すればよいです。

この`inputs`はedit(`wsEdit`)で`step.inputs`にわたります。

`inputs`を詳しく知りたい方は[リファレンス](https://api.slack.com/reference/workflows/workflow_step#input)を参照ください。
※私には`variables`を使うシーンがよくわかりませんでした:frowning2:

```javascript
const outputs = [
  {
    type: 'text',
    name: 'title',
    label: 'タイトル',
  },
  {
    type: 'text',
    name: 'message',
    label: 'メッセージ',
  },
];
```

`outputs`には後続のワークフローステップにわたす変数を定義します。
（変数を挿入するをクリックしたときにでてきます。）
![変数を挿入する](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193977/635db7ec-38cd-3d97-07eb-a8ae5bca95d5.png)

`type`には`text`,`channel`,`user`のいずれかが入ります。

実際に渡す値はexecute(`wsExecute`)で設定します。

```javascript
await update({ inputs, outputs });
```

最後に`update`に`inputs`と`outputs`を渡すことで、モーダルが表示されます。

### execute(wsExecute)
ワークフローが実際にCustom Stepに到達した際に実行されます。

<details><summary>サンプルコード</summary><div>

```app.js
const wsExecute = async ({ step, complete, fail, client }) => {
  const { inputs } = step;

  const outputs = {
    title: inputs.title.value,
    message: inputs.message.value,
  };

  try {
    await client.chat.postMessage({
      channel: '###',
      blocks: [
        {
          type: 'header',
          text: {
            type: 'plain_text',
            text: inputs.title.value,
            emoji: true,
          }
        },
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: inputs.message.value,
          }
        },
      ]
    });
    await complete({ outputs });
  }
  catch (e) {
    fail({ error: { message: 'fail...' } });
  }
}
```

</div></details>

順に説明します。

```javascript
const { inputs } = step;

const outputs = {
  title: inputs.title.value,
  message: inputs.message.value,
};
```

save(`wsSave`)で定義したoutputsに対して、次のステップにわたす値を設定します。

```javascript
try {
  // 省略
  await complete({ outputs });
}
catch (e) {
  fail({ error: { message: 'fail...' } });
}
```
`try`で処理を実行します。
サンプルコードでは、Custom Stepで設定したタイトルとメッセージを固定のチャンネルにポストしています。

最後に

```javascript
await complete({ outputs });
```

でoutputsを次のステップにわたします。

エラーがでた場合は

```javascript
fail({ error: { message: 'fail...' } });
```

でエラーメッセージを返します。
おそらく、ワークフローのアクティビティにエラーメッセージが表示されるのだと思います。（未確認）

## さいごに

これでCustom Stepが実装できました！
ワークフロービルダーでちょっと手の届かないところだけサクッと作れるので、大変重宝しています。

アプリのショートカットはabc順で並ぶので、探すのが大変ですが、ワークフローは一番上に表示されるので、その点でもワークフロービルダーでサクッと作ってしまいたいですよね。

汎用的に使えそうなCustom Stepを複数作ってみて、Slack Lifeを充実させましょう！

**最後になりましたが、間違っているところなどありましたら、ばしばしご指摘くださいませ。**