Slackのワークフローを使ってNotionのDBに情報を貯める！

こんにちは。
今回は、SlackのワークフローとSlack Appを使ってNotionにあるDBに情報を貯める方法についてまとめていきます。

もしNotionのメンバーアカウントを持っている人だけで完結する場合には、Slack内のNotion Appを使うだけでも実現できるのですが、
人数が増えるほど難しくなる状況もあるかと思います。

今回はメンバーアカウントが一人でも作れる方法を紹介できればと思います。

## 構成
構成は以下のようになります。

![notiondb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627854/1b29bb10-47ca-41e7-8b2a-bb9b56347b73.png)

非常にシンプルな構成です。
Cloud Run Service上でboltというSlack App作成のためのフレームワークを使ったサーバーを立てておき、リクエストを受けてNotionに保存します。

使用言語はpythonです。

## 詰まったポイント
Slackに標準でついてるワークフローだと他のアプリを介さない方法で他サイトへフォームの情報を移すのが難しそうでした。（もしかしたら管理者に使用可能な機能を制限されているかもですが）
そこでCustom Functionと呼ばれる、自分だけのワークフローステップを作る方向性で実装を進めることにしました。

## 実装の流れ
- SlackにてWorkflowの雛形を作る
- Custom Functionを作る
- Cloud Runでサーバーをたてる
の3本立てで参ります。

## SlackのWorkflowで雛形を作る

`Automation->Workflows->New Workflow`で新規作成できます。
![Screenshot 2025-05-14 at 10.25.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627854/e2b404d3-d3b1-445e-abce-20d0d9bd8a60.png)

こんな感じで作っちゃいます。この時点でフォームの設計を済ませておくと後々楽だと思います。
![Screenshot 2025-05-14 at 10.26.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627854/d3c8972b-7724-48bb-b0e8-fbd73c2c374c.png)


## Custom Functionの作り方

### 1. slack appを作る

https://api.slack.com/apps
上記リンクへアクセスしてアプリを作ります。

### 2. 組織レベルのアプリにする

Custom functionは組織レベルでないと使えません。
なのでアプリ設定画面の`Features->Org Level Apps`から有効にします。

### 3. Workflowステップを作る

`Features->Workflow Steps`にてStepを追加します。
callback IDは後から変更ができません。

日本語にしておくと実装の際に少し格好悪くなるので気をつけましょう。
今回私は「プロマネ相談」というCallback IDにしました！

次にInput Parametersを設定します。
こちらもParameter IDが後から変更できないので、しっかり変更しておきましょう。

内容はSlackのWorkflowで作ったフォームの内容に沿った形にするといいです。
順番は気にしなくて大丈夫です。

### 4. もしユーザーを扱いたいなら...
もしフォームでユーザー情報を扱いたいならば、Slack Appに`users:read`の権限をつける必要があります。
また、デフォルトで得られるのはuser_idなので、Slackからユーザーの名前を取得できるような処理を作りましょう。

こんな感じ
```python
@lru_cache(maxsize=200)          # ワークスペース人数 ≒ キャッシュ数
def slack_display_name(client, user_id: str) -> str:
    try:
        prof = client.users_info(user=user_id)["user"]["profile"]
        return prof.get("display_name") or prof.get("real_name") or user_id
    except Exception as e:
        return user_id 
```

↓使い方↓
```python
notion = Client(auth=os.environ["NOTION_TOKEN_SECRET"])
name = slack_display_name(client, user_id)
```

## Cloud Runにサーバーを上げる
### 1. アプリを作る

```python
import os
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from flask import Flask, request
from notion_client import Client
from functools import lru_cache

notion = Client(auth=os.environ["NOTION_TOKEN_SECRET"])

# 環境変数からSlackトークン読む
app = App(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"]
)
flask_app = Flask(__name__)
handler = SlackRequestHandler(app)

@lru_cache(maxsize=200)          # ワークスペース人数 ≒ キャッシュ数
def slack_display_name(client, user_id: str) -> str:
    try:
        prof = client.users_info(user=user_id)["user"]["profile"]
        return prof.get("display_name") or prof.get("real_name") or user_id
    except Exception as e:
        return user_id 

@app.function("プロマネ相談")
def handle_form_submit(inputs, client, logger, complete, fail):
    logger.info("Form submission received: %s", inputs)

    if not inputs:
        logger.error("No inputs found in the form submission.")
        return

    title = inputs.get("title")
    user_id = inputs.get("user")
    department = inputs.get("department")
    goal = inputs.get("goal")
    issue = inputs.get("issue")
    deadline = inputs.get("deadline")
    notes = inputs.get("notes") if inputs.get("notes") else ""

    name = slack_display_name(client, user_id)

    logger.info("Form inputs: %s", inputs)

    new_page = {
        "parent": {"database_id": os.environ["DATABASE_ID"]},
        "properties": {
            "起票者": {"rich_text": [{"text": {"content": name}}]},
            "所属課": {"select": {"name": department}},
            "相談内容": {"title": [{"text": {"content": title}}]},  # 新規追加フィールド
        },
        "children": [
            {
                "object": "block",
                "type": "heading_1",
                "heading_1": {"rich_text": [{"text": {"content": "実現したいこと"}}]}
            },
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {"rich_text": [{"text": {"content": goal}}]}
            },
            {
                "object": "block",
                "type": "heading_1",
                "heading_1": {"rich_text": [{"text": {"content": "原因となった課題"}}]}
            },
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {"rich_text": [{"text": {"content": issue}}]}
            },
            {
                "object": "block",
                "type": "heading_1",
                "heading_1": {"rich_text": [{"text": {"content": "いつまでに実現したいか"}}]}
            },
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {"rich_text": [{"text": {"content": deadline}}]}
            },
            {
                "object": "block",
                "type": "heading_1",
                "heading_1": {"rich_text": [{"text": {"content": "その他・補足事項"}}]}
            },
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {"rich_text": [{"text": {"content": notes}}]}
            },
        ]
    }

    try:
        notion.pages.create(**new_page)
        logger.info("notionページの作成に成功しました。")
        complete()
    except Exception as e:
        logger.error("Error creating page: %s", e)
        fail("notionページの作成に失敗しました。")

@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
    return handler.handle(request)

if __name__ == "__main__":
    flask_app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
```

### 2. Cloud Runを作る
上記の内容をArtifact Registry上にコンテナとして展開し、コンテナを使って起動できるようにします。
あと、最低インスタンス数は1にしておくといいです。（これならGCEでもいいかも）

### 3. Slack Appの設定を変更
`Features->Event Subscriptions`にてEnable Eventsをオンにして、Request URLにCloud Runのリンク+上記実装例であれば/slack/eventsをつけたものを設定します。

例: `https://cloudrunちゃん.app/slack/event`

ここでチャレンジが成功したらokです。

### 重要: アプリをインストールする
`Settings->Install App`から組織にアプリをインストールします。

## 最後にワークフローに追加
きちんとinstallができれば、ワークフローのステップの一番下に自分のアプリが表示されます。
追加して実行してみましょう！！

## 感想
今回別アプリのためにboltをCloud Runで運用してたので入れることにしましたが、notionに登録するだけでやけに大袈裟だなと思いました。

## 仲間を募集中
株式会社ホープでは、福岡で働くエンジニアを募集中です。
ぜひ、求人を見てみてください！
▼ Wantedly求人
https://www.wantedly.com/projects/1684027
▼ コーポレートサイト
https://www.zaigenkakuho.com/recruit/
「自治体を通じて人々に新たな価値を提供し、会社及び従業員の成長を追求する」
この理念の実現に向けて、今後も自治体の課題解決に取り組んでいきます。
ご応募お待ちしております！