Notion
Notion Connectorは、ワークスペースのデータベース、ページ、ページ本文を読み、ページ、プロパティ、ブロックを書き戻します。プログラムはこれを使って、データベースを他のシステムと揃えたり(リスクの高い取引先ごとのタスク、CRMのレコードに追従するステータス)、人がNotionに書いた文章を読んだりします。
読み取り元
notion.<database>— インテグレーションと共有された各データベースが読み取り元です。接続で付けた別名で参照します(notion.tasks)。列はデータベースのプロパティに、id(ページid)、url、created_time、created_by、last_edited_time、last_edited_byを加えたものです。キーはidです。データベースは、プロパティの1つをUPSERT用の一意キーとして宣言することもできます(Effectと操作を参照)。notion.pages— インテグレーションから見えるすべてのページ。データベースの中のものも外のものも含みます。id、title、parent(データベースかページのid)、url、archived、作成日時と更新日時、そしてnotion.contentsとJOINするためのresource(ページid)とversion(last_edited_time)。キーはidです。データベースの読み取り元も同じresourceとversionを持ちます。notion.users— ワークスペースのメンバーとbot。id、name、type、email(人の場合で、インテグレーションに読む権限があるとき)。キーはidです。notion.contents— ページの本文をcontent_refとして読みます。resource、version(ページのlast_edited_time)、media_type(text/markdown)、content。notion.pagesかデータベースの読み取り元からresourceとversionでJOINしてください。ConnectorはJOINで指定されたページだけを取得し、ブロックをMarkdownに変換します。notion.files— ページとファイルプロパティに添付されたファイルと画像。resource、version、page_id、name、media_type、size。バイト列は、同じようにnotion.contentsを通して読みます。
型
| Notionのプロパティ | ActQL | 備考 |
|---|---|---|
| title、rich text | string | リッチテキストは読むときにプレーンテキストに平坦化され、書くときはプレーンなリッチテキストとして書かれる。書式は保たれない |
| number | decimal | Notionは数値を倍精度で保存する。Connectorはプロパティに宣言された表示形式でdecimalとして読む |
| select、status | string | 選択肢の名前。未知の選択肢を書くと、選択肢が分かっていれば check で、そうでなければ項目が失敗する |
| multi-select | array<string> | |
| date | date または timestamp。任意で end | 日付の範囲は start/end の列として読む。時刻のない日付は date。Notionはユーザーが選んだタイムゾーンを保持し、Connectorはそれをオフセットとして読む |
| checkbox | bool | |
| people、created_by、last_edited_by | array<string> / string | ユーザーid。名前は notion.users から取る |
| relation | array<string> | 関連するページのid。関連先のデータベースとこれでJOINする |
| rollup、formula | 計算結果の型 | 読み取りのみ |
| url、email、phone_number | string | |
| files | array<content_ref> | 各ファイルは notion.contents を通して読める |
| unique_id | string | 接頭辞と番号を1つの文字列にしたもの。読み取りのみ |
| created_time、last_edited_time | timestamp |
Effectと操作
レコードのEffectは、データベースのページをキーで書きます。
- insert —
EFFECT INSERT notion.tasks (title, status, notes) VALUES (…)は、そのプロパティを持つページを作ります。 - update —
EFFECT UPDATE notion.tasks ID id SET (status = 'Done')。 - upsert —
EFFECT UPSERT notion.tasks ID external_id SET (…)。接続がそのデータベースについて一意と宣言したプロパティ(テキスト、数値、unique_id のプロパティ)をキーにします。Connectorはプレビューを作るときに一意性を検証し、複数のページに一致するキーの項目はfailedになります。 - delete —
EFFECT DELETE notion.tasks ID idは、ページをアーカイブします。NotionのAPIに完全削除はありません。アーカイブしたページはNotionで復元でき、notion.pagesにはarchived = TRUEとして出ます。
ID で指定するキーは、ちょうど1つのページを識別しなければなりません。id は常にそうで、宣言した一意プロパティは検証を経てそうなります。
コンテンツ:
- store —
EFFECT STORE notion.blocks SET (page = id, content = content)は、レコードのcontent(SERIALIZEまたはcontentsの読み取り元から来たcontent_ref)から、ファイルか画像のブロックをページに追記します。
操作:
- append_blocks —
EFFECT COMMAND notion.append_blocks TARGET page_id ID key INPUT (markdown = text)は、Markdownから変換したブロックをページの末尾に追記します。
セットアップ
"notion": {
"connector": "notion",
"databases": {
"tasks": {"id": "a1b2c3d4-…", "unique": "external_id"},
"accounts": "e5f6a7b8-…"
},
"credentials": {"integration_token": {"env": "NOTION_TOKEN"}}
}Notionで内部インテグレーションを作り、各データベースとページツリーをそのインテグレーションと共有します。インテグレーションに見えるのは共有されたものだけです。databases のマップで、各データベースidにプログラムで使う名前を付け、必要なら UPSERT 用の一意プロパティを指定します。読み取りにはインテグレーションのコンテンツ読み取り権限が、書き込みにはコンテンツの挿入と更新の権限が、notion.users にはユーザー情報の読み取り権限が必要です。ActQL Cloudでは、承認済みの場合にホスト型OAuthで公開インテグレーションを使えます。同じ内部インテグレーションのトークンもそのまま使えます。
Capabilityとプッシュダウン
| 演算 | プッシュダウンされる条件 |
|---|---|
| 列の選択 | 常に。データベースのクエリはページ全体を返すので、列の選択はエンジンが保持する量を減らし、Notionが送る量は変えない |
WHERE | 述語が1つのデータベースのプロパティに対するもので、Notionのフィルターが正確に対応する演算子を使うとき。select、status、checkbox、number、date の等値と不等、number と date の範囲比較、IS NULL / IS NOT NULL(空 / 空でない)、テキストに対する LIKE 'x%'、'%x'、'%x%'(前方一致、後方一致、含む。Notionでは大文字小文字を区別しないので、区別する述語はHostに残る) |
ORDER BY | Notionが並べ替えられるプロパティで、プログラムがバイナリの照合順序に依存しないとき |
LIMIT | 常に。ページサイズとカーソルで実現する |
GROUP BY、JOIN | されない。Hostで実行する |
プレビューと突き合わせ
プレビュー。 更新、UPSERT、アーカイブは full_diff です。Connectorは対象の各ページを読み、プロパティと last_edited_time を記録し、プレビューにはプロパティごとに現在の値と新しい値が出ます。適用時にページをもう一度読み、last_edited_time が動いていれば、その項目は書きません。failed(conflict)で終わり、続けるよう求められればRunは新しいプレビューを作ります。追加、STORE、append_blocks は targeted です。プレビューには、データベースかページと、書き込まれるプロパティ、ファイル、ブロックがそのまま出ます。
突き合わせ。 どの書き込みにもリクエストのマーカーが付きます。接続のエントリでマーカー用のプロパティを宣言したデータベース("marker": "actql_request")では、マーカーをそのプロパティに書き込み、応答が失われたときはデータベースをそのマーカーで検索して突き合わせます。マーカー用のプロパティを宣言していない場合、Connectorが認識できる証跡はないので、項目は unknown のままで、何も再送しません。append_blocks と STORE は追記したブロックの内容にマーカーを残し、ページの子ブロックを読んで突き合わせます。操作は、Notionが作成したブロックのidを返した時点で accepted、ページを読み返してそれが見えた時点で applied になります。
上限と費用
- Notionはインテグレーションごとに毎秒およそ3リクエストまでです。Connectorはその範囲でペースを調整し、書き込みをキューに入れます。
- 読み取りは100ページずつページングされます。数千件のデータベースは、プログラムの件数の上限の範囲で多数のリクエストに分かれて読まれます。上限に達した読み取りは、途中までの結果を返すのではなくエラーで止まります。
- ページ本文は1ページにつき1リクエスト(1回に100ブロック)で取得し、入れ子のブロックは階層ごとに1リクエストが必要です。変換後のMarkdownにプログラムのバイト数の上限が適用されます。
- データベースのスキーマが変わると(プロパティ名の変更など)、プログラムか別名を更新するまで
checkは名前を示して失敗します。 - NotionのAPIに利用料はありません。費用にあたるのはレート制限です。
例
担当者別の期限切れタスクです。
FROM notion.tasks
|> WHERE status != 'Done' AND due < CURRENT_DATE()
|> AGGREGATE COUNT(*) AS overdue GROUP BY owner
|> ORDER BY overdue DESC;リスクの高い取引先ごとにタスクを1つ保ち、取引先のidで作成または更新します。
SELECT a.id AS external_id, a.name, risk.value.reason AS reason
FROM salesforce.accounts AS a
JOIN snowflake.usage_30d AS u ON a.id = u.account_id
|> INFER `model-task:churn-risk.v1`
INPUT (notes = a.description, active_users = u.active_users, trend = u.trend) AS risk
|> WHERE risk.outcome = 'ok' AND risk.value.level = 'high'
|> EFFECT UPSERT notion.tasks ID external_id
SET (title = CONCAT('Call ', name), status = 'To do', notes = reason);会議のページを要約し、その要約をページに追記します。
FROM notion.pages AS p
|> WHERE p.parent = @meeting_notes_db AND p.created_time >= CURRENT_DATE()
|> JOIN notion.contents AS c ON p.resource = c.resource AND p.version = c.version
|> INFER `model-task:summarise-doc.v1` INPUT (name = p.title, document = c.content) AS s
|> WHERE s.outcome = 'ok'
|> EFFECT COMMAND notion.append_blocks TARGET p.id ID p.id
INPUT (markdown = CONCAT('## Summary\n', s.value.summary));Source: actql/actql/docs/connectors/notion.md at d904416674e4 · Edit on GitHub