目次
Connector

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 textstringリッチテキストは読むときにプレーンテキストに平坦化され、書くときはプレーンなリッチテキストとして書かれる。書式は保たれない
numberdecimalNotionは数値を倍精度で保存する。Connectorはプロパティに宣言された表示形式でdecimalとして読む
select、statusstring選択肢の名前。未知の選択肢を書くと、選択肢が分かっていれば check で、そうでなければ項目が失敗する
multi-selectarray<string>
datedate または timestamp。任意で end日付の範囲は start/end の列として読む。時刻のない日付は date。Notionはユーザーが選んだタイムゾーンを保持し、Connectorはそれをオフセットとして読む
checkboxbool
people、created_by、last_edited_byarray<string> / stringユーザーid。名前は notion.users から取る
relationarray<string>関連するページのid。関連先のデータベースとこれでJOINする
rollup、formula計算結果の型読み取りのみ
url、email、phone_numberstring
filesarray<content_ref>各ファイルは notion.contents を通して読める
unique_idstring接頭辞と番号を1つの文字列にしたもの。読み取りのみ
created_time、last_edited_timetimestamp

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 BYNotionが並べ替えられるプロパティで、プログラムがバイナリの照合順序に依存しないとき
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