first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,25 @@
{
"node": "n8n-nodes-base.notion",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Productivity"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/notion/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.notion/"
}
],
"generic": [
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
}
]
}
}
@@ -0,0 +1,28 @@
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
import { VersionedNodeType } from 'n8n-workflow';
import { NotionV1 } from './v1/NotionV1.node';
import { NotionV2 } from './v2/NotionV2.node';
export class Notion extends VersionedNodeType {
constructor() {
const baseDescription: INodeTypeBaseDescription = {
displayName: 'Notion',
name: 'notion',
icon: { light: 'file:notion.svg', dark: 'file:notion.dark.svg' },
group: ['output'],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Notion API',
defaultVersion: 2.2,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new NotionV1(baseDescription),
2: new NotionV2(baseDescription),
2.1: new NotionV2(baseDescription),
2.2: new NotionV2(baseDescription),
};
super(nodeVersions, baseDescription);
}
}
@@ -0,0 +1,25 @@
{
"node": "n8n-nodes-base.notionTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Productivity"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/notion/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.notiontrigger/"
}
],
"generic": [
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
}
]
}
}
@@ -0,0 +1,273 @@
import moment from 'moment-timezone';
import {
type IPollFunctions,
type IDataObject,
type INodeExecutionData,
type INodeType,
type INodeTypeDescription,
NodeConnectionTypes,
} from 'n8n-workflow';
import {
databaseUrlExtractionRegexp,
databaseUrlValidationRegexp,
idExtractionRegexp,
idValidationRegexp,
} from './shared/constants';
import { notionApiRequest, simplifyObjects } from './shared/GenericFunctions';
import { listSearch } from './shared/methods';
export class NotionTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Notion Trigger',
name: 'notionTrigger',
icon: { light: 'file:notion.svg', dark: 'file:notion.dark.svg' },
group: ['trigger'],
version: 1,
description: 'Starts the workflow when Notion events occur',
subtitle: '={{$parameter["event"]}}',
defaults: {
name: 'Notion Trigger',
},
credentials: [
{
name: 'notionApi',
required: true,
},
],
polling: true,
inputs: [],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: 'Event',
name: 'event',
type: 'options',
options: [
{
name: 'Page Added to Database',
value: 'pageAddedToDatabase',
},
{
name: 'Page Updated in Database',
value: 'pagedUpdatedInDatabase',
},
],
required: true,
default: 'pageAddedToDatabase',
},
{
displayName:
'In Notion, make sure to <a href="https://www.notion.so/help/add-and-manage-connections-with-the-api" target="_blank">add your connection</a> to the pages you want to access.',
name: 'notionNotice',
type: 'notice',
default: '',
},
{
displayName: 'Database',
name: 'databaseId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
required: true,
modes: [
{
displayName: 'Database',
name: 'list',
type: 'list',
placeholder: 'Select a Database...',
typeOptions: {
searchListMethod: 'getDatabases',
searchable: true,
},
},
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder:
'https://www.notion.so/0fe2f7de558b471eab07e9d871cdf4a9?v=f2d424ba0c404733a3f500c78c881610',
validation: [
{
type: 'regex',
properties: {
regex: databaseUrlValidationRegexp,
errorMessage: 'Not a valid Notion Database URL',
},
},
],
extractValue: {
type: 'regex',
regex: databaseUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Database ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
event: ['pageAddedToDatabase', 'pagedUpdatedInDatabase'],
},
},
description: 'The Notion Database to operate on',
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
event: ['pageAddedToDatabase', 'pagedUpdatedInDatabase'],
},
},
default: true,
description:
'Whether to return a simplified version of the response instead of the raw data',
},
],
};
methods = {
listSearch,
};
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const webhookData = this.getWorkflowStaticData('node');
const databaseId = this.getNodeParameter('databaseId', '', { extractValue: true }) as string;
const event = this.getNodeParameter('event') as string;
const simple = this.getNodeParameter('simple') as boolean;
const lastTimeChecked = webhookData.lastTimeChecked
? moment(webhookData.lastTimeChecked as string)
: moment().set({ second: 0, millisecond: 0 }); // Notion timestamp accuracy is only down to the minute
// update lastTimeChecked to now
webhookData.lastTimeChecked = moment().set({ second: 0, millisecond: 0 });
// because Notion timestamp accuracy is only down to the minute some duplicates can be fetch
const possibleDuplicates = (webhookData.possibleDuplicates as string[]) ?? [];
const sortProperty = event === 'pageAddedToDatabase' ? 'created_time' : 'last_edited_time';
const option: IDataObject = {
headers: {
'Notion-Version': '2022-02-22',
},
};
const body: IDataObject = {
page_size: 1,
sorts: [
{
timestamp: sortProperty,
direction: 'descending',
},
],
...(this.getMode() !== 'manual' && {
filter: {
timestamp: sortProperty,
[sortProperty]: {
on_or_after: lastTimeChecked.utc().format(),
},
},
}),
};
let records: IDataObject[] = [];
let hasMore = true;
//get last record
let { results: data } = await notionApiRequest.call(
this,
'POST',
`/databases/${databaseId}/query`,
body,
{},
'',
option,
);
if (this.getMode() === 'manual') {
if (simple) {
data = simplifyObjects(data, false, 1);
}
if (Array.isArray(data) && data.length) {
return [this.helpers.returnJsonArray(data)];
}
}
// if something changed after the last check
if (Array.isArray(data) && data.length && Object.keys(data[0] as IDataObject).length !== 0) {
do {
body.page_size = 10;
const { results, has_more, next_cursor } = await notionApiRequest.call(
this,
'POST',
`/databases/${databaseId}/query`,
body,
{},
'',
option,
);
records.push(...(results as IDataObject[]));
hasMore = has_more;
if (next_cursor !== null) {
body.start_cursor = next_cursor;
}
// Only stop when we reach records strictly before last recorded time to be sure we catch records from the same minute
} while (
!moment(records[records.length - 1][sortProperty] as string).isBefore(lastTimeChecked) &&
hasMore
);
// Filter out already processed left over records:
// with a time strictly before the last record processed
// or from the same minute not present in the list of processed records
records = records.filter(
(record: IDataObject) => !possibleDuplicates.includes(record.id as string),
);
// Save the time of the most recent record processed
if (records[0]) {
const latestTimestamp = moment(records[0][sortProperty] as string);
// Save record ids with the same timestamp as the latest processed records
webhookData.possibleDuplicates = records
.filter((record: IDataObject) =>
moment(record[sortProperty] as string).isSame(latestTimestamp),
)
.map((record: IDataObject) => record.id);
} else {
webhookData.possibleDuplicates = undefined;
}
if (simple) {
records = simplifyObjects(records, false, 1);
}
if (Array.isArray(records) && records.length) {
return [this.helpers.returnJsonArray(records)];
}
}
return null;
}
}
@@ -0,0 +1,12 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"Messaggi Schedulati": {
"type": "string"
}
},
"version": 2
}
@@ -0,0 +1,136 @@
{
"type": "object",
"properties": {
"has_more": {
"type": "boolean"
},
"next_cursor": {
"type": "null"
},
"object": {
"type": "string"
},
"request_id": {
"type": "string"
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"created_by": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string"
}
}
},
"created_time": {
"type": "string"
},
"has_children": {
"type": "boolean"
},
"id": {
"type": "string"
},
"in_trash": {
"type": "boolean"
},
"last_edited_by": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string"
}
}
},
"last_edited_time": {
"type": "string"
},
"object": {
"type": "string"
},
"paragraph": {
"type": "object",
"properties": {
"color": {
"type": "string"
},
"text": {
"type": "array",
"items": {
"type": "object",
"properties": {
"annotations": {
"type": "object",
"properties": {
"bold": {
"type": "boolean"
},
"code": {
"type": "boolean"
},
"color": {
"type": "string"
},
"italic": {
"type": "boolean"
},
"strikethrough": {
"type": "boolean"
},
"underline": {
"type": "boolean"
}
}
},
"plain_text": {
"type": "string"
},
"text": {
"type": "object",
"properties": {
"content": {
"type": "string"
}
}
},
"type": {
"type": "string"
}
}
}
}
}
},
"parent": {
"type": "object",
"properties": {
"page_id": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"type": {
"type": "string"
}
}
}
}
},
"version": 2
}
@@ -0,0 +1,55 @@
{
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"content": {
"type": "string"
},
"has_children": {
"type": "boolean"
},
"id": {
"type": "string"
},
"in_trash": {
"type": "boolean"
},
"last_edited_by": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string"
}
}
},
"object": {
"type": "string"
},
"parent": {
"type": "object",
"properties": {
"page_id": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"parent_id": {
"type": "string"
},
"root_id": {
"type": "string"
},
"type": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,469 @@
{
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"created_by": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string"
}
}
},
"created_time": {
"type": "string"
},
"description": {
"type": "array",
"items": {
"type": "object",
"properties": {
"annotations": {
"type": "object",
"properties": {
"bold": {
"type": "boolean"
},
"code": {
"type": "boolean"
},
"color": {
"type": "string"
},
"italic": {
"type": "boolean"
},
"strikethrough": {
"type": "boolean"
},
"underline": {
"type": "boolean"
}
}
},
"href": {
"type": "null"
},
"plain_text": {
"type": "string"
},
"text": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"link": {
"type": "null"
}
}
},
"type": {
"type": "string"
}
}
}
},
"id": {
"type": "string"
},
"in_trash": {
"type": "boolean"
},
"is_inline": {
"type": "boolean"
},
"last_edited_by": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string"
}
}
},
"last_edited_time": {
"type": "string"
},
"name": {
"type": "string"
},
"object": {
"type": "string"
},
"parent": {
"type": "object",
"properties": {
"page_id": {
"type": "string"
},
"type": {
"type": "string"
},
"workspace": {
"type": "boolean"
}
}
},
"properties": {
"type": "object",
"properties": {
"": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"answer": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"Author": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"Dateien und Medien": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"department": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"multi_select": {
"type": "object",
"properties": {
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"color": {
"type": "string"
},
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"Name": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"Produkt-ID": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"number": {
"type": "object",
"properties": {
"format": {
"type": "string"
}
}
},
"type": {
"type": "string"
}
}
},
"question": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"Tag": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"multi_select": {
"type": "object",
"properties": {
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"color": {
"type": "string"
},
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"tags": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"multi_select": {
"type": "object",
"properties": {
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"color": {
"type": "string"
},
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"updated_at": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"Zuletzt aktualisiert": {
"type": "object",
"properties": {
"description": {
"type": "null"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
}
},
"public_url": {
"type": "null"
},
"request_id": {
"type": "string"
},
"title": {
"type": "array",
"items": {
"type": "object",
"properties": {
"annotations": {
"type": "object",
"properties": {
"bold": {
"type": "boolean"
},
"code": {
"type": "boolean"
},
"color": {
"type": "string"
},
"italic": {
"type": "boolean"
},
"strikethrough": {
"type": "boolean"
},
"underline": {
"type": "boolean"
}
}
},
"href": {
"type": "null"
},
"plain_text": {
"type": "string"
},
"text": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"link": {
"type": "null"
}
}
},
"type": {
"type": "string"
}
}
}
},
"url": {
"type": "string"
}
},
"version": 6
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 2
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 2
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,29 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"object": {
"type": "string"
},
"person": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
}
},
"request_id": {
"type": "string"
},
"type": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,26 @@
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"object": {
"type": "string"
},
"person": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
}
},
"type": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.58276 6.97679C8.82047 7.98238 9.28479 7.90566 11.6091 7.75057L33.5206 6.43488C33.9853 6.43488 33.5989 5.97127 33.4439 5.89423L29.8049 3.26348C29.1076 2.72213 28.1786 2.10217 26.3982 2.25726L5.18115 3.80476C4.40736 3.88148 4.25282 4.26837 4.56096 4.57847L7.58276 6.97679ZM8.89829 12.0833V35.1381C8.89829 36.3771 9.51746 36.8407 10.911 36.764L34.9919 35.3706C36.3862 35.2939 36.5415 34.4417 36.5415 33.4352V10.5351C36.5415 9.53019 36.1549 8.98829 35.3014 9.06564L10.1367 10.5351C9.20799 10.6131 8.89821 11.0777 8.89821 12.0833H8.89829ZM32.6708 13.32C32.8252 14.017 32.6708 14.7133 31.9725 14.7917L30.8123 15.0229V32.0434C29.8049 32.5848 28.8759 32.8944 28.1018 32.8944C26.8625 32.8944 26.5521 32.5072 25.6237 31.3474L18.0343 19.4329V30.9605L20.4359 31.5024C20.4359 31.5024 20.4359 32.8944 18.4983 32.8944L13.1568 33.2042C13.0016 32.8944 13.1568 32.1214 13.6986 31.9665L15.0925 31.5802V16.3385L13.1572 16.1834C13.0019 15.4864 13.3885 14.4814 14.4733 14.4035L20.2035 14.0172L28.1018 26.0868V15.4097L26.0881 15.1786C25.9335 14.3265 26.5521 13.7078 27.3265 13.6311L32.6708 13.32ZM3.39973 1.71598L25.4688 0.0907457C28.179 -0.141688 28.8763 0.0140245 30.5796 1.25135L37.6243 6.20276C38.7867 7.05421 39.1742 7.28602 39.1742 8.21419V35.3706C39.1742 37.0726 38.5542 38.0791 36.3865 38.2331L10.7577 39.7807C9.13049 39.8583 8.35607 39.6264 7.50392 38.5426L2.31608 31.8117C1.38658 30.5726 1 29.6457 1 28.5613V4.42283C1 3.03105 1.62019 1.87005 3.39973 1.71598V1.71598Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.58276 6.97679C8.82047 7.98238 9.28479 7.90566 11.6091 7.75057L33.5206 6.43488C33.9853 6.43488 33.5989 5.97127 33.4439 5.89423L29.8049 3.26348C29.1076 2.72213 28.1786 2.10217 26.3982 2.25726L5.18115 3.80476C4.40736 3.88148 4.25282 4.26837 4.56096 4.57847L7.58276 6.97679ZM8.89829 12.0833V35.1381C8.89829 36.3771 9.51746 36.8407 10.911 36.764L34.9919 35.3706C36.3862 35.2939 36.5415 34.4417 36.5415 33.4352V10.5351C36.5415 9.53019 36.1549 8.98829 35.3014 9.06564L10.1367 10.5351C9.20799 10.6131 8.89821 11.0777 8.89821 12.0833H8.89829ZM32.6708 13.32C32.8252 14.017 32.6708 14.7133 31.9725 14.7917L30.8123 15.0229V32.0434C29.8049 32.5848 28.8759 32.8944 28.1018 32.8944C26.8625 32.8944 26.5521 32.5072 25.6237 31.3474L18.0343 19.4329V30.9605L20.4359 31.5024C20.4359 31.5024 20.4359 32.8944 18.4983 32.8944L13.1568 33.2042C13.0016 32.8944 13.1568 32.1214 13.6986 31.9665L15.0925 31.5802V16.3385L13.1572 16.1834C13.0019 15.4864 13.3885 14.4814 14.4733 14.4035L20.2035 14.0172L28.1018 26.0868V15.4097L26.0881 15.1786C25.9335 14.3265 26.5521 13.7078 27.3265 13.6311L32.6708 13.32ZM3.39973 1.71598L25.4688 0.0907457C28.179 -0.141688 28.8763 0.0140245 30.5796 1.25135L37.6243 6.20276C38.7867 7.05421 39.1742 7.28602 39.1742 8.21419V35.3706C39.1742 37.0726 38.5542 38.0791 36.3865 38.2331L10.7577 39.7807C9.13049 39.8583 8.35607 39.6264 7.50392 38.5426L2.31608 31.8117C1.38658 30.5726 1 29.6457 1 28.5613V4.42283C1 3.03105 1.62019 1.87005 3.39973 1.71598V1.71598Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
const notionIdRegexp = '[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}';
export const idExtractionRegexp = `^(${notionIdRegexp})`;
export const idValidationRegexp = `${idExtractionRegexp}.*`;
const baseUrlRegexp = '(?:https|http)://www\\.notion\\.so/(?:[a-z0-9-]{2,}/)?';
export const databaseUrlExtractionRegexp = `${baseUrlRegexp}(${notionIdRegexp})`;
export const databaseUrlValidationRegexp = `${databaseUrlExtractionRegexp}.*`;
export const databasePageUrlExtractionRegexp = `${baseUrlRegexp}(?:[a-zA-Z0-9-]{1,}-)?(${notionIdRegexp})`;
export const databasePageUrlValidationRegexp = `${databasePageUrlExtractionRegexp}.*`;
export const blockUrlExtractionRegexp = `${baseUrlRegexp}(?:[a-zA-Z0-9-]{2,}-)?(${notionIdRegexp})`;
export const blockUrlValidationRegexp = `${blockUrlExtractionRegexp}.*`;
@@ -0,0 +1,291 @@
import type { INodeProperties } from 'n8n-workflow';
import { blocks } from './Blocks';
import {
blockUrlExtractionRegexp,
blockUrlValidationRegexp,
idExtractionRegexp,
idValidationRegexp,
} from '../constants';
//RLC with fixed regex for blockId
const blockIdRLC: INodeProperties = {
displayName: 'Block',
name: 'blockId',
type: 'resourceLocator',
default: { mode: 'url', value: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder:
'e.g. https://www.notion.so/Block-Test-88888ccc303e4f44847f27d24bd7ad8e?pvs=4#c44444444444bbbbb4d32fdfdd84e',
validation: [
{
type: 'regex',
properties: {
regex: blockUrlValidationRegexp,
errorMessage: 'Not a valid Notion Block URL',
},
},
],
// extractValue: {
// type: 'regex',
// regex: blockUrlExtractionRegexp,
// },
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'e.g. ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Block ID',
},
},
],
},
],
description:
"The Notion Block to get all children from, when using 'By URL' mode make sure to use the URL of the block itself, you can find it in block parameters in Notion under 'Copy link to block'",
};
export const blockOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['block'],
},
},
options: [
{
name: 'Append After',
value: 'append',
description: 'Append a block',
action: 'Append a block',
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-option-name-wrong-for-get-many
name: 'Get Child Blocks',
value: 'getAll',
description: 'Get many child blocks',
action: 'Get many child blocks',
},
],
default: 'append',
},
];
export const blockFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* block:append */
/* -------------------------------------------------------------------------- */
{
displayName: 'Block',
name: 'blockId',
type: 'resourceLocator',
default: { mode: 'url', value: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder: 'https://www.notion.so/My-Page-b4eeb113e118403ba450af65ac25f0b9',
validation: [
{
type: 'regex',
properties: {
regex: blockUrlValidationRegexp,
errorMessage: 'Not a valid Notion Block URL',
},
},
],
extractValue: {
type: 'regex',
regex: blockUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Block ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
resource: ['block'],
operation: ['append'],
},
hide: {
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
description: 'The Notion Block to append blocks to',
},
{
...blockIdRLC,
displayOptions: {
show: {
resource: ['block'],
operation: ['append'],
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
},
...blocks('block', 'append'),
/* -------------------------------------------------------------------------- */
/* block:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Block',
name: 'blockId',
type: 'resourceLocator',
default: { mode: 'url', value: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder: 'https://www.notion.so/My-Page-b4eeb113e118403ba450af65ac25f0b9',
validation: [
{
type: 'regex',
properties: {
regex: blockUrlValidationRegexp,
errorMessage: 'Not a valid Notion Block URL',
},
},
],
extractValue: {
type: 'regex',
regex: blockUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Block ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
resource: ['block'],
operation: ['getAll'],
},
hide: {
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
description: 'The Notion Block to get all children from',
},
{
...blockIdRLC,
displayOptions: {
show: {
resource: ['block'],
operation: ['getAll'],
'@version': [{ _cnd: { gte: 2.2 } }],
},
},
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['block'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['block'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Also Fetch Nested Blocks',
name: 'fetchNestedBlocks',
type: 'boolean',
displayOptions: {
show: {
resource: ['block'],
operation: ['getAll'],
},
},
default: false,
},
{
displayName: 'Simplify Output',
name: 'simplifyOutput',
type: 'boolean',
displayOptions: {
show: {
resource: ['block'],
operation: ['getAll'],
},
hide: {
'@version': [1, 2],
},
},
default: true,
},
];
@@ -0,0 +1,603 @@
import type { IDisplayOptions, INodeProperties } from 'n8n-workflow';
import {
databaseUrlExtractionRegexp,
databaseUrlValidationRegexp,
idExtractionRegexp,
idValidationRegexp,
} from '../constants';
const colors = [
{
name: 'Default',
value: 'default',
},
{
name: 'Gray',
value: 'gray',
},
{
name: 'Brown',
value: 'brown',
},
{
name: 'Orange',
value: 'orange',
},
{
name: 'Yellow',
value: 'yellow',
},
{
name: 'Green',
value: 'green',
},
{
name: 'Blue',
value: 'blue',
},
{
name: 'Purple',
value: 'purple',
},
{
name: 'Pink',
value: 'pink',
},
{
name: 'Red',
value: 'red',
},
{
name: 'Gray Background',
value: 'gray_background',
},
{
name: 'Brown Background',
value: 'brown_background',
},
{
name: 'Orange Background',
value: 'orange_background',
},
{
name: 'Yellow Background',
value: 'yellow_background',
},
{
name: 'Green Background',
value: 'green_background',
},
{
name: 'Blue Background',
value: 'blue_background',
},
{
name: 'Purple Background',
value: 'purple_background',
},
{
name: 'Pink Background',
value: 'pink_background',
},
{
name: 'Red Background',
value: 'red_background',
},
];
const annotation: INodeProperties[] = [
{
displayName: 'Annotations',
name: 'annotationUi',
type: 'collection',
placeholder: 'Add Annotation',
default: {},
options: [
{
displayName: 'Bold',
name: 'bold',
type: 'boolean',
default: false,
description: 'Whether the text is bolded',
},
{
displayName: 'Italic',
name: 'italic',
type: 'boolean',
default: false,
description: 'Whether the text is italicized',
},
{
displayName: 'Strikethrough',
name: 'strikethrough',
type: 'boolean',
default: false,
description: 'Whether the text is struck through',
},
{
displayName: 'Underline',
name: 'underline',
type: 'boolean',
default: false,
description: 'Whether the text is underlined',
},
{
displayName: 'Code',
name: 'code',
type: 'boolean',
default: false,
description: 'Whether the text is code style',
},
{
displayName: 'Color',
name: 'color',
type: 'options',
options: colors,
default: '',
description: 'Color of the text',
},
],
description: 'All annotations that apply to this rich text',
},
];
const typeMention: INodeProperties[] = [
{
displayName: 'Type',
name: 'mentionType',
type: 'options',
displayOptions: {
show: {
textType: ['mention'],
},
},
options: [
{
name: 'Database',
value: 'database',
},
{
name: 'Date',
value: 'date',
},
{
name: 'Page',
value: 'page',
},
{
name: 'User',
value: 'user',
},
],
default: '',
description:
'An inline mention of a user, page, database, or date. In the app these are created by typing @ followed by the name of a user, page, database, or a date.',
},
{
displayName: 'User Name or ID',
name: 'user',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
displayOptions: {
show: {
mentionType: ['user'],
},
},
default: '',
description:
'The ID of the user being mentioned. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Page ID',
name: 'page',
type: 'string',
displayOptions: {
show: {
mentionType: ['page'],
},
},
default: '',
description: 'The ID of the page being mentioned',
},
{
displayName: 'Database',
name: 'database',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
modes: [
{
displayName: 'Database',
name: 'list',
type: 'list',
placeholder: 'Select a Database...',
typeOptions: {
searchListMethod: 'getDatabases',
searchable: true,
},
},
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder:
'https://www.notion.so/0fe2f7de558b471eab07e9d871cdf4a9?v=f2d424ba0c404733a3f500c78c881610',
validation: [
{
type: 'regex',
properties: {
regex: databaseUrlValidationRegexp,
errorMessage: 'Not a valid Notion Database URL',
},
},
],
extractValue: {
type: 'regex',
regex: databaseUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Database ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
mentionType: ['database'],
},
},
description: 'The Notion Database being mentioned',
},
{
displayName: 'Range',
name: 'range',
displayOptions: {
show: {
mentionType: ['date'],
},
},
type: 'boolean',
default: false,
description: 'Whether or not you want to define a date range',
},
{
displayName: 'Date',
name: 'date',
displayOptions: {
show: {
mentionType: ['date'],
range: [false],
},
},
type: 'dateTime',
default: '',
description: 'An ISO 8601 format date, with optional time',
},
{
displayName: 'Date Start',
name: 'dateStart',
displayOptions: {
show: {
mentionType: ['date'],
range: [true],
},
},
type: 'dateTime',
default: '',
description: 'An ISO 8601 format date, with optional time',
},
{
displayName: 'Date End',
name: 'dateEnd',
displayOptions: {
show: {
range: [true],
mentionType: ['date'],
},
},
type: 'dateTime',
default: '',
description:
'An ISO 8601 formatted date, with optional time. Represents the end of a date range.',
},
];
const typeEquation: INodeProperties[] = [
{
displayName: 'Expression',
name: 'expression',
type: 'string',
displayOptions: {
show: {
textType: ['equation'],
},
},
default: '',
},
];
const typeText: INodeProperties[] = [
{
displayName: 'Text',
name: 'text',
displayOptions: {
show: {
textType: ['text'],
},
},
type: 'string',
default: '',
description:
"Text content. This field contains the actual content of your text and is probably the field you'll use most often.",
},
{
displayName: 'Is Link',
name: 'isLink',
displayOptions: {
show: {
textType: ['text'],
},
},
type: 'boolean',
default: false,
},
{
displayName: 'Text Link',
name: 'textLink',
displayOptions: {
show: {
textType: ['text'],
isLink: [true],
},
},
type: 'string',
default: '',
description: 'The URL that this link points to',
},
];
export const text = (displayOptions: IDisplayOptions): INodeProperties[] =>
[
{
displayName: 'Text',
name: 'text',
placeholder: 'Add Text',
type: 'fixedCollection',
default: {},
typeOptions: {
multipleValues: true,
},
displayOptions,
options: [
{
name: 'text',
displayName: 'Text',
values: [
{
displayName: 'Type',
name: 'textType',
type: 'options',
options: [
{
name: 'Equation',
value: 'equation',
},
{
name: 'Mention',
value: 'mention',
},
{
name: 'Text',
value: 'text',
},
],
default: 'text',
},
...typeText,
...typeMention,
...typeEquation,
...annotation,
],
},
],
description: 'Rich text in the block',
},
] as INodeProperties[];
const todo = (type: string): INodeProperties[] =>
[
{
displayName: 'Checked',
name: 'checked',
type: 'boolean',
default: false,
displayOptions: {
show: {
type: [type],
},
},
description: 'Whether the to_do is checked or not',
},
] as INodeProperties[];
const title = (type: string): INodeProperties[] =>
[
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
displayOptions: {
show: {
type: [type],
},
},
description: 'Plain text of page title',
},
] as INodeProperties[];
const richText = (displayOptions: IDisplayOptions): INodeProperties[] => [
{
displayName: 'Rich Text',
name: 'richText',
type: 'boolean',
displayOptions,
default: false,
},
];
const textContent = (displayOptions: IDisplayOptions): INodeProperties[] => [
{
displayName: 'Text',
name: 'textContent',
type: 'string',
displayOptions,
default: '',
},
];
const imageBlock = (type: string): INodeProperties[] => [
{
displayName: 'Image URL',
name: 'url',
type: 'string',
displayOptions: {
show: {
type: [type],
},
},
default: '',
description: 'Image file reference',
},
];
const block = (blockType: string): INodeProperties[] => {
const data: INodeProperties[] = [];
switch (blockType) {
case 'to_do':
data.push(...todo(blockType));
data.push(
...richText({
show: {
type: [blockType],
},
}),
);
data.push(
...textContent({
show: {
type: [blockType],
richText: [false],
},
}),
);
data.push(
...text({
show: {
type: [blockType],
richText: [true],
},
}),
);
break;
case 'child_page':
data.push(...title(blockType));
break;
case 'image':
data.push(...imageBlock(blockType));
break;
default:
data.push(
...richText({
show: {
type: [blockType],
},
}),
);
data.push(
...textContent({
show: {
type: [blockType],
richText: [false],
},
}),
);
data.push(
...text({
show: {
type: [blockType],
richText: [true],
},
}),
);
break;
}
return data;
};
export const blocks = (resource: string, operation: string): INodeProperties[] => [
{
displayName: 'Blocks',
name: 'blockUi',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
displayOptions: {
show: {
resource: [resource],
operation: [operation],
},
},
placeholder: 'Add Block',
options: [
{
name: 'blockValues',
displayName: 'Block',
values: [
{
displayName: 'Type Name or ID',
name: 'type',
type: 'options',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getBlockTypes',
},
default: 'paragraph',
},
...block('paragraph'),
...block('heading_1'),
...block('heading_2'),
...block('heading_3'),
...block('toggle'),
...block('to_do'),
...block('child_page'),
...block('bulleted_list_item'),
...block('numbered_list_item'),
...block('image'),
],
},
],
},
];
@@ -0,0 +1,320 @@
import type { INodeProperties } from 'n8n-workflow';
import {
databaseUrlExtractionRegexp,
databaseUrlValidationRegexp,
idExtractionRegexp,
idValidationRegexp,
} from '../constants';
export const databaseOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['database'],
},
hide: {
'@version': [1],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a database',
action: 'Get a database',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many databases',
action: 'Get many databases',
},
{
name: 'Search',
value: 'search',
description: 'Search databases using text search',
action: 'Search a database',
},
],
default: 'get',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
'@version': [1],
resource: ['database'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a database',
action: 'Get a database',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many databases',
action: 'Get many databases',
},
],
default: 'get',
},
];
export const databaseFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* database:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Database',
name: 'databaseId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
required: true,
modes: [
{
displayName: 'Database',
name: 'list',
type: 'list',
placeholder: 'Select a Database...',
typeOptions: {
searchListMethod: 'getDatabases',
searchable: true,
},
},
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder:
'https://www.notion.so/0fe2f7de558b471eab07e9d871cdf4a9?v=f2d424ba0c404733a3f500c78c881610',
validation: [
{
type: 'regex',
properties: {
regex: databaseUrlValidationRegexp,
errorMessage:
'Not a valid Notion Database URL. Hint: use the URL of the database itself, not a page containing it.',
},
},
],
extractValue: {
type: 'regex',
regex: databaseUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Database ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
resource: ['database'],
operation: ['get'],
},
},
description: 'The Notion Database to get',
},
/* -------------------------------------------------------------------------- */
/* database:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['database'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['database'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
resource: ['database'],
operation: ['getAll', 'get'],
},
hide: {
'@version': [1],
},
},
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
/* -------------------------------------------------------------------------- */
/* database:search */
/* -------------------------------------------------------------------------- */
{
displayName: 'Search Text',
name: 'text',
type: 'string',
default: '',
displayOptions: {
show: {
resource: ['database'],
operation: ['search'],
},
},
description: 'The text to search for',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['database'],
operation: ['search'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['database'],
operation: ['search'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
resource: ['database'],
operation: ['search'],
},
},
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
resource: ['database'],
operation: ['search'],
},
},
default: {},
placeholder: 'Add Field',
options: [
{
displayName: 'Sort',
name: 'sort',
placeholder: 'Add Sort',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
displayName: 'Sort',
name: 'sortValue',
values: [
{
displayName: 'Direction',
name: 'direction',
type: 'options',
options: [
{
name: 'Ascending',
value: 'ascending',
},
{
name: 'Descending',
value: 'descending',
},
],
default: 'descending',
description: 'The direction to sort',
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'options',
options: [
{
name: 'Last Edited Time',
value: 'last_edited_time',
},
],
default: 'last_edited_time',
description: 'The name of the timestamp to sort against',
},
],
},
],
},
],
},
];
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,388 @@
export const filters = (conditions: any) => [
{
displayName: 'Property Name or ID',
name: 'key',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getFilterProperties',
loadOptionsDependsOn: ['datatabaseId'],
},
default: '',
description:
'The name of the property to filter by. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Type',
name: 'type',
type: 'hidden',
default: '={{$parameter["&key"].split("|")[1]}}',
},
...conditions,
{
displayName: 'Title',
name: 'titleValue',
type: 'string',
displayOptions: {
show: {
type: ['title'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'Text',
name: 'richTextValue',
type: 'string',
displayOptions: {
show: {
type: ['rich_text'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'Phone Number',
name: 'phoneNumberValue',
type: 'string',
displayOptions: {
show: {
type: ['phone_number'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
description: 'Phone number. No structure is enforced.',
},
{
displayName: 'Option Name or ID',
name: 'multiSelectValue',
type: 'options',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getPropertySelectValues',
},
displayOptions: {
show: {
type: ['multi_select'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: [],
},
{
displayName: 'Option Name or ID',
name: 'selectValue',
type: 'options',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getPropertySelectValues',
},
displayOptions: {
show: {
type: ['select'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'Status Name or ID',
name: 'statusValue',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getPropertySelectValues',
},
displayOptions: {
show: {
type: ['status'],
},
},
default: '',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
},
{
displayName: 'Email',
name: 'emailValue',
type: 'string',
displayOptions: {
show: {
type: ['email'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'URL',
name: 'urlValue',
type: 'string',
displayOptions: {
show: {
type: ['url'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'User Name or ID',
name: 'peopleValue',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
displayOptions: {
show: {
type: ['people'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
description:
'List of users. Multiples can be defined separated by comma. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'User Name or ID',
name: 'createdByValue',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
displayOptions: {
show: {
type: ['created_by'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
description:
'List of users. Multiples can be defined separated by comma. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'User Name or ID',
name: 'lastEditedByValue',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
displayOptions: {
show: {
type: ['last_edited_by'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
description:
'List of users. Multiples can be defined separated by comma. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Relation ID',
name: 'relationValue',
type: 'string',
displayOptions: {
show: {
type: ['relation'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'Checked',
name: 'checkboxValue',
displayOptions: {
show: {
type: ['checkbox'],
},
},
type: 'boolean',
default: false,
description:
'Whether or not the checkbox is checked. <code>true</code> represents checked. <code>false</code> represents unchecked',
},
{
displayName: 'Number',
name: 'numberValue',
displayOptions: {
show: {
type: ['number'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
type: 'number',
default: 0,
description: 'Number value',
},
{
displayName: 'Date',
name: 'date',
displayOptions: {
show: {
type: ['date'],
},
hide: {
condition: [
'is_empty',
'is_not_empty',
'past_week',
'past_month',
'past_year',
'next_week',
'next_month',
'next_year',
],
},
},
type: 'dateTime',
default: '',
description: 'An ISO 8601 format date, with optional time',
},
{
displayName: 'Created Time',
name: 'createdTimeValue',
displayOptions: {
show: {
type: ['created_time'],
},
hide: {
condition: [
'is_empty',
'is_not_empty',
'past_week',
'past_month',
'past_year',
'next_week',
'next_month',
'next_year',
],
},
},
type: 'dateTime',
default: '',
description: 'An ISO 8601 format date, with optional time',
},
{
displayName: 'Last Edited Time',
name: 'lastEditedTime',
displayOptions: {
show: {
type: ['last_edited_time'],
},
hide: {
condition: [
'is_empty',
'is_not_empty',
'past_week',
'past_month',
'past_year',
'next_week',
'next_month',
'next_year',
],
},
},
type: 'dateTime',
default: '',
description: 'An ISO 8601 format date, with optional time',
},
//formula types
{
displayName: 'Number',
name: 'numberValue',
displayOptions: {
show: {
type: ['formula'],
returnType: ['number'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
type: 'number',
default: 0,
description: 'Number value',
},
{
displayName: 'Text',
name: 'textValue',
type: 'string',
displayOptions: {
show: {
type: ['formula'],
returnType: ['text'],
},
hide: {
condition: ['is_empty', 'is_not_empty'],
},
},
default: '',
},
{
displayName: 'Boolean',
name: 'checkboxValue',
displayOptions: {
show: {
type: ['formula'],
returnType: ['checkbox'],
},
},
type: 'boolean',
default: false,
description:
'Whether or not the checkbox is checked. <code>true</code> represents checked. <code>false</code> represents unchecked',
},
{
displayName: 'Date',
name: 'dateValue',
displayOptions: {
show: {
type: ['formula'],
returnType: ['date'],
},
hide: {
condition: [
'is_empty',
'is_not_empty',
'past_week',
'past_month',
'past_year',
'next_week',
'next_month',
'next_year',
],
},
},
type: 'dateTime',
default: '',
description: 'An ISO 8601 format date, with optional time',
},
];
@@ -0,0 +1,491 @@
import type { INodeProperties } from 'n8n-workflow';
import { blocks } from './Blocks';
import {
databasePageUrlExtractionRegexp,
databasePageUrlValidationRegexp,
idExtractionRegexp,
idValidationRegexp,
} from '../constants';
export const pageOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
'@version': [1],
resource: ['page'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a page',
action: 'Create a page',
},
{
name: 'Get',
value: 'get',
description: 'Get a page',
action: 'Get a page',
},
{
name: 'Search',
value: 'search',
description: 'Text search of pages',
action: 'Search a page',
},
],
default: 'create',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['page'],
},
hide: {
'@version': [1],
},
},
options: [
{
name: 'Archive',
value: 'archive',
description: 'Archive a page',
action: 'Archive a page',
},
{
name: 'Create',
value: 'create',
description: 'Create a page',
action: 'Create a page',
},
{
name: 'Search',
value: 'search',
description: 'Text search of pages',
action: 'Search a page',
},
],
default: 'create',
},
];
export const pageFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* page:archive */
/* -------------------------------------------------------------------------- */
{
displayName: 'Page',
name: 'pageId',
type: 'resourceLocator',
default: { mode: 'url', value: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder: 'https://www.notion.so/My-Page-b4eeb113e118403aa450af65ac25f0b9',
validation: [
{
type: 'regex',
properties: {
regex: databasePageUrlValidationRegexp,
errorMessage: 'Not a valid Notion Database Page URL',
},
},
],
extractValue: {
type: 'regex',
regex: databasePageUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Page ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
resource: ['page'],
operation: ['archive'],
},
hide: {
'@version': [1],
},
},
description: 'The Notion Page to archive',
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
resource: ['page'],
operation: ['archive'],
},
hide: {
'@version': [1],
},
},
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
/* -------------------------------------------------------------------------- */
/* page:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Parent Page',
name: 'pageId',
type: 'resourceLocator',
default: { mode: 'url', value: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder: 'https://www.notion.so/My-Page-b4eeb113e118403aa450af65ac25f0b9',
validation: [
{
type: 'regex',
properties: {
regex: databasePageUrlValidationRegexp,
errorMessage: 'Not a valid Notion Database Page URL',
},
},
],
extractValue: {
type: 'regex',
regex: databasePageUrlExtractionRegexp,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'ab1545b247fb49fa92d6f4b49f4d8116',
validation: [
{
type: 'regex',
properties: {
regex: idValidationRegexp,
errorMessage: 'Not a valid Notion Page ID',
},
},
],
extractValue: {
type: 'regex',
regex: idExtractionRegexp,
},
url: '=https://www.notion.so/{{$value.replace(/-/g, "")}}',
},
],
displayOptions: {
show: {
resource: ['page'],
operation: ['create'],
},
},
description: 'The Notion Database Page to create a child page for',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['page'],
operation: ['create'],
},
},
description: 'Page title. Appears at the top of the page and can be found via Quick Find.',
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
resource: ['page'],
operation: ['create'],
},
},
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
...blocks('page', 'create'),
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
resource: ['page'],
operation: ['create'],
},
},
default: {},
placeholder: 'Add option',
options: [
{
displayName: 'Icon Type',
name: 'iconType',
type: 'options',
options: [
{
name: 'Emoji',
value: 'emoji',
description: 'Use an Emoji for the icon',
},
{
name: 'File',
value: 'file',
description: 'Use a file for the icon',
},
],
default: 'emoji',
description: 'The icon type for the page, Either a URL or an Emoji',
},
{
displayName: 'Icon',
name: 'icon',
type: 'string',
default: '',
description: 'Emoji or File URL to use as the icon',
},
],
},
/* -------------------------------------------------------------------------- */
/* page:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Page Link or ID',
name: 'pageId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
'@version': [1],
resource: ['page'],
operation: ['get'],
},
},
description:
"The Page URL from Notion's 'copy link' functionality (or just the ID contained within the URL)",
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
'@version': [1],
resource: ['page'],
operation: ['get'],
},
},
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
/* -------------------------------------------------------------------------- */
/* page:search */
/* -------------------------------------------------------------------------- */
{
displayName: 'Search Text',
name: 'text',
type: 'string',
default: '',
displayOptions: {
show: {
resource: ['page'],
operation: ['search'],
},
},
description: 'The text to search for',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['page'],
operation: ['search'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['page'],
operation: ['search'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Simplify',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
resource: ['page'],
operation: ['search'],
},
},
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
resource: ['page'],
operation: ['search'],
},
},
default: {},
placeholder: 'Add Field',
options: [
{
displayName: 'Filters',
name: 'filter',
placeholder: 'Add Filter',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
displayName: 'Filter',
name: 'filters',
values: [
{
displayName: 'Property',
name: 'property',
type: 'options',
options: [
{
name: 'Object',
value: 'object',
},
],
default: 'object',
description: 'The name of the property to filter by',
},
{
displayName: 'Value',
name: 'value',
type: 'options',
options: [
{
name: 'Database',
value: 'database',
},
{
name: 'Page',
value: 'page',
},
],
default: '',
description: 'The value of the property to filter by',
},
],
},
],
},
{
displayName: 'Sort',
name: 'sort',
placeholder: 'Add Sort',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
displayName: 'Sort',
name: 'sortValue',
values: [
{
displayName: 'Direction',
name: 'direction',
type: 'options',
options: [
{
name: 'Ascending',
value: 'ascending',
},
{
name: 'Descending',
value: 'descending',
},
],
default: 'descending',
description: 'The direction to sort',
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'options',
options: [
{
name: 'Last Edited Time',
value: 'last_edited_time',
},
],
default: 'last_edited_time',
description: 'The name of the timestamp to sort against',
},
],
},
],
},
],
},
];
@@ -0,0 +1,83 @@
import type { INodeProperties } from 'n8n-workflow';
export const userOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['user'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a user',
action: 'Get a user',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many users',
action: 'Get many users',
},
],
default: 'get',
},
];
export const userFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* user:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'User ID',
name: 'userId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['user'],
operation: ['get'],
},
},
},
/* -------------------------------------------------------------------------- */
/* user:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['user'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['user'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
];
@@ -0,0 +1 @@
export * as listSearch from './listSearch';
@@ -0,0 +1,38 @@
import type {
IDataObject,
ILoadOptionsFunctions,
INodeListSearchItems,
INodeListSearchResult,
} from 'n8n-workflow';
import { notionApiRequestAllItems } from '../GenericFunctions';
export async function getDatabases(
this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const returnData: INodeListSearchItems[] = [];
const body: IDataObject = {
page_size: 100,
query: filter,
filter: { property: 'object', value: 'database' },
};
const databases = await notionApiRequestAllItems.call(this, 'results', 'POST', '/search', body);
for (const database of databases) {
returnData.push({
name: database.title[0]?.plain_text || database.id,
value: database.id,
url: database.url,
});
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return { results: returnData };
}
@@ -0,0 +1,206 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions, INode, INodeParameterResourceLocator } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { databasePageUrlExtractionRegexp } from '../shared/constants';
import { extractPageId, formatBlocks, getPageId } from '../shared/GenericFunctions';
describe('Test NotionV2, formatBlocks', () => {
it('should format to_do block', () => {
const blocks = [
{
type: 'to_do',
checked: false,
richText: false,
textContent: 'Testing',
},
];
const result = formatBlocks(blocks);
expect(result).toEqual([
{
object: 'block',
type: 'to_do',
to_do: {
checked: false,
text: [
{
text: {
content: 'Testing',
},
},
],
},
},
]);
});
});
describe('Test Notion', () => {
const baseUrl = 'https://www.notion.so/fake-instance';
const testIds = [
'4eb10d5001254b7faaa831d72d9445aa', // Taken from Notion
'fffb95d3060b80309027eb9c99605ec3', // Taken from user comment
'a6356387779d4df485449a72a408f0d4', // Random v4 UUID
'f4c1217e48f711ef94540242ac120002', // Random v1 UUID
];
describe('extractPageId From URL', () => {
// RLC does some Regex extraction before extractPageId is called
const extractIdFromUrl = (url: string): string => {
const match = url.match(databasePageUrlExtractionRegexp);
return match ? match[1] : url;
};
test('should return the part after "p="', () => {
for (const testId of testIds) {
const page = `${baseUrl}?p=${testId}`;
const result = extractPageId(extractIdFromUrl(page));
expect(result).toBe(testId);
}
});
test('should return the last part after splitting by "-" when URL contains multiple "-"', () => {
for (const testId of testIds) {
const page = `${baseUrl}/some-page-${testId}`;
const result = extractPageId(extractIdFromUrl(page));
expect(result).toBe(testId);
}
});
test('should return the last part after splitting by "-" when URL contains one "-"', () => {
for (const testId of testIds) {
const page = `${baseUrl}/1-${testId}`;
const result = extractPageId(extractIdFromUrl(page));
expect(result).toBe(testId);
}
});
test('should return just the id when there is an instance name', () => {
for (const testId of testIds) {
const page = `${baseUrl}/${testId}`;
const result = extractPageId(extractIdFromUrl(page));
expect(result).toBe(testId);
}
});
test('should return the id when there is no instance name', () => {
for (const testId of testIds) {
const page = `https://www.notion.so/${testId}`;
const result = extractPageId(extractIdFromUrl(page));
expect(result).toBe(testId);
}
});
});
});
describe('Test Notion, getPageId', () => {
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
const id = '3ab5bc794647496dac48feca926813fd';
beforeEach(() => {
mockExecuteFunctions = mock<IExecuteFunctions>();
});
afterEach(() => {
jest.clearAllMocks();
});
it('should return page ID directly when mode is id', () => {
const page = {
mode: 'id',
value: id,
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
const result = getPageId.call(mockExecuteFunctions, 0);
expect(result).toBe(id);
expect(mockExecuteFunctions.getNodeParameter).toHaveBeenCalledWith('pageId', 0, {});
});
it('should extract page ID from URL with p parameter', () => {
const page = {
mode: 'url',
value: `https://www.notion.so/xxxxx?v=xxxxx&p=${id}&pm=s`,
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
const result = getPageId.call(mockExecuteFunctions, 0);
expect(result).toBe(id);
});
it('should extract page ID from URL using regex', () => {
const page = {
mode: 'url',
value: `https://www.notion.so/page-name-${id}`,
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
const result = getPageId.call(mockExecuteFunctions, 0);
expect(result).toBe(id);
});
it('should throw error when page ID cannot be extracted', () => {
const page = {
mode: 'url',
value: 'https://www.notion.so/invalid-url',
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ name: 'Notion', type: 'notion' }));
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(NodeOperationError);
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(
'Could not extract page ID from URL: https://www.notion.so/invalid-url',
);
});
it('should throw error when page value is empty', () => {
const page = {
mode: 'url',
value: '',
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ name: 'Notion', type: 'notion' }));
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(NodeOperationError);
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(
'Could not extract page ID from URL: ',
);
});
it('should throw error when page value is undefined', () => {
const page = {
mode: 'url',
value: undefined,
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ name: 'Notion', type: 'notion' }));
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(NodeOperationError);
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(
'Could not extract page ID from URL: undefined',
);
});
it('should throw error when page value is not a string', () => {
const page = {
mode: 'url',
value: 123 as any,
} as INodeParameterResourceLocator;
mockExecuteFunctions.getNodeParameter.mockReturnValue(page);
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ name: 'Notion', type: 'notion' }));
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(NodeOperationError);
expect(() => getPageId.call(mockExecuteFunctions, 0)).toThrow(
'Could not extract page ID from URL: 123',
);
});
});
@@ -0,0 +1,421 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'list',
results: [
{
object: 'block',
id: '15bfb9cb-4cf0-8162-bd53-fc201157675f',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'paragraph',
paragraph: {
color: 'default',
text: [
{
type: 'text',
text: {
content: 'new text',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'new text',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-8104-8d3f-c8ca8919e791',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'heading_1',
heading_1: {
is_toggleable: false,
color: 'default',
text: [
{
type: 'text',
text: {
content: 'h1',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'h1',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-814d-b4ad-f65a2c558bb9',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'heading_2',
heading_2: {
is_toggleable: false,
color: 'default',
text: [
{
type: 'text',
text: {
content: 'h2',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'h2',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-8135-9e43-f3f8a21e2e86',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'heading_3',
heading_3: {
is_toggleable: false,
color: 'default',
text: [
{
type: 'text',
text: {
content: 'h3',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'h3',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-816b-9457-efba183a957c',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'toggle',
toggle: {
color: 'default',
text: [
{
type: 'text',
text: {
content: 'toggle',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'toggle',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-814f-9928-ed87ed0d9470',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'to_do',
to_do: {
checked: false,
color: 'default',
text: [
{
type: 'text',
text: {
content: 'todo',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'todo',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-812d-95e9-dc8ddc3153dd',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'bulleted_list_item',
bulleted_list_item: {
color: 'default',
text: [
{
type: 'text',
text: {
content: 'bullet 1',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'bullet 1',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-8106-9a25-d3859984ce34',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'numbered_list_item',
numbered_list_item: {
color: 'default',
text: [
{
type: 'text',
text: {
content: 'point 1',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'point 1',
href: null,
},
],
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-81b1-a6f1-eac377c4d163',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T03:40:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: false,
archived: false,
in_trash: false,
type: 'paragraph',
paragraph: {
color: 'default',
text: [
{
type: 'text',
text: {
content: '',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: '',
href: null,
},
],
},
},
],
next_cursor: null,
has_more: false,
request_id: '33358f1b-fc4d-4387-8d95-43c7d03519a5',
};
describe('Test NotionV2, block => append', () => {
nock('https://api.notion.com')
.patch('/v1/blocks/90e03468f8aa457695da02ccad963040/children')
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['append.workflow.json'],
});
});
@@ -0,0 +1,535 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "block",
"blockId": {
"__rl": true,
"value": "https://www.notion.so/Block-Test-88188cbb303e4f44847f27d24bd7ad8e?pvs=4#90e03468f8aa457695da02ccad963040",
"mode": "url"
},
"blockUi": {
"blockValues": [
{
"richText": true,
"text": {
"text": [
{
"text": "new text",
"annotationUi": {}
}
]
}
},
{
"type": "heading_1",
"textContent": "h1"
},
{
"type": "heading_2",
"textContent": "h2"
},
{
"type": "heading_3",
"textContent": "h3"
},
{
"type": "toggle",
"textContent": "toggle"
},
{
"type": "to_do",
"textContent": "todo"
},
{
"type": "bulleted_list_item",
"textContent": "bullet 1"
},
{
"type": "numbered_list_item",
"textContent": "point 1"
},
{}
]
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"object": "list",
"results": [
{
"object": "block",
"id": "15bfb9cb-4cf0-8162-bd53-fc201157675f",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "paragraph",
"paragraph": {
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "new text",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "new text",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-8104-8d3f-c8ca8919e791",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "heading_1",
"heading_1": {
"is_toggleable": false,
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "h1",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "h1",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-814d-b4ad-f65a2c558bb9",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "heading_2",
"heading_2": {
"is_toggleable": false,
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "h2",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "h2",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-8135-9e43-f3f8a21e2e86",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "heading_3",
"heading_3": {
"is_toggleable": false,
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "h3",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "h3",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-816b-9457-efba183a957c",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "toggle",
"toggle": {
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "toggle",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "toggle",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-814f-9928-ed87ed0d9470",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "to_do",
"to_do": {
"checked": false,
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "todo",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "todo",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-812d-95e9-dc8ddc3153dd",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "bulleted_list_item",
"bulleted_list_item": {
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "bullet 1",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "bullet 1",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-8106-9a25-d3859984ce34",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "numbered_list_item",
"numbered_list_item": {
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "point 1",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "point 1",
"href": null
}
]
}
},
{
"object": "block",
"id": "15bfb9cb-4cf0-81b1-a6f1-eac377c4d163",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"created_time": "2024-12-13T03:40:00.000Z",
"last_edited_time": "2024-12-13T03:40:00.000Z",
"created_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "paragraph",
"paragraph": {
"color": "default",
"text": [
{
"type": "text",
"text": {
"content": "",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "",
"href": null
}
]
}
}
],
"next_cursor": null,
"has_more": false,
"request_id": "33358f1b-fc4d-4387-8d95-43c7d03519a5"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "87575721-cf21-472f-a9f0-24aa31c8100f",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,220 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
results: [
{
object: 'block',
id: 'b14bdaaf-b7e9-48c9-a7fa-1b9e1e2092ae',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2023-11-23T10:42:00.000Z',
last_edited_time: '2024-12-13T03:35:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
has_children: true,
archived: false,
in_trash: false,
type: 'toggle',
toggle: {
color: 'default',
text: [
{
type: 'text',
text: {
content: 'Drop down First',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Drop down First',
href: null,
},
],
},
},
{
object: 'block',
id: 'de572f5d-ff5c-4c13-a879-efc20fe47db0',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2023-11-23T10:42:00.000Z',
last_edited_time: '2024-03-11T12:39:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
has_children: true,
archived: false,
in_trash: false,
type: 'bulleted_list_item',
bulleted_list_item: {
color: 'default',
text: [
{
type: 'text',
text: {
content: 'Bullet Point Second',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Bullet Point Second',
href: null,
},
],
},
},
{
object: 'block',
id: 'c98cf981-c967-47f5-9948-4aa1be9ce9d0',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2023-11-24T04:41:00.000Z',
last_edited_time: '2024-12-13T03:35:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
has_children: false,
archived: false,
in_trash: false,
type: 'heading_2',
heading_2: {
is_toggleable: false,
color: 'default',
text: [
{
type: 'text',
text: {
content: 'Hello World',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Hello World',
href: null,
},
],
},
},
{
object: 'block',
id: '527a0555-a486-401e-93a8-19819615c132',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2023-11-23T10:42:00.000Z',
last_edited_time: '2023-11-23T10:43:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
has_children: false,
archived: false,
in_trash: false,
type: 'child_page',
child_page: {
title: 'Page Third',
},
},
{
object: 'block',
id: '15bfb9cb-4cf0-81b1-a6f1-eac377c4d163',
parent: {
type: 'block_id',
block_id: '90e03468-f8aa-4576-95da-02ccad963040',
},
created_time: '2024-12-13T03:40:00.000Z',
last_edited_time: '2024-12-13T06:15:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
has_children: false,
archived: false,
in_trash: false,
type: 'paragraph',
paragraph: {
color: 'default',
text: [
{
type: 'text',
text: {
content: '',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: '',
href: null,
},
],
},
},
],
has_more: false,
};
describe('Test NotionV2, block => getAll', () => {
nock('https://api.notion.com')
.get('/v1/blocks/90e03468f8aa457695da02ccad963040/children')
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['getAll.workflow.json'],
});
});
@@ -0,0 +1,188 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "block",
"operation": "getAll",
"blockId": {
"__rl": true,
"value": "https://www.notion.so/Block-Test-88188cbb303e4f44847f27d24bd7ad8e?pvs=4#90e03468f8aa457695da02ccad963040",
"mode": "url"
},
"returnAll": true
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"object": "block",
"parent_id": "90e03468f8aa457695da02ccad963040",
"id": "b14bdaaf-b7e9-48c9-a7fa-1b9e1e2092ae",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"last_edited_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"has_children": true,
"archived": false,
"in_trash": false,
"type": "toggle",
"root_id": "90e03468f8aa457695da02ccad963040",
"content": "Drop down First"
}
},
{
"json": {
"object": "block",
"parent_id": "90e03468f8aa457695da02ccad963040",
"id": "de572f5d-ff5c-4c13-a879-efc20fe47db0",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"last_edited_by": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324"
},
"has_children": true,
"archived": false,
"in_trash": false,
"type": "bulleted_list_item",
"root_id": "90e03468f8aa457695da02ccad963040",
"content": "Bullet Point Second"
}
},
{
"json": {
"object": "block",
"parent_id": "90e03468f8aa457695da02ccad963040",
"id": "c98cf981-c967-47f5-9948-4aa1be9ce9d0",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"last_edited_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "heading_2",
"root_id": "90e03468f8aa457695da02ccad963040",
"content": "Hello World"
}
},
{
"json": {
"object": "block",
"parent_id": "90e03468f8aa457695da02ccad963040",
"id": "527a0555-a486-401e-93a8-19819615c132",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"last_edited_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "child_page",
"root_id": "90e03468f8aa457695da02ccad963040",
"content": "Page Third"
}
},
{
"json": {
"object": "block",
"parent_id": "90e03468f8aa457695da02ccad963040",
"id": "15bfb9cb-4cf0-81b1-a6f1-eac377c4d163",
"parent": {
"type": "block_id",
"block_id": "90e03468-f8aa-4576-95da-02ccad963040"
},
"last_edited_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"has_children": false,
"archived": false,
"in_trash": false,
"type": "paragraph",
"root_id": "90e03468f8aa457695da02ccad963040",
"content": ""
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "3edb0c05-7e84-4953-a6a5-86c3c6824403",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,75 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'database',
id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
cover: null,
icon: null,
created_time: '2024-11-08T07:59:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_time: '2024-11-08T07:59:00.000Z',
title: [
{
type: 'text',
text: {
content: 'TEST_DB',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'TEST_DB',
href: null,
},
],
description: [],
is_inline: false,
properties: {
Tags: {
id: '%40~Tp',
name: 'Tags',
type: 'multi_select',
multi_select: {
options: [],
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
parent: {
type: 'page_id',
page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a',
},
url: 'https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f',
public_url: null,
archived: false,
in_trash: false,
request_id: 'd22a9046-be0d-4ef5-b551-8691da552d47',
};
describe('Test NotionV2, database => get', () => {
nock('https://api.notion.com')
.get('/v1/databases/138fb9cb-4cf0-804c-8663-d8ecdd5e692f')
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['get.workflow.json'],
});
});
@@ -0,0 +1,90 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "database",
"databaseId": {
"__rl": true,
"value": "138fb9cb-4cf0-804c-8663-d8ecdd5e692f",
"mode": "list",
"cachedResultName": "TEST_DB",
"cachedResultUrl": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f"
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "138fb9cb-4cf0-804c-8663-d8ecdd5e692f",
"name": "TEST_DB",
"url": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "9f59436f-17e7-4c9d-a550-a594bb15618f",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,319 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
results: [
{
object: 'database',
id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
cover: null,
icon: null,
created_time: '2024-11-08T07:59:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_time: '2024-11-08T07:59:00.000Z',
title: [
{
type: 'text',
text: {
content: 'TEST_DB',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'TEST_DB',
href: null,
},
],
description: [],
is_inline: false,
properties: {
Tags: {
id: '%40~Tp',
name: 'Tags',
type: 'multi_select',
multi_select: {
options: [],
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
parent: {
type: 'page_id',
page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a',
},
url: 'https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f',
public_url: null,
archived: false,
in_trash: false,
},
{
object: 'database',
id: 'f7216195-e0d4-46cd-a2d3-587a05baf472',
cover: null,
icon: null,
created_time: '2022-03-07T11:25:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_time: '2024-11-08T07:54:00.000Z',
title: [
{
type: 'text',
text: {
content: 'ListDatabase',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'ListDatabase',
href: null,
},
],
description: [],
is_inline: false,
properties: {
Email: {
id: 'Fitu',
name: 'Email',
type: 'email',
email: {},
},
'Last edited by': {
id: 'XZ~H',
name: 'Last edited by',
type: 'last_edited_by',
last_edited_by: {},
},
Tags: {
id: 'a%7BRG',
name: 'Tags',
type: 'multi_select',
multi_select: {
options: [],
},
},
Created: {
id: 'eqq~',
name: 'Created',
type: 'created_time',
created_time: {},
},
Status: {
id: 'nZEQ',
name: 'Status',
type: 'status',
status: {
options: [
{
id: '70312bf9-84d5-40e6-b1eb-d71798ee556f',
name: 'Not started',
color: 'default',
description: null,
},
{
id: '02a6bb40-3f4b-47d6-818d-6a4129cc6091',
name: 'In progress',
color: 'gray',
description: null,
},
{
id: 'a3c13d01-63b2-4571-8a02-8c1801649af7',
name: 'Done',
color: 'green',
description: null,
},
],
groups: [
{
id: '82e2022c-001d-47ac-a8b8-243ef7fac352',
name: 'To-do',
color: 'gray',
option_ids: ['70312bf9-84d5-40e6-b1eb-d71798ee556f'],
},
{
id: '296000d7-287e-4121-9445-98fa8f7de298',
name: 'In progress',
color: 'blue',
option_ids: ['02a6bb40-3f4b-47d6-818d-6a4129cc6091'],
},
{
id: 'db7689ae-127d-4218-8b3c-306e59e02070',
name: 'Complete',
color: 'green',
option_ids: ['a3c13d01-63b2-4571-8a02-8c1801649af7'],
},
],
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
parent: {
type: 'page_id',
page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a',
},
url: 'https://www.notion.so/f7216195e0d446cda2d3587a05baf472',
public_url: 'https://pleasant-halloumi-63e.notion.site/f7216195e0d446cda2d3587a05baf472',
archived: false,
in_trash: false,
},
{
object: 'database',
id: 'e9c354e3-e506-4c42-83e2-d9c81a083f05',
cover: null,
icon: null,
created_time: '2022-03-07T11:05:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_time: '2023-09-29T08:00:00.000Z',
title: [
{
type: 'text',
text: {
content: 'n8n-trigger',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'n8n-trigger',
href: null,
},
],
description: [],
is_inline: false,
properties: {
Total: {
id: 'A%3DdW',
name: 'Total',
type: 'formula',
formula: {
expression:
'((({{notion:block_property:n%7DI%5E:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}} + {{notion:block_property:MH~%3B:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) - {{notion:block_property:MwMd:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) + {{notion:block_property:fJea:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) - {{notion:block_property:rSrM:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}',
},
},
'Total Incomes': {
id: 'MH~%3B',
name: 'Total Incomes',
type: 'number',
number: {
format: 'number',
},
},
'Total Expenses': {
id: 'MwMd',
name: 'Total Expenses',
type: 'number',
number: {
format: 'number',
},
},
'Created time': {
id: 'Z%3BGM',
name: 'Created time',
type: 'created_time',
created_time: {},
},
'Last edited time': {
id: '%60%5ElG',
name: 'Last edited time',
type: 'last_edited_time',
last_edited_time: {},
},
'Total Transfer-In': {
id: 'fJea',
name: 'Total Transfer-In',
type: 'number',
number: {
format: 'number',
},
},
'Starting Balance': {
id: 'n%7DI%5E',
name: 'Starting Balance',
type: 'number',
number: {
format: 'number',
},
},
'Total Transfer-Out': {
id: 'rSrM',
name: 'Total Transfer-Out',
type: 'number',
number: {
format: 'number',
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
parent: {
type: 'page_id',
page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a',
},
url: 'https://www.notion.so/e9c354e3e5064c4283e2d9c81a083f05',
public_url: null,
archived: false,
in_trash: false,
},
],
has_more: false,
};
describe('Test NotionV2, database => getAll', () => {
nock('https://api.notion.com')
.post('/v1/search', { filter: { property: 'object', value: 'database' } })
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['getAll.workflow.json'],
});
});
@@ -0,0 +1,99 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "database",
"operation": "getAll",
"returnAll": true
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "138fb9cb-4cf0-804c-8663-d8ecdd5e692f",
"name": "TEST_DB",
"url": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f"
}
},
{
"json": {
"id": "f7216195-e0d4-46cd-a2d3-587a05baf472",
"name": "ListDatabase",
"url": "https://www.notion.so/f7216195e0d446cda2d3587a05baf472"
}
},
{
"json": {
"id": "e9c354e3-e506-4c42-83e2-d9c81a083f05",
"name": "n8n-trigger",
"url": "https://www.notion.so/e9c354e3e5064c4283e2d9c81a083f05"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "c9094e39-7195-4d4c-9e08-928415e1902b",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,197 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
results: [
{
object: 'database',
id: 'e9c354e3-e506-4c42-83e2-d9c81a083f05',
cover: null,
icon: null,
created_time: '2022-03-07T11:05:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_time: '2023-09-29T08:00:00.000Z',
title: [
{
type: 'text',
text: {
content: 'n8n-trigger',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'n8n-trigger',
href: null,
},
],
description: [],
is_inline: false,
properties: {
Total: {
id: 'A%3DdW',
name: 'Total',
type: 'formula',
formula: {
expression:
'((({{notion:block_property:n%7DI%5E:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}} + {{notion:block_property:MH~%3B:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) - {{notion:block_property:MwMd:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) + {{notion:block_property:fJea:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) - {{notion:block_property:rSrM:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}',
},
},
'Total Incomes': {
id: 'MH~%3B',
name: 'Total Incomes',
type: 'number',
number: {
format: 'number',
},
},
'Total Expenses': {
id: 'MwMd',
name: 'Total Expenses',
type: 'number',
number: {
format: 'number',
},
},
'Created time': {
id: 'Z%3BGM',
name: 'Created time',
type: 'created_time',
created_time: {},
},
'Last edited time': {
id: '%60%5ElG',
name: 'Last edited time',
type: 'last_edited_time',
last_edited_time: {},
},
'Total Transfer-In': {
id: 'fJea',
name: 'Total Transfer-In',
type: 'number',
number: {
format: 'number',
},
},
'Starting Balance': {
id: 'n%7DI%5E',
name: 'Starting Balance',
type: 'number',
number: {
format: 'number',
},
},
'Total Transfer-Out': {
id: 'rSrM',
name: 'Total Transfer-Out',
type: 'number',
number: {
format: 'number',
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
parent: {
type: 'page_id',
page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a',
},
url: 'https://www.notion.so/e9c354e3e5064c4283e2d9c81a083f05',
public_url: null,
archived: false,
in_trash: false,
},
{
object: 'database',
id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
cover: null,
icon: null,
created_time: '2024-11-08T07:59:00.000Z',
created_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_by: {
object: 'user',
id: '88f72c1a-07ed-4bae-9fa0-231365d813d9',
},
last_edited_time: '2024-11-08T07:59:00.000Z',
title: [
{
type: 'text',
text: {
content: 'TEST_DB',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'TEST_DB',
href: null,
},
],
description: [],
is_inline: false,
properties: {
Tags: {
id: '%40~Tp',
name: 'Tags',
type: 'multi_select',
multi_select: {
options: [],
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
parent: {
type: 'page_id',
page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a',
},
url: 'https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f',
public_url: null,
archived: false,
in_trash: false,
},
],
has_more: false,
};
describe('Test NotionV2, database => search', () => {
nock('https://api.notion.com')
.post('/v1/search', {
filter: { property: 'object', value: 'database' },
query: 't',
sort: { direction: 'ascending', timestamp: 'last_edited_time' },
})
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['search.workflow.json'],
});
});
@@ -0,0 +1,265 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "database",
"operation": "search",
"text": "t",
"limit": 2,
"simple": false,
"options": {
"sort": {
"sortValue": {
"direction": "ascending"
}
}
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"object": "database",
"id": "e9c354e3-e506-4c42-83e2-d9c81a083f05",
"cover": null,
"icon": null,
"created_time": "2022-03-07T11:05:00.000Z",
"created_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"last_edited_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"last_edited_time": "2023-09-29T08:00:00.000Z",
"title": [
{
"type": "text",
"text": {
"content": "n8n-trigger",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "n8n-trigger",
"href": null
}
],
"description": [],
"is_inline": false,
"properties": {
"Total": {
"id": "A%3DdW",
"name": "Total",
"type": "formula",
"formula": {
"expression": "((({{notion:block_property:n%7DI%5E:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}} + {{notion:block_property:MH~%3B:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) - {{notion:block_property:MwMd:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) + {{notion:block_property:fJea:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}) - {{notion:block_property:rSrM:00000000-0000-0000-0000-000000000000:fe91914e-2dc5-4510-82f8-399dd9b2daf8}}"
}
},
"Total Incomes": {
"id": "MH~%3B",
"name": "Total Incomes",
"type": "number",
"number": {
"format": "number"
}
},
"Total Expenses": {
"id": "MwMd",
"name": "Total Expenses",
"type": "number",
"number": {
"format": "number"
}
},
"Created time": {
"id": "Z%3BGM",
"name": "Created time",
"type": "created_time",
"created_time": {}
},
"Last edited time": {
"id": "%60%5ElG",
"name": "Last edited time",
"type": "last_edited_time",
"last_edited_time": {}
},
"Total Transfer-In": {
"id": "fJea",
"name": "Total Transfer-In",
"type": "number",
"number": {
"format": "number"
}
},
"Starting Balance": {
"id": "n%7DI%5E",
"name": "Starting Balance",
"type": "number",
"number": {
"format": "number"
}
},
"Total Transfer-Out": {
"id": "rSrM",
"name": "Total Transfer-Out",
"type": "number",
"number": {
"format": "number"
}
},
"Name": {
"id": "title",
"name": "Name",
"type": "title",
"title": {}
}
},
"parent": {
"type": "page_id",
"page_id": "cc3d2b3c-f31a-4773-ab39-17a60c54567a"
},
"url": "https://www.notion.so/e9c354e3e5064c4283e2d9c81a083f05",
"public_url": null,
"archived": false,
"in_trash": false
}
},
{
"json": {
"object": "database",
"id": "138fb9cb-4cf0-804c-8663-d8ecdd5e692f",
"cover": null,
"icon": null,
"created_time": "2024-11-08T07:59:00.000Z",
"created_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"last_edited_by": {
"object": "user",
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
},
"last_edited_time": "2024-11-08T07:59:00.000Z",
"title": [
{
"type": "text",
"text": {
"content": "TEST_DB",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "TEST_DB",
"href": null
}
],
"description": [],
"is_inline": false,
"properties": {
"Tags": {
"id": "%40~Tp",
"name": "Tags",
"type": "multi_select",
"multi_select": {
"options": []
}
},
"Name": {
"id": "title",
"name": "Name",
"type": "title",
"title": {}
}
},
"parent": {
"type": "page_id",
"page_id": "cc3d2b3c-f31a-4773-ab39-17a60c54567a"
},
"url": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f",
"public_url": null,
"archived": false,
"in_trash": false
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "08dad8e3-abcc-42d1-8ad3-c026644d1280",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,106 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'page',
id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
created_time: '2024-12-13T04:45:00.000Z',
last_edited_time: '2024-12-13T04:45:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😗',
},
parent: {
type: 'database_id',
database_id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
},
archived: false,
in_trash: false,
properties: {
Tags: {
id: '%40~Tp',
type: 'multi_select',
multi_select: [],
},
Name: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'new name 1',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'new name 1',
href: null,
},
],
},
},
url: 'https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3',
public_url: null,
request_id: '1416702d-daa8-4f8d-9be3-c55fe52486b5',
};
describe('Test NotionV2, databasePage => create', () => {
nock('https://api.notion.com')
.get('/v1/databases/138fb9cb-4cf0-804c-8663-d8ecdd5e692f')
.reply(200, {
properties: {
Tags: {
id: '%40~Tp',
name: 'Tags',
type: 'multi_select',
multi_select: {
options: [],
},
},
Name: {
id: 'title',
name: 'Name',
type: 'title',
title: {},
},
},
})
.post('/v1/pages', {
parent: { database_id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f' },
properties: { Name: { title: [{ text: { content: 'new name 1' } }] } },
children: [
{
object: 'block',
type: 'paragraph',
paragraph: { text: [{ text: { content: 'new text' } }] },
},
{
object: 'block',
type: 'toggle',
toggle: { text: [{ text: { content: 'new toggle' } }] },
},
],
icon: { emoji: '😗' },
})
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['create.workflow.json'],
});
});
@@ -0,0 +1,116 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "databasePage",
"databaseId": {
"__rl": true,
"value": "138fb9cb-4cf0-804c-8663-d8ecdd5e692f",
"mode": "list",
"cachedResultName": "TEST_DB",
"cachedResultUrl": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f"
},
"title": "new page",
"propertiesUi": {
"propertyValues": [
{
"key": "Name|title",
"title": "new name 1"
}
]
},
"blockUi": {
"blockValues": [
{
"textContent": "new text"
},
{
"type": "toggle",
"textContent": "new toggle"
}
]
},
"options": {
"iconType": "emoji",
"icon": "😗"
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3",
"name": "new name 1",
"url": "https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3",
"property_tags": [],
"property_name": "new name 1"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "5f4f1112-c094-44b6-bc05-005b54190852",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,71 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'page',
id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
created_time: '2024-12-13T04:45:00.000Z',
last_edited_time: '2024-12-13T04:45:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😗',
},
parent: {
type: 'database_id',
database_id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
},
archived: false,
in_trash: false,
properties: {
Tags: {
id: '%40~Tp',
type: 'multi_select',
multi_select: [],
},
Name: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'new name 1',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'new name 1',
href: null,
},
],
},
},
url: 'https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3',
public_url: null,
request_id: 'c01d3a3e-d9c3-4e48-8d73-077a8503c3ba',
};
describe('Test NotionV2, databasePage => get', () => {
nock('https://api.notion.com')
.get('/v1/pages/15bfb9cb4cf081c7aab4c5855b8cb6c3')
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['get.workflow.json'],
});
});
@@ -0,0 +1,91 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "databasePage",
"operation": "get",
"pageId": {
"__rl": true,
"value": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f?v=7bcc03614eed4c95a9e6168c040e9c58&p=15bfb9cb4cf081c7aab4c5855b8cb6c3&pm=s",
"mode": "url"
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3",
"name": "new name 1",
"url": "https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3",
"property_tags": [],
"property_name": "new name 1"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "68afba7e-c066-458c-984b-c758ef92463c",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,78 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
results: [
{
object: 'page',
id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
created_time: '2024-12-13T04:45:00.000Z',
last_edited_time: '2024-12-13T04:45:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😗',
},
parent: {
type: 'database_id',
database_id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
},
archived: false,
in_trash: false,
properties: {
Tags: {
id: '%40~Tp',
type: 'multi_select',
multi_select: [],
},
Name: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'new name 1',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'new name 1',
href: null,
},
],
},
},
url: 'https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3',
public_url: null,
},
],
has_more: false,
};
describe('Test NotionV2, databasePage => getAll', () => {
nock('https://api.notion.com')
.post('/v1/databases/138fb9cb-4cf0-804c-8663-d8ecdd5e692f/query', {
filter: { or: [{ property: 'Name', title: { contains: 'new' } }] },
sorts: [{ direction: 'ascending', property: 'Name' }],
})
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['getAll.workflow.json'],
});
});
@@ -0,0 +1,114 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"value": "138fb9cb-4cf0-804c-8663-d8ecdd5e692f",
"mode": "list",
"cachedResultName": "TEST_DB",
"cachedResultUrl": "https://www.notion.so/138fb9cb4cf0804c8663d8ecdd5e692f"
},
"returnAll": true,
"filterType": "manual",
"filters": {
"conditions": [
{
"key": "Name|title",
"condition": "contains",
"titleValue": "new"
}
]
},
"options": {
"sort": {
"sortValue": [
{
"key": "Name|title",
"direction": "ascending"
}
]
}
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3",
"name": "new name 1",
"url": "https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3",
"property_tags": [],
"property_name": "new name 1"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "c69706eb-f8e8-4fe0-b456-23e482743d1c",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,73 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'page',
id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
created_time: '2024-12-13T04:45:00.000Z',
last_edited_time: '2024-12-13T05:21:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😗',
},
parent: {
type: 'database_id',
database_id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
},
archived: false,
in_trash: false,
properties: {
Tags: {
id: '%40~Tp',
type: 'multi_select',
multi_select: [],
},
Name: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'Updated Name',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Updated Name',
href: null,
},
],
},
},
url: 'https://www.notion.so/Updated-Name-15bfb9cb4cf081c7aab4c5855b8cb6c3',
public_url: null,
request_id: 'a4683091-f165-4f10-92b4-a629b8b1266e',
};
describe('Test NotionV2, databasePage => update', () => {
nock('https://api.notion.com')
.patch('/v1/pages/15bfb9cb4cf081c7aab4c5855b8cb6c3', {
properties: { Name: { title: [{ text: { content: 'Updated Name' } }] } },
})
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['update.workflow.json'],
});
});
@@ -0,0 +1,100 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "databasePage",
"operation": "update",
"pageId": {
"__rl": true,
"value": "https://www.notion.so/new-name-1-15bfb9cb4cf081c7aab4c5855b8cb6c3",
"mode": "url"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Name|title",
"title": "Updated Name"
}
]
},
"options": {}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3",
"name": "Updated Name",
"url": "https://www.notion.so/Updated-Name-15bfb9cb4cf081c7aab4c5855b8cb6c3",
"property_tags": [],
"property_name": "Updated Name"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "ab4a62f2-5d45-456a-ac7a-97cf84e78815",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,71 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'page',
id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
created_time: '2024-12-13T04:45:00.000Z',
last_edited_time: '2024-12-13T05:52:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😗',
},
parent: {
type: 'database_id',
database_id: '138fb9cb-4cf0-804c-8663-d8ecdd5e692f',
},
archived: true,
in_trash: true,
properties: {
Tags: {
id: '%40~Tp',
type: 'multi_select',
multi_select: [],
},
Name: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'Updated Name',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Updated Name',
href: null,
},
],
},
},
url: 'https://www.notion.so/Updated-Name-15bfb9cb4cf081c7aab4c5855b8cb6c3',
public_url: null,
request_id: 'ce91abaa-261f-43c3-9237-57c0f28af682',
};
describe('Test NotionV2, page => archive', () => {
nock('https://api.notion.com')
.patch('/v1/pages/15bfb9cb4cf081c7aab4c5855b8cb6c3', { archived: true })
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['archive.workflow.json'],
});
});
@@ -0,0 +1,90 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"operation": "archive",
"pageId": {
"__rl": true,
"value": "15bfb9cb4cf081c7aab4c5855b8cb6c3",
"mode": "id"
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3",
"name": "Updated Name",
"url": "https://www.notion.so/Updated-Name-15bfb9cb4cf081c7aab4c5855b8cb6c3",
"property_tags": [],
"property_name": "Updated Name"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "aae2fbb6-0277-449a-9f26-d8d3cf53d2c4",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,82 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'page',
id: '15bfb9cb-4cf0-812b-b4bc-c85cd00727f8',
created_time: '2024-12-13T06:01:00.000Z',
last_edited_time: '2024-12-13T06:01:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😊',
},
parent: {
type: 'page_id',
page_id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
},
archived: false,
in_trash: false,
properties: {
title: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'Child page',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Child page',
href: null,
},
],
},
},
url: 'https://www.notion.so/Child-page-15bfb9cb4cf0812bb4bcc85cd00727f8',
public_url: null,
request_id: 'df28ec00-4361-46af-a3b6-add18c8d1295',
};
describe('Test NotionV2, page => create', () => {
nock('https://api.notion.com')
.post('/v1/pages', {
parent: { page_id: '15bfb9cb4cf081c7aab4c5855b8cb6c3' },
properties: { title: [{ text: { content: 'Child page' } }] },
children: [
{
object: 'block',
type: 'heading_1',
heading_1: { text: [{ type: 'text', text: { content: 'Title' }, annotations: {} }] },
},
{
object: 'block',
type: 'paragraph',
paragraph: { text: [{ text: { content: 'text' } }] },
},
],
icon: { emoji: '😊' },
})
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['create.workflow.json'],
});
});
@@ -0,0 +1,111 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"pageId": {
"__rl": true,
"value": "15bfb9cb4cf081c7aab4c5855b8cb6c3",
"mode": "id",
"__regex": "^([0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12})"
},
"title": "Child page",
"blockUi": {
"blockValues": [
{
"type": "heading_1",
"richText": true,
"text": {
"text": [
{
"text": "Title",
"annotationUi": {}
}
]
}
},
{
"textContent": "text"
}
]
},
"options": {
"icon": "😊"
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-812b-b4bc-c85cd00727f8",
"name": "Child page",
"url": "https://www.notion.so/Child-page-15bfb9cb4cf0812bb4bcc85cd00727f8"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "f8f473ad-4d37-49c4-95f3-187b3a70a6f1",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,74 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
results: [
{
object: 'page',
id: '15bfb9cb-4cf0-812b-b4bc-c85cd00727f8',
created_time: '2024-12-13T06:01:00.000Z',
last_edited_time: '2024-12-13T06:01:00.000Z',
created_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
last_edited_by: {
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
},
cover: null,
icon: {
type: 'emoji',
emoji: '😊',
},
parent: {
type: 'page_id',
page_id: '15bfb9cb-4cf0-81c7-aab4-c5855b8cb6c3',
},
archived: false,
in_trash: false,
properties: {
title: {
id: 'title',
type: 'title',
title: [
{
type: 'text',
text: {
content: 'Child page',
link: null,
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default',
},
plain_text: 'Child page',
href: null,
},
],
},
},
url: 'https://www.notion.so/Child-page-15bfb9cb4cf0812bb4bcc85cd00727f8',
public_url: null,
},
],
has_more: false,
};
describe('Test NotionV2, page => search', () => {
nock('https://api.notion.com')
.post('/v1/search', {
query: 'child',
filter: { property: 'object', value: 'page' },
sort: { direction: 'ascending', timestamp: 'last_edited_time' },
})
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['search.workflow.json'],
});
});
@@ -0,0 +1,97 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"operation": "search",
"text": "child",
"returnAll": true,
"options": {
"filter": {
"filters": {
"value": "page"
}
},
"sort": {
"sortValue": {
"direction": "ascending"
}
}
}
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "15bfb9cb-4cf0-812b-b4bc-c85cd00727f8",
"name": "Child page",
"url": "https://www.notion.so/Child-page-15bfb9cb4cf0812bb4bcc85cd00727f8"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "75e271ec-4d76-4094-b283-9a3f61a0c111",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,22 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
object: 'user',
id: '34a945c6-de97-4efc-90d6-6d7cc14a6583',
name: 'second',
avatar_url: null,
type: 'bot',
bot: {},
request_id: 'ad2a00c0-fa6a-4a14-bf9a-68e1715b51a1',
};
describe('Test NotionV2, user => get', () => {
nock('https://api.notion.com')
.get('/v1/users/34a945c6-de97-4efc-90d6-6d7cc14a6583')
.reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['get.workflow.json'],
});
});
@@ -0,0 +1,88 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "user",
"userId": "34a945c6-de97-4efc-90d6-6d7cc14a6583"
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"object": "user",
"id": "34a945c6-de97-4efc-90d6-6d7cc14a6583",
"name": "second",
"avatar_url": null,
"type": "bot",
"bot": {},
"request_id": "ad2a00c0-fa6a-4a14-bf9a-68e1715b51a1"
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d654cac7-21b8-4d6e-99ce-c5c980d9f49d",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,47 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const API_RESPONSE = {
results: [
{
object: 'user',
id: 'f215e49c-4677-40c0-9adc-87440d341324',
name: 'n8n-test',
avatar_url: null,
type: 'bot',
bot: {
owner: {
type: 'workspace',
workspace: true,
},
workspace_name: "Michael Kret's Notion",
},
},
{
object: 'user',
id: '34a945c6-de97-4efc-90d6-6d7cc14a6583',
name: 'second',
avatar_url: null,
type: 'bot',
bot: {},
},
{
object: 'user',
id: '2598a5de-49b3-4acd-adad-20f6b18c9fbe',
name: 'DryMerge',
avatar_url:
'https://s3-us-west-2.amazonaws.com/public.notion-static.com/e67863a3-a867-4355-a602-c9830dbb1828/Primary_(recommended).jpg',
type: 'bot',
bot: {},
},
],
has_more: false,
};
describe('Test NotionV2, user => getAll', () => {
nock('https://api.notion.com').get('/v1/users').reply(200, API_RESPONSE);
new NodeTestHarness().setupTests({
workflowFiles: ['getAll.workflow.json'],
});
});
@@ -0,0 +1,104 @@
{
"name": "tests notion",
"nodes": [
{
"parameters": {},
"id": "4260fdbd-e92f-4712-8114-38b85f8289ea",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [820, 360]
},
{
"parameters": {
"resource": "user",
"operation": "getAll",
"limit": 2
},
"id": "5ab80e6a-c9c4-4cc5-9332-2fc7a3f8ae24",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [1040, 360],
"credentials": {
"notionApi": {
"id": "CiZXWkDmjiZzpcL1",
"name": "Notion account"
}
}
},
{
"parameters": {},
"id": "a664f506-72a5-4e50-80b4-97ed2e6eb334",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1260, 360]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"object": "user",
"id": "f215e49c-4677-40c0-9adc-87440d341324",
"name": "n8n-test",
"avatar_url": null,
"type": "bot",
"bot": {
"owner": {
"type": "workspace",
"workspace": true
},
"workspace_name": "Michael Kret's Notion"
}
}
},
{
"json": {
"object": "user",
"id": "34a945c6-de97-4efc-90d6-6d7cc14a6583",
"name": "second",
"avatar_url": null,
"type": "bot",
"bot": {}
}
}
]
},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "702bd2aa-53d0-4c82-bbc2-a95a153953b0",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "Ucav6QC99JNMCkd3",
"tags": []
}
@@ -0,0 +1,626 @@
import moment from 'moment-timezone';
import type {
IExecuteFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import { versionDescription } from './VersionDescription';
import type { SortData } from '../shared/GenericFunctions';
import {
extractDatabaseId,
extractDatabaseMentionRLC,
extractPageId,
formatBlocks,
formatTitle,
getBlockTypesOptions,
mapFilters,
mapProperties,
mapSorting,
notionApiRequest,
notionApiRequestAllItems,
simplifyObjects,
} from '../shared/GenericFunctions';
import { listSearch } from '../shared/methods';
export class NotionV1 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
...versionDescription,
};
}
methods = {
listSearch,
loadOptions: {
async getDatabaseProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const databaseId = this.getCurrentNodeParameter('databaseId', {
extractValue: true,
}) as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
for (const key of Object.keys(properties as IDataObject)) {
//remove parameters that cannot be set from the API.
if (
![
'created_time',
'last_edited_time',
'created_by',
'last_edited_by',
'formula',
'files',
'rollup',
].includes(properties[key].type as string)
) {
returnData.push({
name: `${key} - (${properties[key].type})`,
value: `${key}|${properties[key].type}`,
});
}
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return returnData;
},
async getFilterProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const databaseId = this.getCurrentNodeParameter('databaseId', {
extractValue: true,
}) as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
for (const key of Object.keys(properties as IDataObject)) {
returnData.push({
name: `${key} - (${properties[key].type})`,
value: `${key}|${properties[key].type}`,
});
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return returnData;
},
async getBlockTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
return getBlockTypesOptions();
},
async getPropertySelectValues(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const [name, type] = (this.getCurrentNodeParameter('&key') as string).split('|');
const databaseId = this.getCurrentNodeParameter('databaseId', {
extractValue: true,
}) as string;
const resource = this.getCurrentNodeParameter('resource') as string;
const operation = this.getCurrentNodeParameter('operation') as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
if (resource === 'databasePage') {
if (['multi_select', 'select'].includes(type) && operation === 'getAll') {
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.name,
}));
} else if (['multi_select'].includes(type) && ['create', 'update'].includes(operation)) {
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.name,
}));
}
}
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.id,
}));
},
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const users = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
for (const user of users) {
if (user.type === 'person') {
returnData.push({
name: user.name,
value: user.id,
});
}
}
return returnData;
},
async getDatabaseIdFromPage(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const pageId = extractPageId(
this.getCurrentNodeParameter('pageId', { extractValue: true }) as string,
);
const {
parent: { database_id: databaseId },
} = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
for (const key of Object.keys(properties as IDataObject)) {
//remove parameters that cannot be set from the API.
if (
![
'created_time',
'last_edited_time',
'created_by',
'last_edited_by',
'formula',
'files',
].includes(properties[key].type as string)
) {
returnData.push({
name: `${key} - (${properties[key].type})`,
value: `${key}|${properties[key].type}`,
});
}
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return returnData;
},
async getDatabaseOptionsFromPage(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const pageId = extractPageId(
this.getCurrentNodeParameter('pageId', { extractValue: true }) as string,
);
const [name, type] = (this.getCurrentNodeParameter('&key') as string).split('|');
const {
parent: { database_id: databaseId },
} = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.id,
}));
},
// Get all the timezones to display them to user so that they can
// select them easily
async getTimezones(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
for (const timezone of moment.tz.names()) {
const timezoneName = timezone;
const timezoneId = timezone;
returnData.push({
name: timezoneName,
value: timezoneId,
});
}
returnData.unshift({
name: 'Default',
value: 'default',
description: 'Timezone set in n8n',
});
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const length = items.length;
let responseData;
const qs: IDataObject = {};
const timezone = this.getTimezone();
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
if (resource === 'block') {
if (operation === 'append') {
for (let i = 0; i < length; i++) {
const blockId = extractPageId(
this.getNodeParameter('blockId', i, '', { extractValue: true }) as string,
);
const blockValues = this.getNodeParameter('blockUi.blockValues', i, []) as IDataObject[];
extractDatabaseMentionRLC(blockValues);
const body: IDataObject = {
children: formatBlocks(blockValues),
};
const block = await notionApiRequest.call(
this,
'PATCH',
`/blocks/${blockId}/children`,
body,
);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(block as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'getAll') {
for (let i = 0; i < length; i++) {
const blockId = extractPageId(
this.getNodeParameter('blockId', i, '', { extractValue: true }) as string,
);
const returnAll = this.getNodeParameter('returnAll', i);
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'GET',
`/blocks/${blockId}/children`,
{},
);
} else {
qs.page_size = this.getNodeParameter('limit', i);
responseData = await notionApiRequest.call(
this,
'GET',
`/blocks/${blockId}/children`,
{},
qs,
);
responseData = responseData.results;
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
}
if (resource === 'database') {
if (operation === 'get') {
for (let i = 0; i < length; i++) {
const databaseId = extractDatabaseId(
this.getNodeParameter('databaseId', i, '', { extractValue: true }) as string,
);
responseData = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'getAll') {
for (let i = 0; i < length; i++) {
const body: IDataObject = {
filter: { property: 'object', value: 'database' },
};
const returnAll = this.getNodeParameter('returnAll', i);
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
} else {
body.page_size = this.getNodeParameter('limit', i);
responseData = await notionApiRequest.call(this, 'POST', '/search', body);
responseData = responseData.results;
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
}
if (resource === 'databasePage') {
if (operation === 'create') {
for (let i = 0; i < length; i++) {
const simple = this.getNodeParameter('simple', i) as boolean;
const body: { [key: string]: any } = {
parent: {},
properties: {},
};
body.parent.database_id = this.getNodeParameter('databaseId', i, '', {
extractValue: true,
}) as string;
const properties = this.getNodeParameter(
'propertiesUi.propertyValues',
i,
[],
) as IDataObject[];
if (properties.length !== 0) {
body.properties = mapProperties.call(this, properties, timezone) as IDataObject;
}
const blockValues = this.getNodeParameter('blockUi.blockValues', i, []) as IDataObject[];
extractDatabaseMentionRLC(blockValues);
body.children = formatBlocks(blockValues);
responseData = await notionApiRequest.call(this, 'POST', '/pages', body);
if (simple) {
responseData = simplifyObjects(responseData, false, 1);
}
const options = this.getNodeParameter('options', i);
if (options.icon) {
if (options.iconType && options.iconType === 'file') {
body.icon = { external: { url: options.icon } };
} else {
body.icon = { emoji: options.icon };
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'getAll') {
for (let i = 0; i < length; i++) {
const simple = this.getNodeParameter('simple', 0) as boolean;
const databaseId = this.getNodeParameter('databaseId', i, '', {
extractValue: true,
}) as string;
const returnAll = this.getNodeParameter('returnAll', i);
const filters = this.getNodeParameter('options.filter', i, {}) as IDataObject;
const sort = this.getNodeParameter('options.sort.sortValue', i, []) as SortData[];
const body: IDataObject = {
filter: {},
};
if (filters.singleCondition) {
body.filter = mapFilters([filters.singleCondition] as IDataObject[], timezone);
}
if (filters.multipleCondition) {
const { or, and } = (filters.multipleCondition as IDataObject).condition as IDataObject;
if (Array.isArray(or) && or.length !== 0) {
Object.assign(body.filter!, {
or: (or as IDataObject[]).map((data) => mapFilters([data], timezone)),
});
}
if (Array.isArray(and) && and.length !== 0) {
Object.assign(body.filter!, {
and: (and as IDataObject[]).map((data) => mapFilters([data], timezone)),
});
}
}
if (!Object.keys(body.filter as IDataObject).length) {
delete body.filter;
}
if (sort) {
body.sorts = mapSorting(sort);
}
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
`/databases/${databaseId}/query`,
body,
{},
);
} else {
body.page_size = this.getNodeParameter('limit', i);
responseData = await notionApiRequest.call(
this,
'POST',
`/databases/${databaseId}/query`,
body,
qs,
);
responseData = responseData.results;
}
if (simple) {
responseData = simplifyObjects(responseData, false, 1);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'update') {
for (let i = 0; i < length; i++) {
const pageId = extractPageId(
this.getNodeParameter('pageId', i, '', { extractValue: true }) as string,
);
const simple = this.getNodeParameter('simple', i) as boolean;
const properties = this.getNodeParameter(
'propertiesUi.propertyValues',
i,
[],
) as IDataObject[];
const body: { [key: string]: any } = {
properties: {},
};
if (properties.length !== 0) {
body.properties = mapProperties.call(this, properties, timezone) as IDataObject;
}
responseData = await notionApiRequest.call(this, 'PATCH', `/pages/${pageId}`, body);
if (simple) {
responseData = simplifyObjects(responseData, false, 1);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
}
if (resource === 'user') {
if (operation === 'get') {
for (let i = 0; i < length; i++) {
const userId = this.getNodeParameter('userId', i) as string;
responseData = await notionApiRequest.call(this, 'GET', `/users/${userId}`);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'getAll') {
for (let i = 0; i < length; i++) {
const returnAll = this.getNodeParameter('returnAll', i);
if (returnAll) {
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
responseData = responseData.splice(0, qs.limit);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
}
if (resource === 'page') {
if (operation === 'create') {
for (let i = 0; i < length; i++) {
const simple = this.getNodeParameter('simple', i) as boolean;
const body: { [key: string]: any } = {
parent: {},
properties: {},
};
body.parent.page_id = extractPageId(
this.getNodeParameter('pageId', i, '', { extractValue: true }) as string,
);
body.properties = formatTitle(this.getNodeParameter('title', i) as string);
const blockValues = this.getNodeParameter('blockUi.blockValues', i, []) as IDataObject[];
extractDatabaseMentionRLC(blockValues);
body.children = formatBlocks(blockValues);
responseData = await notionApiRequest.call(this, 'POST', '/pages', body);
if (simple) {
responseData = simplifyObjects(responseData, false, 1);
}
const options = this.getNodeParameter('options', i);
if (options.icon) {
if (options.iconType && options.iconType === 'file') {
body.icon = { external: { url: options.icon } };
} else {
body.icon = { emoji: options.icon };
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'get') {
for (let i = 0; i < length; i++) {
const pageId = extractPageId(this.getNodeParameter('pageId', i) as string);
const simple = this.getNodeParameter('simple', i) as boolean;
responseData = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
if (simple) {
responseData = simplifyObjects(responseData, false, 1);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
if (operation === 'search') {
for (let i = 0; i < length; i++) {
const text = this.getNodeParameter('text', i) as string;
const options = this.getNodeParameter('options', i);
const returnAll = this.getNodeParameter('returnAll', i);
const simple = this.getNodeParameter('simple', i) as boolean;
const body: IDataObject = {};
if (text) {
body.query = text;
}
if (options.filter) {
const filter = ((options.filter as IDataObject)?.filters as IDataObject[]) || [];
body.filter = filter;
}
if (options.sort) {
const sort = ((options.sort as IDataObject)?.sortValue as IDataObject) || {};
body.sort = sort;
}
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
responseData = responseData.splice(0, qs.limit);
}
if (simple) {
responseData = simplifyObjects(responseData, false, 1);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
}
}
return [returnData];
}
}
@@ -0,0 +1,115 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
import { blockFields, blockOperations } from '../shared/descriptions/BlockDescription';
import { databaseFields, databaseOperations } from '../shared/descriptions/DatabaseDescription';
import {
databasePageFields,
databasePageOperations,
} from '../shared/descriptions/DatabasePageDescription';
import { pageFields, pageOperations } from '../shared/descriptions/PageDescription';
import { userFields, userOperations } from '../shared/descriptions/UserDescription';
export const versionDescription: INodeTypeDescription = {
displayName: 'Notion',
name: 'notion',
icon: 'file:notion.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Notion API',
defaults: {
name: 'Notion',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'notionApi',
required: true,
// displayOptions: {
// show: {
// authentication: [
// 'apiKey',
// ],
// },
// },
},
// {
// name: 'notionOAuth2Api',
// required: true,
// displayOptions: {
// show: {
// authentication: [
// 'oAuth2',
// ],
// },
// },
// },
],
properties: [
// {
// displayName: 'Authentication',
// name: 'authentication',
// type: 'options',
// options: [
// {
// name: 'API Key',
// value: 'apiKey',
// },
// {
// name: 'OAuth2',
// value: 'oAuth2',
// },
// ],
// default: 'apiKey',
// description: 'The resource to operate on.',
// },
{
displayName:
'In Notion, make sure to <a href="https://www.notion.so/help/add-and-manage-connections-with-the-api" target="_blank">add your connection</a> to the pages you want to access.',
name: 'notionNotice',
type: 'notice',
default: '',
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Block',
value: 'block',
},
{
name: 'Database',
value: 'database',
},
{
name: 'Database Page',
value: 'databasePage',
},
{
name: 'Page',
value: 'page',
},
{
name: 'User',
value: 'user',
},
],
default: 'page',
},
...blockOperations,
...blockFields,
...databaseOperations,
...databaseFields,
...databasePageOperations,
...databasePageFields,
...pageOperations,
...pageFields,
...userOperations,
...userFields,
],
};
@@ -0,0 +1,776 @@
import type {
IExecuteFunctions,
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import { jsonParse, NodeApiError } from 'n8n-workflow';
import { loadOptions } from './methods';
import { versionDescription } from './VersionDescription';
import type { SortData, FileRecord } from '../shared/GenericFunctions';
import {
downloadFiles,
extractBlockId,
extractDatabaseId,
extractDatabaseMentionRLC,
getPageId,
formatBlocks,
formatTitle,
mapFilters,
mapProperties,
mapSorting,
notionApiRequest,
notionApiRequestAllItems,
notionApiRequestGetBlockChildrens,
prepareNotionError,
simplifyBlocksOutput,
simplifyObjects,
validateJSON,
} from '../shared/GenericFunctions';
import { listSearch } from '../shared/methods';
export class NotionV2 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
...versionDescription,
};
}
methods = { listSearch, loadOptions };
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const nodeVersion = this.getNode().typeVersion;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
const itemsLength = items.length;
const timezone = this.getTimezone();
const qs: IDataObject = {};
let returnData: INodeExecutionData[] = [];
let responseData;
let download = false;
if (resource === 'block') {
if (operation === 'append') {
for (let i = 0; i < itemsLength; i++) {
try {
const blockId = extractBlockId.call(this, nodeVersion, i);
const blockValues = this.getNodeParameter(
'blockUi.blockValues',
i,
[],
) as IDataObject[];
extractDatabaseMentionRLC(blockValues);
const body: IDataObject = {
children: formatBlocks(blockValues),
};
const block = await notionApiRequest.call(
this,
'PATCH',
`/blocks/${blockId}/children`,
body,
);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(block as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'getAll') {
for (let i = 0; i < itemsLength; i++) {
try {
const blockId = extractBlockId.call(this, nodeVersion, i);
const returnAll = this.getNodeParameter('returnAll', i);
const fetchNestedBlocks = this.getNodeParameter('fetchNestedBlocks', i) as boolean;
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'GET',
`/blocks/${blockId}/children`,
{},
);
if (fetchNestedBlocks) {
responseData = await notionApiRequestGetBlockChildrens.call(this, responseData);
}
} else {
const limit = this.getNodeParameter('limit', i);
qs.page_size = limit;
responseData = await notionApiRequest.call(
this,
'GET',
`/blocks/${blockId}/children`,
{},
qs,
);
const results = responseData.results;
if (fetchNestedBlocks) {
responseData = await notionApiRequestGetBlockChildrens.call(
this,
results,
[],
limit,
);
} else {
responseData = results;
}
}
responseData = responseData.map((_data: IDataObject) => ({
object: _data.object,
parent_id: blockId,
..._data,
}));
if (nodeVersion > 2) {
const simplifyOutput = this.getNodeParameter('simplifyOutput', i) as boolean;
if (simplifyOutput) {
responseData = simplifyBlocksOutput(responseData, blockId);
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
}
if (resource === 'database') {
if (operation === 'get') {
const simple = this.getNodeParameter('simple', 0) as boolean;
for (let i = 0; i < itemsLength; i++) {
try {
const databaseId = extractDatabaseId(
this.getNodeParameter('databaseId', i, '', { extractValue: true }) as string,
);
responseData = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
if (simple) {
responseData = simplifyObjects(responseData, download)[0];
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'getAll') {
const simple = this.getNodeParameter('simple', 0) as boolean;
for (let i = 0; i < itemsLength; i++) {
try {
const body: IDataObject = {
filter: { property: 'object', value: 'database' },
};
const returnAll = this.getNodeParameter('returnAll', i);
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
} else {
body.page_size = this.getNodeParameter('limit', i);
responseData = await notionApiRequest.call(this, 'POST', '/search', body);
responseData = responseData.results;
}
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'search') {
for (let i = 0; i < itemsLength; i++) {
try {
const text = this.getNodeParameter('text', i) as string;
const options = this.getNodeParameter('options', i);
const returnAll = this.getNodeParameter('returnAll', i);
const simple = this.getNodeParameter('simple', i) as boolean;
const body: IDataObject = {
filter: {
property: 'object',
value: 'database',
},
};
if (text) {
body.query = text;
}
if (options.sort) {
const sort = ((options.sort as IDataObject)?.sortValue as IDataObject) || {};
body.sort = sort;
}
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
responseData = responseData.splice(0, qs.limit);
}
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
}
if (resource === 'databasePage') {
if (operation === 'create') {
const databaseId = this.getNodeParameter('databaseId', 0, '', {
extractValue: true,
}) as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
let titleKey = '';
for (const key of Object.keys(properties as IDataObject)) {
if (properties[key].type === 'title') {
titleKey = key;
}
}
for (let i = 0; i < itemsLength; i++) {
try {
const title = this.getNodeParameter('title', i) as string;
const simple = this.getNodeParameter('simple', i) as boolean;
const body: { [key: string]: any } = {
parent: {},
properties: {},
};
if (title !== '') {
body.properties[titleKey] = {
title: [
{
text: {
content: title,
},
},
],
};
}
body.parent.database_id = this.getNodeParameter('databaseId', i, '', {
extractValue: true,
}) as string;
const propertiesValues = this.getNodeParameter(
'propertiesUi.propertyValues',
i,
[],
) as IDataObject[];
if (propertiesValues.length !== 0) {
body.properties = Object.assign(
body.properties,
mapProperties.call(this, propertiesValues, timezone, 2) as IDataObject,
);
}
const blockValues = this.getNodeParameter(
'blockUi.blockValues',
i,
[],
) as IDataObject[];
extractDatabaseMentionRLC(blockValues);
body.children = formatBlocks(blockValues);
const options = this.getNodeParameter('options', i);
if (options.icon) {
if (options.iconType && options.iconType === 'file') {
body.icon = { external: { url: options.icon } };
} else {
body.icon = { emoji: options.icon };
}
}
responseData = await notionApiRequest.call(this, 'POST', '/pages', body);
if (simple) {
responseData = simplifyObjects(responseData);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'get') {
for (let i = 0; i < itemsLength; i++) {
try {
const pageId = getPageId.call(this, i);
const simple = this.getNodeParameter('simple', i) as boolean;
responseData = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'getAll') {
for (let i = 0; i < itemsLength; i++) {
try {
download = this.getNodeParameter('options.downloadFiles', 0, false) as boolean;
const simple = this.getNodeParameter('simple', 0) as boolean;
const databaseId = this.getNodeParameter('databaseId', i, '', {
extractValue: true,
}) as string;
const returnAll = this.getNodeParameter('returnAll', i);
const filterType = this.getNodeParameter('filterType', 0) as string;
const conditions = this.getNodeParameter('filters.conditions', i, []) as IDataObject[];
const sort = this.getNodeParameter('options.sort.sortValue', i, []) as IDataObject[];
const body: IDataObject = {
filter: {},
};
if (filterType === 'manual') {
const matchType = this.getNodeParameter('matchType', 0) as string;
if (matchType === 'anyFilter') {
Object.assign(body.filter!, {
or: conditions.map((data) => mapFilters([data], timezone)),
});
} else if (matchType === 'allFilters') {
Object.assign(body.filter!, {
and: conditions.map((data) => mapFilters([data], timezone)),
});
}
} else if (filterType === 'json') {
const filterJson = this.getNodeParameter('filterJson', i) as string;
if (validateJSON(filterJson) !== undefined) {
body.filter = jsonParse(filterJson);
} else {
throw new NodeApiError(
this.getNode(),
{
message: 'Filters (JSON) must be a valid json',
},
{ itemIndex: i },
);
}
}
if (!Object.keys(body.filter as IDataObject).length) {
delete body.filter;
}
if (sort) {
body.sorts = mapSorting(sort as SortData[]);
}
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
`/databases/${databaseId}/query`,
body,
{},
);
} else {
body.page_size = this.getNodeParameter('limit', i);
responseData = await notionApiRequest.call(
this,
'POST',
`/databases/${databaseId}/query`,
body,
qs,
);
responseData = responseData.results;
}
if (download) {
responseData = await downloadFiles.call(this, responseData as FileRecord[], [
{ item: i },
]);
}
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'update') {
for (let i = 0; i < itemsLength; i++) {
try {
const pageId = getPageId.call(this, i);
const simple = this.getNodeParameter('simple', i) as boolean;
const properties = this.getNodeParameter(
'propertiesUi.propertyValues',
i,
[],
) as IDataObject[];
const body: { [key: string]: any } = {
properties: {},
};
if (properties.length !== 0) {
body.properties = mapProperties.call(this, properties, timezone, 2) as IDataObject;
}
const options = this.getNodeParameter('options', i);
if (options.icon) {
if (options.iconType && options.iconType === 'file') {
body.icon = { type: 'external', external: { url: options.icon } };
} else {
body.icon = { type: 'emoji', emoji: options.icon };
}
}
responseData = await notionApiRequest.call(this, 'PATCH', `/pages/${pageId}`, body);
if (simple) {
responseData = simplifyObjects(responseData, false);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
}
if (resource === 'user') {
if (operation === 'get') {
for (let i = 0; i < itemsLength; i++) {
try {
const userId = this.getNodeParameter('userId', i) as string;
responseData = await notionApiRequest.call(this, 'GET', `/users/${userId}`);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'getAll') {
for (let i = 0; i < itemsLength; i++) {
try {
const returnAll = this.getNodeParameter('returnAll', i);
if (returnAll) {
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
responseData = responseData.splice(0, qs.limit);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
}
if (resource === 'page') {
if (operation === 'archive') {
for (let i = 0; i < itemsLength; i++) {
try {
const pageId = getPageId.call(this, i);
const simple = this.getNodeParameter('simple', i) as boolean;
responseData = await notionApiRequest.call(this, 'PATCH', `/pages/${pageId}`, {
archived: true,
});
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'create') {
for (let i = 0; i < itemsLength; i++) {
try {
const simple = this.getNodeParameter('simple', i) as boolean;
const body: { [key: string]: any } = {
parent: {},
properties: {},
};
body.parent.page_id = getPageId.call(this, i);
body.properties = formatTitle(this.getNodeParameter('title', i) as string);
const blockValues = this.getNodeParameter(
'blockUi.blockValues',
i,
[],
) as IDataObject[];
extractDatabaseMentionRLC(blockValues);
body.children = formatBlocks(blockValues);
const options = this.getNodeParameter('options', i);
if (options.icon) {
if (options.iconType && options.iconType === 'file') {
body.icon = { external: { url: options.icon } };
} else {
body.icon = { emoji: options.icon };
}
}
responseData = await notionApiRequest.call(this, 'POST', '/pages', body);
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
if (operation === 'search') {
for (let i = 0; i < itemsLength; i++) {
try {
const text = this.getNodeParameter('text', i) as string;
const options = this.getNodeParameter('options', i);
const returnAll = this.getNodeParameter('returnAll', i);
const simple = this.getNodeParameter('simple', i) as boolean;
const body: IDataObject = {};
if (text) {
body.query = text;
}
if (options.filter) {
const filter = ((options.filter as IDataObject)?.filters as IDataObject[]) || [];
body.filter = filter;
}
if (options.sort) {
const sort = ((options.sort as IDataObject)?.sortValue as IDataObject) || {};
body.sort = sort;
}
if (returnAll) {
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await notionApiRequestAllItems.call(
this,
'results',
'POST',
'/search',
body,
);
responseData = responseData.splice(0, qs.limit);
}
if (simple) {
responseData = simplifyObjects(responseData, download);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData = returnData.concat(executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
} else {
throw prepareNotionError(this.getNode(), error, i);
}
}
}
}
}
return [returnData];
}
}
@@ -0,0 +1,122 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
import { blockFields, blockOperations } from '../shared/descriptions/BlockDescription';
import { databaseFields, databaseOperations } from '../shared/descriptions/DatabaseDescription';
import {
databasePageFields,
databasePageOperations,
} from '../shared/descriptions/DatabasePageDescription';
import { pageFields, pageOperations } from '../shared/descriptions/PageDescription';
import { userFields, userOperations } from '../shared/descriptions/UserDescription';
export const versionDescription: INodeTypeDescription = {
displayName: 'Notion',
name: 'notion',
icon: { light: 'file:notion.svg', dark: 'file:notion.dark.svg' },
group: ['output'],
version: [2, 2.1, 2.2],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Notion API',
defaults: {
name: 'Notion',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
usableAsTool: true,
credentials: [
{
name: 'notionApi',
required: true,
// displayOptions: {
// show: {
// authentication: [
// 'apiKey',
// ],
// },
// },
},
// {
// name: 'notionOAuth2Api',
// required: true,
// displayOptions: {
// show: {
// authentication: [
// 'oAuth2',
// ],
// },
// },
// },
],
properties: [
// {
// displayName: 'Authentication',
// name: 'authentication',
// type: 'options',
// options: [
// {
// name: 'API Key',
// value: 'apiKey',
// },
// {
// name: 'OAuth2',
// value: 'oAuth2',
// },
// ],
// default: 'apiKey',
// description: 'The resource to operate on.',
// },
{
displayName:
'In Notion, make sure to <a href="https://www.notion.so/help/add-and-manage-connections-with-the-api" target="_blank">add your connection</a> to the pages you want to access.',
name: 'notionNotice',
type: 'notice',
default: '',
},
{
displayName: '',
name: 'Credentials',
type: 'credentials',
default: '',
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Block',
value: 'block',
},
{
name: 'Database',
value: 'database',
},
{
name: 'Database Page',
value: 'databasePage',
},
{
name: 'Page',
value: 'page',
},
{
name: 'User',
value: 'user',
},
],
default: 'page',
},
...blockOperations,
...blockFields,
...databaseOperations,
...databaseFields,
...databasePageOperations,
...databasePageFields,
...pageOperations,
...pageFields,
...userOperations,
...userFields,
],
};
@@ -0,0 +1 @@
export * as loadOptions from './loadOptions';
@@ -0,0 +1,201 @@
import moment from 'moment-timezone';
import type { IDataObject, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
import {
extractPageId,
getBlockTypesOptions,
notionApiRequest,
notionApiRequestAllItems,
} from '../../shared/GenericFunctions';
export async function getDatabaseProperties(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const databaseId = this.getCurrentNodeParameter('databaseId', {
extractValue: true,
}) as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
for (const key of Object.keys(properties as IDataObject)) {
//remove parameters that cannot be set from the API.
if (
![
'created_time',
'last_edited_time',
'created_by',
'last_edited_by',
'formula',
'rollup',
].includes(properties[key].type as string)
) {
returnData.push({
name: `${key}`,
value: `${key}|${properties[key].type}`,
});
}
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return returnData;
}
export async function getFilterProperties(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const databaseId = this.getCurrentNodeParameter('databaseId', {
extractValue: true,
}) as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
for (const key of Object.keys(properties as IDataObject)) {
returnData.push({
name: `${key}`,
value: `${key}|${properties[key].type}`,
});
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return returnData;
}
export async function getBlockTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
return getBlockTypesOptions();
}
export async function getPropertySelectValues(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const [name, type] = (this.getCurrentNodeParameter('&key') as string).split('|');
const databaseId = this.getCurrentNodeParameter('databaseId', {
extractValue: true,
}) as string;
const resource = this.getCurrentNodeParameter('resource') as string;
const operation = this.getCurrentNodeParameter('operation') as string;
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
if (resource === 'databasePage') {
if (['multi_select', 'select', 'status'].includes(type) && operation === 'getAll') {
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.name,
}));
} else if (
['multi_select', 'select', 'status'].includes(type) &&
['create', 'update'].includes(operation)
) {
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.name,
}));
}
}
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.id,
}));
}
export async function getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const users = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
for (const user of users) {
if (user.type === 'person') {
returnData.push({
name: user.name,
value: user.id,
});
}
}
return returnData;
}
export async function getDatabaseIdFromPage(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const pageId = extractPageId(
this.getCurrentNodeParameter('pageId', { extractValue: true }) as string,
);
const {
parent: { database_id: databaseId },
} = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
for (const key of Object.keys(properties as IDataObject)) {
//remove parameters that cannot be set from the API.
if (
![
'created_time',
'last_edited_time',
'created_by',
'last_edited_by',
'formula',
'rollup',
].includes(properties[key].type as string)
) {
returnData.push({
name: `${key}`,
value: `${key}|${properties[key].type}`,
});
}
}
returnData.sort((a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
return -1;
}
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
return 1;
}
return 0;
});
return returnData;
}
export async function getDatabaseOptionsFromPage(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const pageId = extractPageId(
this.getCurrentNodeParameter('pageId', { extractValue: true }) as string,
);
const [name, type] = (this.getCurrentNodeParameter('&key') as string).split('|');
const {
parent: { database_id: databaseId },
} = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
return properties[name][type].options.map((option: IDataObject) => ({
name: option.name,
value: option.name,
}));
}
// Get all the timezones to display them to user so that they can
// select them easily
export async function getTimezones(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
for (const timezone of moment.tz.names()) {
const timezoneName = timezone;
const timezoneId = timezone;
returnData.push({
name: timezoneName,
value: timezoneId,
});
}
returnData.unshift({
name: 'Default',
value: 'default',
description: 'Timezone set in n8n',
});
return returnData;
}