mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
fix(Notion Node): Paginate Get Many operations beyond 100-item API cap (#29690)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com>
This commit is contained in:
parent
6b893b45a0
commit
d318bc1e33
|
|
@ -91,6 +91,9 @@ export async function notionApiRequestAllItems(
|
|||
): Promise<any> {
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
|
||||
const limit = query.limit as number | undefined;
|
||||
delete query.limit;
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
|
@ -104,13 +107,12 @@ export async function notionApiRequestAllItems(
|
|||
body.start_cursor = next_cursor;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||
const limit = query.limit as number | undefined;
|
||||
if (limit && limit <= returnData.length) {
|
||||
return returnData;
|
||||
return returnData.slice(0, limit);
|
||||
}
|
||||
} while (responseData.has_more !== false);
|
||||
|
||||
return returnData;
|
||||
return limit ? returnData.slice(0, limit) : returnData;
|
||||
}
|
||||
|
||||
export async function notionApiRequestGetBlockChildrens(
|
||||
|
|
|
|||
|
|
@ -256,7 +256,6 @@ export const blockFields: INodeProperties[] = [
|
|||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ export const databaseFields: INodeProperties[] = [
|
|||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
|
|
|
|||
|
|
@ -1222,7 +1222,6 @@ export const databasePageFields: INodeProperties[] = [
|
|||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
formatBlocks,
|
||||
getPageId,
|
||||
notionApiRequest,
|
||||
notionApiRequestAllItems,
|
||||
} from '../shared/GenericFunctions';
|
||||
import { versionDescription as versionDescriptionV1 } from '../v1/VersionDescription';
|
||||
import { versionDescription as versionDescriptionV2 } from '../v2/VersionDescription';
|
||||
|
|
@ -431,3 +432,119 @@ describe('Test Notion, notionApiRequest', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Notion, notionApiRequestAllItems', () => {
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 2 }));
|
||||
mockExecuteFunctions.helpers = {
|
||||
requestWithAuthentication: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should extract limit from query, remove it, and stop pagination early', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name: string) => {
|
||||
if (name === 'resource') return 'block';
|
||||
if (name === 'authentication') return 'apiKey';
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const page1 = {
|
||||
results: [{ id: '1' }, { id: '2' }],
|
||||
has_more: true,
|
||||
next_cursor: 'cursor-2',
|
||||
};
|
||||
const page2 = {
|
||||
results: [{ id: '3' }, { id: '4' }],
|
||||
has_more: false,
|
||||
};
|
||||
|
||||
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock)
|
||||
.mockResolvedValueOnce(page1)
|
||||
.mockResolvedValueOnce(page2);
|
||||
|
||||
const query = { page_size: 2, limit: 3 };
|
||||
const result = await notionApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'results',
|
||||
'GET',
|
||||
'/blocks/test-block/children',
|
||||
{},
|
||||
query,
|
||||
);
|
||||
|
||||
expect(query).not.toHaveProperty('limit');
|
||||
expect(result).toHaveLength(3);
|
||||
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should paginate through all pages for non-block resources', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name: string) => {
|
||||
if (name === 'resource') return 'database';
|
||||
if (name === 'authentication') return 'apiKey';
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const page1 = {
|
||||
results: [{ id: '1' }],
|
||||
has_more: true,
|
||||
next_cursor: 'cursor-2',
|
||||
};
|
||||
const page2 = {
|
||||
results: [{ id: '2' }],
|
||||
has_more: false,
|
||||
next_cursor: null,
|
||||
};
|
||||
|
||||
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock)
|
||||
.mockResolvedValueOnce(page1)
|
||||
.mockResolvedValueOnce(page2);
|
||||
|
||||
const result = await notionApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'results',
|
||||
'POST',
|
||||
'/search',
|
||||
{},
|
||||
{ limit: 5 },
|
||||
);
|
||||
|
||||
expect(result).toEqual([{ id: '1' }, { id: '2' }]);
|
||||
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should return all items without slicing when no limit is provided', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name: string) => {
|
||||
if (name === 'resource') return 'database';
|
||||
if (name === 'authentication') return 'apiKey';
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const page1 = {
|
||||
results: [{ id: '1' }, { id: '2' }],
|
||||
has_more: false,
|
||||
next_cursor: null,
|
||||
};
|
||||
|
||||
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValueOnce(
|
||||
page1,
|
||||
);
|
||||
|
||||
const result = await notionApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'results',
|
||||
'POST',
|
||||
'/search',
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result).toEqual([{ id: '1' }, { id: '2' }]);
|
||||
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
158
packages/nodes-base/nodes/Notion/test/NotionV2.node.test.ts
Normal file
158
packages/nodes-base/nodes/Notion/test/NotionV2.node.test.ts
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import get from 'lodash/get';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IGetNodeParameterOptions,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
IPairedItemData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as GenericFunctions from '../shared/GenericFunctions';
|
||||
import { NotionV2 } from '../v2/NotionV2.node';
|
||||
|
||||
jest.mock('../shared/GenericFunctions', () => ({
|
||||
...jest.requireActual<typeof GenericFunctions>('../shared/GenericFunctions'),
|
||||
notionApiRequestAllItems: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockNotionApiRequestAllItems = GenericFunctions.notionApiRequestAllItems as jest.Mock;
|
||||
|
||||
function createMockExecuteFunction(nodeParameters: IDataObject): IExecuteFunctions {
|
||||
return {
|
||||
getInputData: () => [{ json: {} }],
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
_itemIndex: number,
|
||||
fallbackValue?: unknown,
|
||||
options?: IGetNodeParameterOptions,
|
||||
) {
|
||||
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
|
||||
return get(nodeParameters, parameter, fallbackValue);
|
||||
},
|
||||
getNode: () =>
|
||||
({
|
||||
typeVersion: 2.2,
|
||||
name: 'Notion',
|
||||
type: 'n8n-nodes-base.notion',
|
||||
}) as unknown as INode,
|
||||
getTimezone: () => 'UTC',
|
||||
continueOnFail: () => false,
|
||||
helpers: {
|
||||
constructExecutionMetaData: (
|
||||
inputData: INodeExecutionData[],
|
||||
_options: { itemData: IPairedItemData | IPairedItemData[] },
|
||||
) => inputData,
|
||||
returnJsonArray: (data: IDataObject | IDataObject[]) =>
|
||||
(Array.isArray(data) ? data : [data]).map((d) => ({ json: d })),
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
}
|
||||
|
||||
const node = new NotionV2({
|
||||
name: 'notion',
|
||||
displayName: 'Notion',
|
||||
icon: 'file:notion.svg',
|
||||
group: ['output'],
|
||||
defaultVersion: 2.2,
|
||||
description: 'Consume Notion API',
|
||||
});
|
||||
|
||||
describe('NotionV2 getAll pagination (coverage)', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('block getAll: should paginate with limit and slice results', async () => {
|
||||
const mockData = Array.from({ length: 150 }, (_, i) => ({
|
||||
object: 'block',
|
||||
id: `block-${i}`,
|
||||
}));
|
||||
mockNotionApiRequestAllItems.mockResolvedValueOnce(mockData);
|
||||
|
||||
const context = createMockExecuteFunction({
|
||||
resource: 'block',
|
||||
operation: 'getAll',
|
||||
'blockId.value': 'test-block-id',
|
||||
blockId: { __rl: true, mode: 'id', value: 'test-block-id' },
|
||||
returnAll: false,
|
||||
limit: 150,
|
||||
fetchNestedBlocks: false,
|
||||
});
|
||||
|
||||
const result = await node.execute.call(context);
|
||||
|
||||
expect(mockNotionApiRequestAllItems).toHaveBeenCalledWith(
|
||||
'results',
|
||||
'GET',
|
||||
'/blocks/test-block-id/children',
|
||||
{},
|
||||
{ page_size: 100, limit: 150 },
|
||||
);
|
||||
expect(result[0]).toHaveLength(150);
|
||||
});
|
||||
|
||||
it('database getAll: should paginate with limit and slice results', async () => {
|
||||
const mockData = Array.from({ length: 150 }, (_, i) => ({
|
||||
object: 'database',
|
||||
id: `db-${i}`,
|
||||
}));
|
||||
mockNotionApiRequestAllItems.mockResolvedValueOnce(mockData);
|
||||
|
||||
const context = createMockExecuteFunction({
|
||||
resource: 'database',
|
||||
operation: 'getAll',
|
||||
returnAll: false,
|
||||
limit: 150,
|
||||
simple: false,
|
||||
});
|
||||
|
||||
const result = await node.execute.call(context);
|
||||
|
||||
expect(mockNotionApiRequestAllItems).toHaveBeenCalledWith(
|
||||
'results',
|
||||
'POST',
|
||||
'/search',
|
||||
expect.objectContaining({
|
||||
filter: { property: 'object', value: 'database' },
|
||||
page_size: 100,
|
||||
}),
|
||||
{ limit: 150 },
|
||||
);
|
||||
expect(result[0]).toHaveLength(150);
|
||||
});
|
||||
|
||||
it('databasePage getAll: should paginate with limit and slice results', async () => {
|
||||
const mockData = Array.from({ length: 150 }, (_, i) => ({
|
||||
object: 'page',
|
||||
id: `page-${i}`,
|
||||
}));
|
||||
mockNotionApiRequestAllItems.mockResolvedValueOnce(mockData);
|
||||
|
||||
const context = createMockExecuteFunction({
|
||||
resource: 'databasePage',
|
||||
operation: 'getAll',
|
||||
'databaseId.value': 'test-db-id',
|
||||
databaseId: { __rl: true, mode: 'id', value: 'test-db-id' },
|
||||
returnAll: false,
|
||||
limit: 150,
|
||||
simple: false,
|
||||
filterType: 'none',
|
||||
'options.downloadFiles': false,
|
||||
'filters.conditions': [],
|
||||
'options.sort.sortValue': [],
|
||||
'options.filter': {},
|
||||
});
|
||||
|
||||
const result = await node.execute.call(context);
|
||||
|
||||
expect(mockNotionApiRequestAllItems).toHaveBeenCalledWith(
|
||||
'results',
|
||||
'POST',
|
||||
'/databases/test-db-id/query',
|
||||
expect.objectContaining({ page_size: 100 }),
|
||||
{ limit: 150 },
|
||||
);
|
||||
expect(result[0]).toHaveLength(150);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
function makeBlock(id: string, parentBlockId: string, text: string) {
|
||||
return {
|
||||
object: 'block',
|
||||
id,
|
||||
parent: { type: 'block_id', block_id: parentBlockId },
|
||||
created_time: '2024-01-01T00:00:00.000Z',
|
||||
last_edited_time: '2024-01-01T00:00: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: 'paragraph',
|
||||
paragraph: {
|
||||
color: 'default',
|
||||
text: [
|
||||
{
|
||||
type: 'text',
|
||||
text: { content: text, link: null },
|
||||
annotations: {
|
||||
bold: false,
|
||||
italic: false,
|
||||
strikethrough: false,
|
||||
underline: false,
|
||||
code: false,
|
||||
color: 'default',
|
||||
},
|
||||
plain_text: text,
|
||||
href: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const PARENT_BLOCK_ID = 'aabb0011-aabb-0011-aabb-0011aabb0011';
|
||||
|
||||
const PAGE_1_RESPONSE = {
|
||||
results: [
|
||||
makeBlock('b0000001-0000-0000-0000-000000000001', PARENT_BLOCK_ID, 'Block 1'),
|
||||
makeBlock('b0000002-0000-0000-0000-000000000002', PARENT_BLOCK_ID, 'Block 2'),
|
||||
makeBlock('b0000003-0000-0000-0000-000000000003', PARENT_BLOCK_ID, 'Block 3'),
|
||||
],
|
||||
has_more: true,
|
||||
next_cursor: 'cursor-page2',
|
||||
};
|
||||
|
||||
const PAGE_2_RESPONSE = {
|
||||
results: [
|
||||
makeBlock('b0000004-0000-0000-0000-000000000004', PARENT_BLOCK_ID, 'Block 4'),
|
||||
makeBlock('b0000005-0000-0000-0000-000000000005', PARENT_BLOCK_ID, 'Block 5'),
|
||||
],
|
||||
has_more: false,
|
||||
};
|
||||
|
||||
describe('Test NotionV2, block => getAll with pagination', () => {
|
||||
nock('https://api.notion.com')
|
||||
.get('/v1/blocks/aabb0011aabb0011aabb0011aabb0011/children')
|
||||
.query(true)
|
||||
.reply(200, PAGE_1_RESPONSE)
|
||||
.get('/v1/blocks/aabb0011aabb0011aabb0011aabb0011/children')
|
||||
.query(true)
|
||||
.reply(200, PAGE_2_RESPONSE);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['getAllPaginated.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
{
|
||||
"name": "test notion block getAll paginated",
|
||||
"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": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"mode": "id"
|
||||
},
|
||||
"returnAll": false,
|
||||
"limit": 4
|
||||
},
|
||||
"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": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"id": "b0000001-0000-0000-0000-000000000001",
|
||||
"parent": {
|
||||
"type": "block_id",
|
||||
"block_id": "aabb0011-aabb-0011-aabb-0011aabb0011"
|
||||
},
|
||||
"last_edited_by": {
|
||||
"object": "user",
|
||||
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
|
||||
},
|
||||
"has_children": false,
|
||||
"archived": false,
|
||||
"in_trash": false,
|
||||
"type": "paragraph",
|
||||
"root_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"content": "Block 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"object": "block",
|
||||
"parent_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"id": "b0000002-0000-0000-0000-000000000002",
|
||||
"parent": {
|
||||
"type": "block_id",
|
||||
"block_id": "aabb0011-aabb-0011-aabb-0011aabb0011"
|
||||
},
|
||||
"last_edited_by": {
|
||||
"object": "user",
|
||||
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
|
||||
},
|
||||
"has_children": false,
|
||||
"archived": false,
|
||||
"in_trash": false,
|
||||
"type": "paragraph",
|
||||
"root_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"content": "Block 2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"object": "block",
|
||||
"parent_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"id": "b0000003-0000-0000-0000-000000000003",
|
||||
"parent": {
|
||||
"type": "block_id",
|
||||
"block_id": "aabb0011-aabb-0011-aabb-0011aabb0011"
|
||||
},
|
||||
"last_edited_by": {
|
||||
"object": "user",
|
||||
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
|
||||
},
|
||||
"has_children": false,
|
||||
"archived": false,
|
||||
"in_trash": false,
|
||||
"type": "paragraph",
|
||||
"root_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"content": "Block 3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"object": "block",
|
||||
"parent_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"id": "b0000004-0000-0000-0000-000000000004",
|
||||
"parent": {
|
||||
"type": "block_id",
|
||||
"block_id": "aabb0011-aabb-0011-aabb-0011aabb0011"
|
||||
},
|
||||
"last_edited_by": {
|
||||
"object": "user",
|
||||
"id": "88f72c1a-07ed-4bae-9fa0-231365d813d9"
|
||||
},
|
||||
"has_children": false,
|
||||
"archived": false,
|
||||
"in_trash": false,
|
||||
"type": "paragraph",
|
||||
"root_id": "aabb0011aabb0011aabb0011aabb0011",
|
||||
"content": "Block 4"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
|
||||
},
|
||||
"id": "PaginatedBlockTest",
|
||||
"tags": []
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
function makeDatabase(id: string, name: string) {
|
||||
return {
|
||||
object: 'database',
|
||||
id,
|
||||
cover: null,
|
||||
icon: null,
|
||||
created_time: '2024-01-01T00:00: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-01-01T00:00:00.000Z',
|
||||
title: [
|
||||
{
|
||||
type: 'text',
|
||||
text: { content: name, link: null },
|
||||
annotations: {
|
||||
bold: false,
|
||||
italic: false,
|
||||
strikethrough: false,
|
||||
underline: false,
|
||||
code: false,
|
||||
color: 'default',
|
||||
},
|
||||
plain_text: name,
|
||||
href: null,
|
||||
},
|
||||
],
|
||||
description: [],
|
||||
is_inline: false,
|
||||
properties: {
|
||||
Name: { id: 'title', name: 'Name', type: 'title', title: {} },
|
||||
},
|
||||
parent: { type: 'page_id', page_id: 'cc3d2b3c-f31a-4773-ab39-17a60c54567a' },
|
||||
url: `https://www.notion.so/${id.replace(/-/g, '')}`,
|
||||
public_url: null,
|
||||
archived: false,
|
||||
in_trash: false,
|
||||
};
|
||||
}
|
||||
|
||||
const PAGE_1_RESPONSE = {
|
||||
results: [
|
||||
makeDatabase('d0000001-0000-0000-0000-000000000001', 'Database A'),
|
||||
makeDatabase('d0000002-0000-0000-0000-000000000002', 'Database B'),
|
||||
],
|
||||
has_more: true,
|
||||
next_cursor: 'cursor-page2',
|
||||
};
|
||||
|
||||
const PAGE_2_RESPONSE = {
|
||||
results: [
|
||||
makeDatabase('d0000003-0000-0000-0000-000000000003', 'Database C'),
|
||||
makeDatabase('d0000004-0000-0000-0000-000000000004', 'Database D'),
|
||||
],
|
||||
has_more: false,
|
||||
};
|
||||
|
||||
describe('Test NotionV2, database => getAll with pagination', () => {
|
||||
nock('https://api.notion.com')
|
||||
.post('/v1/search')
|
||||
.reply(200, PAGE_1_RESPONSE)
|
||||
.post('/v1/search')
|
||||
.reply(200, PAGE_2_RESPONSE);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['getAllPaginated.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"name": "test notion database getAll paginated",
|
||||
"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": false,
|
||||
"limit": 3
|
||||
},
|
||||
"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": "d0000001-0000-0000-0000-000000000001",
|
||||
"name": "Database A",
|
||||
"url": "https://www.notion.so/d0000001000000000000000000000001"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "d0000002-0000-0000-0000-000000000002",
|
||||
"name": "Database B",
|
||||
"url": "https://www.notion.so/d0000002000000000000000000000002"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "d0000003-0000-0000-0000-000000000003",
|
||||
"name": "Database C",
|
||||
"url": "https://www.notion.so/d0000003000000000000000000000003"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
|
||||
},
|
||||
"id": "PaginatedDbTest",
|
||||
"tags": []
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
function makePage(id: string, name: string) {
|
||||
return {
|
||||
object: 'page',
|
||||
id,
|
||||
created_time: '2024-01-01T00:00:00.000Z',
|
||||
last_edited_time: '2024-01-01T00:00:00.000Z',
|
||||
created_by: { object: 'user', id: '88f72c1a-07ed-4bae-9fa0-231365d813d9' },
|
||||
last_edited_by: { object: 'user', id: '88f72c1a-07ed-4bae-9fa0-231365d813d9' },
|
||||
cover: null,
|
||||
icon: null,
|
||||
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: name, link: null },
|
||||
annotations: {
|
||||
bold: false,
|
||||
italic: false,
|
||||
strikethrough: false,
|
||||
underline: false,
|
||||
code: false,
|
||||
color: 'default',
|
||||
},
|
||||
plain_text: name,
|
||||
href: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
url: `https://www.notion.so/${name.replace(/ /g, '-')}-${id.replace(/-/g, '')}`,
|
||||
public_url: null,
|
||||
};
|
||||
}
|
||||
|
||||
const PAGE_1_RESPONSE = {
|
||||
results: [
|
||||
makePage('p0000001-0000-0000-0000-000000000001', 'Page A'),
|
||||
makePage('p0000002-0000-0000-0000-000000000002', 'Page B'),
|
||||
],
|
||||
has_more: true,
|
||||
next_cursor: 'cursor-page2',
|
||||
};
|
||||
|
||||
const PAGE_2_RESPONSE = {
|
||||
results: [
|
||||
makePage('p0000003-0000-0000-0000-000000000003', 'Page C'),
|
||||
makePage('p0000004-0000-0000-0000-000000000004', 'Page D'),
|
||||
],
|
||||
has_more: false,
|
||||
};
|
||||
|
||||
describe('Test NotionV2, databasePage => getAll with pagination', () => {
|
||||
nock('https://api.notion.com')
|
||||
.post('/v1/databases/138fb9cb-4cf0-804c-8663-d8ecdd5e692f/query')
|
||||
.reply(200, PAGE_1_RESPONSE)
|
||||
.post('/v1/databases/138fb9cb-4cf0-804c-8663-d8ecdd5e692f/query')
|
||||
.reply(200, PAGE_2_RESPONSE);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['getAllPaginated.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"name": "test notion databasePage getAll paginated",
|
||||
"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": false,
|
||||
"limit": 3
|
||||
},
|
||||
"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": "p0000001-0000-0000-0000-000000000001",
|
||||
"name": "Page A",
|
||||
"url": "https://www.notion.so/Page-A-p0000001000000000000000000000001",
|
||||
"property_tags": [],
|
||||
"property_name": "Page A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "p0000002-0000-0000-0000-000000000002",
|
||||
"name": "Page B",
|
||||
"url": "https://www.notion.so/Page-B-p0000002000000000000000000000002",
|
||||
"property_tags": [],
|
||||
"property_name": "Page B"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "p0000003-0000-0000-0000-000000000003",
|
||||
"name": "Page C",
|
||||
"url": "https://www.notion.so/Page-C-p0000003000000000000000000000003",
|
||||
"property_tags": [],
|
||||
"property_name": "Page C"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
|
||||
},
|
||||
"id": "PaginatedDbPageTest",
|
||||
"tags": []
|
||||
}
|
||||
|
|
@ -117,26 +117,23 @@ export class NotionV2 implements INodeType {
|
|||
responseData = await notionApiRequestGetBlockChildrens.call(this, responseData);
|
||||
}
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i);
|
||||
qs.page_size = limit;
|
||||
responseData = await notionApiRequest.call(
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await notionApiRequestAllItems.call(
|
||||
this,
|
||||
'results',
|
||||
'GET',
|
||||
`/blocks/${blockId}/children`,
|
||||
{},
|
||||
qs,
|
||||
{ page_size: Math.min(limit, 100), limit },
|
||||
);
|
||||
const results = responseData.results;
|
||||
|
||||
if (fetchNestedBlocks) {
|
||||
responseData = await notionApiRequestGetBlockChildrens.call(
|
||||
this,
|
||||
results,
|
||||
responseData,
|
||||
[],
|
||||
limit,
|
||||
);
|
||||
} else {
|
||||
responseData = results;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,9 +218,16 @@ export class NotionV2 implements INodeType {
|
|||
body,
|
||||
);
|
||||
} else {
|
||||
body.page_size = this.getNodeParameter('limit', i);
|
||||
responseData = await notionApiRequest.call(this, 'POST', '/search', body);
|
||||
responseData = responseData.results;
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
body.page_size = Math.min(limit, 100);
|
||||
responseData = await notionApiRequestAllItems.call(
|
||||
this,
|
||||
'results',
|
||||
'POST',
|
||||
'/search',
|
||||
body,
|
||||
{ limit },
|
||||
);
|
||||
}
|
||||
if (simple) {
|
||||
responseData = simplifyObjects(responseData, download);
|
||||
|
|
@ -484,15 +488,16 @@ export class NotionV2 implements INodeType {
|
|||
{},
|
||||
);
|
||||
} else {
|
||||
body.page_size = this.getNodeParameter('limit', i);
|
||||
responseData = await notionApiRequest.call(
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
body.page_size = Math.min(limit, 100);
|
||||
responseData = await notionApiRequestAllItems.call(
|
||||
this,
|
||||
'results',
|
||||
'POST',
|
||||
`/databases/${databaseId}/query`,
|
||||
body,
|
||||
qs,
|
||||
{ limit },
|
||||
);
|
||||
responseData = responseData.results;
|
||||
}
|
||||
if (download) {
|
||||
responseData = await downloadFiles.call(this, responseData as FileRecord[], [
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user