SlackからAirtableへレコード作成・更新する

# やりたかったこと
- Slack上で作業申請→承認→開始→終了連絡を気軽に実施しつつ、Airtableにて各作業のステータスとともに一覧管理したい
- 申請内容はSlackワークフローのフォームで入力
- 承認・開始・終了連絡はリアクション付けるだけ
- ノーコードで手軽に実施したい

# 1. 使ったツール
- Slack
- Airtable
- Zapier

# 2. 全体デザイン
![作業管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/b267b07a-eb53-0b59-d952-779cde774600.png)

# 3. 構築手順
## 1. Slackワークフローの設定<a id="anchor3-1"></a>
利用者には **#リリース管理** チャンネルだけを意識してもらいたいので、**#リリース管理**チャンネルの関連ページとしてワークフローのリンクを置いておくことにしました。
![スクリーンショット 2023-07-15 17.37.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/351e8be1-e012-774c-4e09-2aefd744c593.png)
フォームに入力した内容は敢えて別のチャンネル **#zapier-input** に出力しています。
![スクリーンショット 2023-07-15 17.38.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/e8dcbfa6-5dd6-2b23-7756-c3da603d5900.png)
この意図としては、以下のとおりです。
- ユーザーにステータス変更のリアクションを付けてもらう投稿にAirtable上のレコード識別子を入れておきたかった。（なのでレコード挿入後に払い出されたレコード識別子を含めた投稿の方だけをユーザに見せたい）
- Slackフォームにて日時タイプで入力された項目をメッセージとして出力した際に、UTCで表示されてしまうためそのままでは見づらい
- Zapierに各項目の値をパースさせるにあたり、目視用にラベルなどをつけているとSplitが必要となるし、separaterのエスケープとか考えるのが面倒

このワークフローを実行して、**#zapier-input** に出力されたらこんな感じになります。
![スクリーンショット 2023-07-16 17.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/e7a73c64-97d6-4d9e-b8c6-2e82adfdc6a2.png)
- 日時はUNIXタイムスタンプ形式にしています。ここの出力結果は目視目的でなくZapierへの受け渡し目的なので。
- 承認者や申請者などのユーザー情報は、ユーザID、表示名、氏名の３パターンで出力してあります。ユーザIDは後でメンションの際に使うため、氏名はAirtableへの記録用です。表示名は今のところ使ってませんが念の為。

## 2. Airtableの設定
受け手のテーブルを作っておきます。
![スクリーンショット 2023-07-15 18.06.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/fef09f18-90c9-a248-9367-2ef6b8dab6db.png)
フォームの入力項目に加えて、
- Autonumberタイプのカラムをレコードの識別子として追加
    - Airtableのシステム内での識別子もあり、Zapierからの登録時にレスポンスとしてわかるので、更新対象を明確にするだけであれば不要ですが、ユーザの目に触れさせるものでも無さそうだし、共通言語としてあった方がユーザ同士でも便利だろうと入れることにしました
- ステータス
    - とりあえず、Open/Approved/in progress/Done だけ設定
- 各種日時
    - 作成日時（Airtable機能としてCreated timeを利用）
    - 承認日時
    - 実際の作業開始・終了日時
    - 最終更新日時（Airtable機能としてLast modified timeを利用）
    - 最終更新者

## 3. Zapierの設定
全部で４つのZapを作ります。
![スクリーンショット 2023-07-15 18.20.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/28b9441c-6d32-12b7-318e-41a3460eb385.png)
それぞれ以下に対応しています。
a. 作業申請（レコード登録）
b. 作業承認
c. 作業開始
d. 作業終了
*****
### a. 作業申請（レコード登録）用のZap
**+ Create Zap**から新規Zapの作成を開始し、Triggerの**App**としてSlack、**Event**として「New Message Postedto Channel」を選択します。
![スクリーンショット 2023-07-16 18.13.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/af6e52ee-2b3e-4d58-c211-872e910be783.png)
**Account**では適切なSlackアカウントでログインして権限を許諾します。
**Trigger**では、**Channnel**には先ほどのワークフローが申請内容を出力するチャンネルを選択して、**Trigger for Bot Messages?** は「Yes」にしておきます。
![スクリーンショット 2023-07-16 18.20.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/2780e1a6-ae60-3aa6-12be-6d6d92533f0a.png)
**Test**を実行して、対象としたいメッセージが取得できることを確認して、今後のアクションの設定で参照したいテストメッセージを選んでおきます。
![スクリーンショット 2023-07-16 18.23.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/63497973-6187-4359-f753-9b435ffd7755.png)
Zapierではこのように実際のサンプルデータを確認しながら、後続のアクションを設定できるのが本当にわかりやすくて好きです。
２番目のアクションでは、**Filter by Zapier**を利用して、投稿者がワークフローの場合のみ後続のアクションを続けるように設定します。
![スクリーンショット 2023-07-16 18.26.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/2230cbe5-ba2e-87c0-2198-3707358b488c.png)
今回は**User Name** が一致する場合としました。ここでUser Nameの選択を行う際に、下の画像のように、**Insert Data ...** が現れて、どのアクションの結果の値を使うか、その中のどの項目を使うかが、テスト結果のサンプルデータとともに表示されるのでわかりやすいです。
![スクリーンショット 2023-07-16 18.31.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/52ebb33a-5843-3a02-16e5-b17187f2cea8.png)
次は**Formatter by Zapier**を利用して、メッセージに含まれる各データ項目を抽出します。
**Event**は「Text」を選択します。
![スクリーンショット 2023-07-16 18.36.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/050056c9-660a-4f8b-a74e-e83d494a4169.png)
**Transform**は「Split Text」を選び、inputデータは１番目のアクション結果から「Raw Text」を選びます。ここでも選択項目にテストデータが添えられているのでわかりやすいと思います。
**Separator**は「[:newline:]」、**Segment Index**は「All (as Separate Fields)」を選択しておきます。
![スクリーンショット 2023-07-16 18.38.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/580a43a6-62d6-1010-5d5d-74c31a442eb5.png)
Testを実行して、Raw Textから行ごとにデータ項目が別itemとして切り出されたことを確認します。
４つ目のアクションも**Formatter**です。今度は**Event**にはDate/Timeを選びます。
![スクリーンショット 2023-07-16 18.49.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/34c19a6f-ef94-1625-7f72-0577936da44d.png)
**Transform**では、３つ目のアクションのテスト結果から作業開始日時の項目を選び、それを日本のタイムゾーンで可読の形式に変更するように設定します。
![スクリーンショット 2023-07-16 18.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/411955e9-d460-8bf6-5205-7cf778dbdc16.png)
5つ目のアクションは作業終了日時に関してほぼ同じ内容で設定します。（詳細は割愛します）
６つ目のアクションとしていよいよAirtableへレコードを書き込みます。

**App**としてAirtableを選択、**Event**は「Create Record」です。
![スクリーンショット 2023-07-16 18.55.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/13deba5d-7507-6aca-f182-2ef9fed143bc.png)

**Account**で、受け手のテーブルを作成したAirtableアカウントを連携すると、**Action**セクションにて**Base**や**Table**のプルダウン選択肢にAirtable側の設定が表示されるようになります。
**Table**を選択すると、以降のフィールドはTableのカラム構造に合わせたものになります。
Airtable側で自動で値が設定されるAutonumber型のカラムや、Created timeやLast modifiedなどは空欄にしておき、Statusフィールドは文字列で「Open」、その他の項目は３番目のアクションで行単位にSplitした項目から適切なものを選択して、Airtableへ値が設定されるようにします。
![スクリーンショット 2023-07-16 18.58.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/de55ffa2-7fec-ce89-a1c5-dd869345aaf7.png)
このアクションの**Test**を実行すると、Airtable側へレコードが作成されますので、Airtable側で結果を確認します。
Test結果として、Zapier側でもAirtableの設定結果が取得されます。
![スクリーンショット 2023-07-17 9.21.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/c8bc296a-3bfa-5a08-6eba-fad7efd71bc8.png)

最後に登録結果をSlackにメッセージとして投稿するのですが、コンセプトとしてはSlackから入力した値をそのまま投稿するのではなく、できるだけAirtableからの返却値を利用したいと考えましたので、UTCで返却された日時関連の項目をJSTへ変換するアクションを先に追加します。
ここでも利用するのは、**Formatter**です。前に行ったのと同じ要領ですので説明は割愛します。
![スクリーンショット 2023-07-17 9.27.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/a6fd53f3-b0d2-e50c-1657-8e4c1df56690.png)

最後のアクションはSlackにたいして**Send Channel Message**を設定します。
![スクリーンショット 2023-07-17 9.29.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/300bae51-b3d9-af0d-aef1-d49d30d1130f.png)
投稿先のチャンネルは、ユーザーに見てもらう **#リリース管理** チャンネル、Messageテキストとしては、Airtableからの返却値にテキストでラベルを付けて設定します。日時項目は先ほど追加したJST変換済みの項目です。Bot Nameは適当に設定しておきましょう。
![スクリーンショット 2023-07-17 9.32.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/77a1d8e4-0ff1-b7d2-400f-b27f1bc71b2d.png)
ここで、承認者や投稿者などの項目はAirtableへ連携した氏名に加えて、メンションするために、```<@ユーザID>```を付け加えておきます。ユーザIDはAirtableへは渡してないので（渡すように設計しても良いのですが）、Slackからの入力値を行スプリットしたアクション結果から持ってきています。
このアクションの**Test**を実行すると、Slackへ実際にメッセージが投稿されます。
![スクリーンショット 2023-07-17 9.42.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/7edfec27-09f8-7fca-aa6d-cc9ee228b73b.png)

これで作業申請（レコード登録）用のZapは完成です。
*****
### b.作業承認用のZapの作成
別のZapとして作業承認用のZapを作成します。
今度の**Event**は**New Reaction Added**です。
![スクリーンショット 2023-07-17 9.46.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/6df65e76-6ac9-8e92-b643-fac030179874.png)
先ほどの新規登録用のZapが投稿したSlackメッセージに対して、特定のリアクションを付けられたことをトリガーにして動作するように設定します。今回適切な承認者かどうかのチェックまではスコープにしませんので、**User**は誰でも良いように空にしておきます。
![スクリーンショット 2023-07-17 9.48.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/0a9dd774-31a7-0059-d6e8-3d27188409f2.png)
このアクションの**Test**を実行して、これ以降の設定で参照するサンプルデータを選択します。テスト実行前に該当のSlackメッセージへリアクションを付けておきます。
![スクリーンショット 2023-07-17 9.52.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/d9f5bc39-b20e-7041-d327-c1bdcbafea99.png)
この**New Reaction Added**のtest recordsの取得は**かなり曲者**だと思いました。
- **Triger**で<font color="Red">指定したチャンネル以外の</font>データも候補として表示される
- **Triger**で<font color="Red">指定したリアクション以外の</font>データも候補として表示される
- 連携した<font color="Red">Slackアカウントが一度もどのリアクションをどこにも付けたことが無なければデータ取得エラー</font>となる
1,2点目は正直これではテストになってないじゃん！って思いますが、後続アクションの設定用のサンプルデータを拾う意味しか無いと割り切って目を瞑りましょう。
３点目について、私は今回のZapier連携用に専用のSlackアカウントを作成したためハマりました。
正直これでは心配なので、念の為、意図したチャンネルかどうかをチェックするフィルターを念の為入れました。
![スクリーンショット 2023-07-17 10.06.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/1e75a3f0-f5ee-f41f-9098-881ad138943b.png)
リアクションを付けたメッセージの投稿者が１つ目のZapであることもチェックしておきます。
![スクリーンショット 2023-07-17 10.08.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/b802d541-4688-2184-aa68-75ace5e40013.png)
リアクションを付けた元のメッセージの内容が**Message Text**として取得できているので、行単位に分解しておきます。１つ目のZapで行ったのと同じ要領なので詳細説明は省略します。
![スクリーンショット 2023-07-17 10.11.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/8e74eca9-c2d3-7c1c-5d14-2e183a5d3240.png)
これで取得したメッセージ内容の中からレコードの識別子としてAutonumber型で設定したフィールドの値を抽出しておきます。
![スクリーンショット 2023-07-17 10.36.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/80fd0844-49f1-0dd2-070f-92db538d48b8.png)
次にこの取得したIDを利用して、更新対象のレコードを取得しておきます。
**Event**は**Find Record**です。
![スクリーンショット 2023-07-17 10.34.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/1f46521c-d412-ede1-994f-1460e26ad05a.png)
レコード作成時と同じように**Account**と**Base**、**Table**を選択形式で設定すると、**Search by Field**も実際のテーブルのカラムから選択できるようになるので、Autonumber型で設定したID用のカラムを選択し、**Search Value**へ一つ前のアクションで取得したIDを使用するように設定します。
![スクリーンショット 2023-07-17 10.32.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/28a4fc7e-498e-36ee-c59f-e1e3cfacdbea.png)
**Test**を実行すると、Airtableの該当レコードの内容が取得されます。

そしていよいよレコードの更新です。**Event**には**Update Record**を使用します。
![スクリーンショット 2023-07-17 10.42.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/e326b4d2-939c-9a79-159c-08c82e06270f.png)
これまでと同じように**Base**と**Table**を選択肢し、**Record**として、**Find Records**の結果からID（Airtableのシステム設定のレコードID）を設定します。
他は更新すべき項目だけ値を入れていきます。
![スクリーンショット 2023-07-17 10.43.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/2eb6148b-2c97-0a05-3d91-73a2a999f745.png)
- Statusを「Approved」に文字列で設定
- 承認者や更新者は、1番目のアクションの結果からSlackの**User Real Name**を持ってきました
- 承認日時は、```{{zap_meta_utc_iso}}```を設定すると、現在日時で設定してくれます
![スクリーンショット 2023-07-17 10.48.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/19bca337-980f-86c2-bb41-a95fb7c4d9d6.png)
**Test**を実行するとAirtable側での更新が確認できるとともに、Zapier側でも更新結果が返却値として確認できます。

最後に更新結果をSlackへ投稿する前に、**Formatter**にて承認日時をJSTに変換しておきます。
![スクリーンショット 2023-07-17 10.58.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/3ab8655b-6a1d-25d8-1083-e4b0ee60e52b.png)
Slackへの結果送信は、また**Send Channnel Message in Slack**を使用します。
![スクリーンショット 2023-07-17 11.02.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/d6c8a77b-22a0-e121-f26a-236a8139f1cf.png)
<font color="red">Bot Nameはレコード作成時と異なるものにしておいた方が良い</font>でしょう。（その投稿にリアクションを付けられてしまったときにこのZap内で作成したFilterアクションで区別できるように）
また、元の投稿のスレッド返信として投稿するために、**Thread**フィールドに元のメッセージのタイムスタンプが設定されるようにします。
![スクリーンショット 2023-07-17 11.07.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/3d3df3ef-f2b8-c18c-b132-1fa70b6bfbea.png)
このアクションを**Test**すると、Slackにスレッド返信が投稿されます。
![スクリーンショット 2023-07-17 11.09.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489239/897fa515-1db3-01b3-7ee8-c9288a853c73.png)
これにて２つ目のZapも作成完了です。
*****
### c,d. 作業開始・終了記録用のZap
基本的には作業承認と同じなので、説明は省略します。
全部作成が終わったら、各ZapのRunnning ステータスをONにして動かします。

これにて完成です。お疲れ様でした。
*****
# 4. 終わりに
- nocodeでやりきれましたが、使う頭は結局コーディングしたときと似てるなと感じました。コード構文そのものの知識は不要でも、シンプルな命令を組み合わせてどうやってやりたいことを実現するのかを考えたり、あらかじめここでこういう変数を持たせておくと後で処理に使えるといった設計など。
- 今回、異常系・準正常系をほとんど考慮してないので、その辺りで何かテクニカルな工夫点があればまた共有します。
- Airtableに記録した情報をAirtableにログインせずに気軽に参照できるようにしたいので、UXから検討したいと思います。