Notion APIを利用して問い合わせ対応の業務を改善した話

:::note info
この記事は[クラウドワークスアドベントカレンダー2022](https://qiita.com/advent-calendar/2022/crowdworks) 20日目の記事です。
:::

# はじめに
こんにちは。クラウドログの開発チームでエンジニアをしている寺島です。
普段は、クラウドログのバックエンド開発をしながら、お客様からの問い合わせ対応をしています。

今年の９月くらいまでは問い合わせ対応や不具合対応をメインタスクとしていたのですが、不具合対応に目処が立ったこともあり、ついにサポートチームも新規機能開発チームに合流することに！

しかし、お客様からの問い合わせは止まりません。新規機能開発をしながらのサポート業務（問い合わせ対応やステータス確認など）がスムーズにできず、問い合わせの管理に抜け漏れが発生していました。

そこで、ちょうどNotionを利用して問い合わせ情報を管理していたのでNotion APIを利用して業務改善を試みました。今回はその話をしようと思います。

# Notionとは
Notionとは、米国サンフランシスコに本社を置く`Notion Labs Inc`が開発したクラウドツールです。メモやタスクの管理、Wiki、データベースなどさまざまな機能を一元的に使うことができるため`All-in-one workspace`と呼ばれています。
クラウドログのチームでも今年初めから利用しており、メモだけではなくプロジェクト管理やナレッジ管理に利用しています。

# Notion APIとは
アプリだけでも優秀なNotionですが、APIが準備されており、2022年冬時点では無料で利用することができます。

https://developers.notion.com/

APIでは次のような操作が可能です。
- データベースの作成
- 指定のデータベースの取得、更新
- ページの追加、更新
- ユーザー情報の取得


# やりたいこと
では冒頭で述べた通り、Notion APIを利用して業務改善を図りたいと思います。
現在の運用は、Slackワークフローから問い合わせ内容を投稿する形になっているため、Slackワークフローで投稿された内容をNotionデータベースに登録する仕組みを作ろうと思います。

今回やりたいことは以下の通りです。
1. Slackワークフローで投稿した内容をGoogleSpreadSheetに保存
1. GoogleSpreadSheetの変更をトリガーにGoogleAppsScript（GAS)を実行
1. GASから NotionAPI と SlackAPI を実行
    - Slack投稿のURLを取得
    - Notionデータベースへ保存

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953687/287b7d98-f0b0-f8ab-9ca3-18cd08def8fc.png)

# 事前準備
今回はNotionデータベースを操作するため、以下の準備を行います。

### インテグレーションの作成
まずは以下のページから画面の説明に沿ってインテグレーションを作成し、「内部インテグレーション」のトークンを発行します。
※トークンは外部に公開しないようにご注意ください。

https://www.notion.so/my-integrations

### データベースIDの取得
Slackワークフローの内容を保存するデータベースのIDを取得します。データベース右上のメニューから「ビューのリンクをコピー」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953687/f32eafbc-5d6e-befd-5b8f-38f06565c644.png)
取得したリンクの`www.notion.so/` と `?v=` の間の32文字を取得します。この32文字がデータベースIDです。
```
https://www.notion.so/workspace/1234567890abcdef1234567890abcdef?v=...
                                |--------- Database ID --------|
```


### データベースへの接続
次に、先ほど作成したインテグレーションとデータベースを接続します。
データベースがあるページの設定を開き`コネクトの追加`を選択します。今回は「調査チャンネル自動連携」という名前でインテグレーションを作成したので、対象のインテグレーションを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953687/bd94ad45-62d2-8a77-363c-d6c953437977.png)


# APIを試してみる
まずは、手元のシェルのcurlコマンドで動作確認を行います。使いたい機能は以下の２つです。

### ユーザー一覧を取得する
まずは、ユーザー一覧を取得します。１点、期待と異なる挙動がありました。このAPIでは、「メンバー」の情報は取得できますが、「ゲスト」の情報は取得できないようです。

Notionサポートに問い合わせたところ、2022冬現在では「ゲスト」ユーザーの情報を取得するAPIおよびアプリ上での手段は無いとのことでした。


```bash
curl -X GET https://api.notion.com/v1/users \
-H "Authorization: Bearer $NOTION_TOKEN" \
-H "Content-Type: application/json" \
-H 'Notion-Version: 2022-06-28';
```

### データベースへページを追加する
次は、投稿された情報を新規ページとして追加します。APIを叩く際にbodyに本文やプロパティ条件を渡せますが、まずは条件無しでページの追加をしてみます。
```bash
curl -X POST https://api.notion.com/v1/pages \
-H "Authorization: Bearer $NOTION_TOKEN" \
-H "Content-Type: application/json" \
-H 'Notion-Version: 2022-06-28' \
--data "{
  \"parent\": {
    \"database_id\": \"$DATABASE_ID\"
  },
  \"properties\": {
  }
}
"
```

```bash
curl -X POST https://api.notion.com/v1/pages \
-H "Authorization: Bearer $NOTION_TOKEN" \
-H "Content-Type: application/json" \
-H 'Notion-Version: 2022-06-28' \
--data "{
  \"parent\": {
    \"database_id\": \"$DATABASE_ID\"
  },
  \"properties\": {
    \"タイトル\": {
      \"title\": [
        {
          \"text\": {
            \"content\": \"テストタイトル\"
          }
        }
      ]
    },
    \"ステータス\": {
      \"multi_select\": [
        {
           \"name\": \"New\"
        }
      ]
    },
    \"投稿者\": {
      \"people\": [
        {
          \"object\": \"user\",
          \"id\": \"$NOTION_USER_ID\"
        }
      ]
    }
  }
}
"
```

# コードを実装する（GAS）
curlで動作確認ができたので、実際にGASで実装します。Slackワークフローの投稿をGoogleSpreadSheetへ保存しているため、その変更をトリガーに実行されるコードを実装します。

### ワークフロー投稿者のUserIdを取得する
Slackワークフローの投稿者のemailからNotionのUserIdを取得します。NotionのPeopleプロパティはUserIdを指定する必要があるため、Slack投稿者のemailから検索をします。
```javascript
function getNotionUserId(email) {
  const res = runGetMethod("https://api.notion.com/v1/users");
  var datas = JSON.parse(res.getContentText());
  for(let d in datas.results){
    let person = datas.results[d].person
    if (person != undefined){
      let mail = datas.results[d].person.email
      if (email === mail) {
        return datas.results[d].id
      }
    }    
  }
  return null
}

function runGetMethod(url) {
  const options = {
    "method" : "GET",
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Bearer " + NOTION_TOKEN,
      "Notion-Version": "2022-06-28",
    }
  };
  return UrlFetchApp.fetch(url, options);
}
```

### Slack投稿のURLを取得する
SlackAPIを使って、投稿されたワークフローのURLを取得します。どの投稿とNotionのページがリンクしているかわかるようにするためです。今回は、この部分に関しては割愛します。

### 投稿された内容でページを作成する
必要な情報が集まったので、その内容でページを作成します。プロパティ要素は`properties`パラメータへ、ページの本文は`childlen`パラメータへ記載します。
```javascript
function createArticle(title, details, userId, permalink) {
  payload = {
    "parent": {
      "database_id": DATABASE_ID
    },
    "properties": {
      "タイトル": {
        "title": [
          {
            "text": {
              "content": title
            }
          }
        ]
      },
      "ステータス": {
        "multi_select": [
          {
            "name": "New"
          }
        ]
      },
      "投稿者": {
        "people": [
          {
            "object": "user",
            "id": userId
          }
        ]
      },
      "URL": {
        "url": permalink
      }
    },
    "children": [
      {
        "type": "code",
        "code": {
          "rich_text": [{
            "type": "text",
            "text": {
              "content": details,
              }
            }],
          "language": "plain text"
        }
      }
    ]
  }
  
  res = runPostMethod("https://api.notion.com/v1/pages/", payload);
  var json = JSON.parse(res.getContentText());
  return json.id;
}

function runPostMethod(url, payload) {
  const options = {
    "method" : "POST",
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Bearer " + NOTION_TOKEN,
      "Notion-Version": "2022-06-28",
    },
    "muteHttpExceptions" : true,
    "payload" : JSON.stringify(payload)
  };
```

### シークレットをプロパティストアで管理する
トークンをプロパティストアで管理します。GASで実装をするにあたり、NotionのシークレットとデータベースIDが必要になります。しかし、セキュリティ的にコードにベタで書きたくはありません。
なので、専用のデータ格納領域であるプロパティストアで管理します。

https://developers.google.com/apps-script/guides/properties

### トリガーを設定する
最後に、GAS実行のトリガーを設定します。詳細は割愛しますが、シートのデータ変更をトリガーに実行するようにします。

# 完成品
![完成品.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953687/cca8a011-010f-234b-428c-7ffaca8eb0ec.gif)

# おわりに
以上、Notion APIを使ってみた話でした。

今回使用したAPIで残念だったのは、取得できるユーザー一覧はメンバーのみで、ゲストユーザーのIDを取得する方法は現在はまだ無いようです。
ゲストユーザーの方が投稿する場合は、担当者情報を手動で入力する形となってしまい、完全な自動化に出来なかったところは残念でした。

しかし、今回の目的である「問い合わせの転記漏れ」はこれで防げるようになりました。
小さい業務改善ですが、チームメンバーからは結構好評で、さらに今回の内容は他の業務改善にも活用できそうなので、かなり可能性を感じています。

今後も機能更新のアップデートをキャッチして色々試していきたいです。