Worked on pagination

This commit is contained in:
Adina Totorean 2025-01-26 15:28:04 +02:00
parent d28af0f96a
commit 6307ac162f
6 changed files with 223 additions and 63 deletions

View file

@ -17,9 +17,9 @@ export class AzureCosmosDbSharedKeyApi implements ICredentialType {
properties: INodeProperties[] = [ properties: INodeProperties[] = [
{ {
displayName: 'Database', displayName: 'Account',
name: 'databaseAccount', name: 'account',
description: 'Database account', description: 'Account name',
type: 'string', type: 'string',
default: '', default: '',
}, },

View file

@ -2,7 +2,8 @@ import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow';
import { containerFields, containerOperations } from './descriptions/ContainerDescription'; import { containerFields, containerOperations } from './descriptions/ContainerDescription';
import { itemFields, itemOperations } from '../../Aws/DynamoDB/ItemDescription'; import { itemFields, itemOperations } from './descriptions/ItemDescription';
import { searchCollections, searchDatabases } from './GenericFunctions';
export class AzureCosmosDb implements INodeType { export class AzureCosmosDb implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -33,7 +34,7 @@ export class AzureCosmosDb implements INodeType {
}, },
], ],
requestDefaults: { requestDefaults: {
baseURL: '=https://{$credentials.databaseAccount}.documents.azure.com', baseURL: '=https://{$credentials.account}.documents.azure.com',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
}, },
@ -68,8 +69,8 @@ export class AzureCosmosDb implements INodeType {
], ],
default: 'container', default: 'container',
}, },
...itemFields,
...itemOperations, ...itemOperations,
...itemFields,
...containerOperations, ...containerOperations,
...containerFields, ...containerFields,
], ],
@ -77,8 +78,8 @@ export class AzureCosmosDb implements INodeType {
methods = { methods = {
listSearch: { listSearch: {
// searchCollections, searchCollections,
// searchDatabases, searchDatabases,
}, },
}; };
} }

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M105.825 58.635c5.491 22.449-8.458 45.049-31.156 50.48-22.695 5.429-45.547-8.365-51.036-30.815-5.492-22.449 8.458-45.049 31.153-50.48l.078-.018c22.588-5.46 45.374 8.223 50.896 30.565l.065.268Z" fill="#59B3D8"/><path fill-rule="evenodd" clip-rule="evenodd" d="M58.747 85.194c-.013-6.137-5.055-11.1-11.259-11.085h-1.701c1.442-5.932-2.248-11.895-8.246-13.322a11.182 11.182 0 0 0-2.703-.306H23.162c-2.525 12.851 1.246 26.127 10.174 35.796h14.149c6.205.015 11.247-4.948 11.262-11.083ZM72.69 39.233c0 .649.085 1.296.255 1.925h-4.861c-6.445 0-11.667 5.168-11.667 11.543 0 6.372 5.222 11.54 11.667 11.54h38.645c-1.258-13.787-9.486-26.01-21.862-32.477h-4.605c-4.177-.002-7.562 3.339-7.572 7.469Zm34.043 33.587H83.679c-5.259-.013-9.531 4.187-9.552 9.385a9.241 9.241 0 0 0 1.148 4.471c-5.003 1.546-7.792 6.814-6.228 11.765 1.242 3.934 4.938 6.607 9.106 6.589h6.427c12.314-6.454 20.607-18.51 22.153-32.21Z" fill="#B6DEEC"/><path fill-rule="evenodd" clip-rule="evenodd" d="M17.382 40.624a1.286 1.286 0 0 1-1.3-1.275v-.003c-.021-8.064-6.637-14.587-14.79-14.579A1.286 1.286 0 0 1 0 23.489c0-.703.579-1.275 1.292-1.275 8.143.007 14.756-6.503 14.79-14.554a1.29 1.29 0 0 1 1.356-1.227c.672.028 1.21.56 1.241 1.227.021 8.061 6.639 14.584 14.792 14.577.713 0 1.292.572 1.292 1.277 0 .706-.579 1.279-1.292 1.279-8.148-.011-14.766 6.507-14.792 14.566-.01.7-.589 1.265-1.297 1.265Z" fill="#B7D332"/><path fill-rule="evenodd" clip-rule="evenodd" d="M108.6 122.793a.764.764 0 0 1-.768-.759c-.018-4.821-3.98-8.719-8.854-8.709a.762.762 0 0 1-.77-.756c0-.419.342-.759.765-.759h.005c4.872.002 8.826-3.893 8.844-8.711a.77.77 0 0 1 .778-.767.77.77 0 0 1 .775.767c.018 4.818 3.972 8.713 8.843 8.711a.761.761 0 0 1 .77.756.759.759 0 0 1-.765.759h-.005c-4.871-.002-8.828 3.893-8.843 8.714a.764.764 0 0 1-.773.754h-.002Z" fill="#0072C5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M126.317 30.84c-4.035-6.539-14.175-8.049-29.306-4.384a121.688 121.688 0 0 0-13.893 4.384 42.829 42.829 0 0 1 8.187 5.173c2.574-.836 5.101-1.59 7.512-2.173a53.33 53.33 0 0 1 12.335-1.727c4.957 0 7.691 1.211 8.606 2.686 1.496 2.423.119 8.816-8.681 18.871-1.566 1.789-3.326 3.601-5.179 5.423a175.936 175.936 0 0 1-31.843 24.149 176.032 176.032 0 0 1-36.329 17.105c-15.317 4.936-25.773 4.836-28.119 1.048-2.342-3.788 2.344-13.048 13.776-24.29a41.005 41.005 0 0 1-.938-9.735c-18.2 16.271-24.09 30.365-19.387 37.981 2.463 3.985 7.844 6.229 15.705 6.229a80.772 80.772 0 0 0 27.183-5.932 194.648 194.648 0 0 0 32.11-15.926 193.405 193.405 0 0 0 28.884-21.148 118.565 118.565 0 0 0 9.947-9.941c10.207-11.655 13.466-21.268 9.43-27.793Z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,41 +1,45 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import type { import type {
DeclarativeRestApiSettings,
IDataObject, IDataObject,
IExecutePaginationFunctions,
IHttpRequestOptions, IHttpRequestOptions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodeExecutionData,
INodeListSearchItems, INodeListSearchItems,
INodeListSearchResult, INodeListSearchResult,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow'; import { ApplicationError } from 'n8n-workflow';
import * as querystring from 'querystring'; import * as querystring from 'querystring';
export const HeaderConstants = { // export const HeaderConstants = {
// Required // // Required
AUTHORIZATION: 'Authorization', // AUTHORIZATION: 'Authorization',
CONTENT_TYPE: 'Content-Type', // CONTENT_TYPE: 'Content-Type',
X_MS_DATE: 'x-ms-date', // X_MS_DATE: 'x-ms-date',
X_MS_VERSION: 'x-ms-version', // X_MS_VERSION: 'x-ms-version',
//Required - for session consistency only // //Required - for session consistency only
X_MS_SESSION_TOKEN: 'x-ms-session-token', // X_MS_SESSION_TOKEN: 'x-ms-session-token',
// Optional // // Optional
IF_MATCH: 'If-Match', // IF_MATCH: 'If-Match',
IF_NONE_MATCH: 'If-None-Match', // IF_NONE_MATCH: 'If-None-Match',
IF_MODIFIED_SINCE: 'If-Modified-Since', // IF_MODIFIED_SINCE: 'If-Modified-Since',
USER_AGENT: 'User-Agent', // USER_AGENT: 'User-Agent',
X_MS_ACTIVITY_ID: 'x-ms-activity-id', // X_MS_ACTIVITY_ID: 'x-ms-activity-id',
X_MS_CONSISTENCY_LEVEL: 'x-ms-consistency-level', // X_MS_CONSISTENCY_LEVEL: 'x-ms-consistency-level',
X_MS_CONTINUATION: 'x-ms-continuation', // X_MS_CONTINUATION: 'x-ms-continuation',
X_MS_MAX_ITEM_COUNT: 'x-ms-max-item-count', // X_MS_MAX_ITEM_COUNT: 'x-ms-max-item-count',
X_MS_DOCUMENTDB_PARTITIONKEY: 'x-ms-documentdb-partitionkey', // X_MS_DOCUMENTDB_PARTITIONKEY: 'x-ms-documentdb-partitionkey',
X_MS_DOCUMENTDB_QUERY_ENABLECROSSPARTITION: 'x-ms-documentdb-query-enablecrosspartition', // X_MS_DOCUMENTDB_ISQUERY: 'x-ms-documentdb-isquery',
A_IM: 'A-IM', // X_MS_DOCUMENTDB_QUERY_ENABLECROSSPARTITION: 'x-ms-documentdb-query-enablecrosspartition',
X_MS_DOCUMENTDB_PARTITIONKEYRANGEID: 'x-ms-documentdb-partitionkeyrangeid', // A_IM: 'A-IM',
X_MS_COSMOS_ALLOW_TENTATIVE_WRITES: 'x-ms-cosmos-allow-tentative-writes', // X_MS_DOCUMENTDB_PARTITIONKEYRANGEID: 'x-ms-documentdb-partitionkeyrangeid',
// X_MS_COSMOS_ALLOW_TENTATIVE_WRITES: 'x-ms-cosmos-allow-tentative-writes',
PREFIX_FOR_STORAGE: 'x-ms-', // PREFIX_FOR_STORAGE: 'x-ms-',
}; // };
export function getAuthorizationTokenUsingMasterKey( export function getAuthorizationTokenUsingMasterKey(
verb: string, verb: string,
@ -61,12 +65,67 @@ export function getAuthorizationTokenUsingMasterKey(
return authorizationString; return authorizationString;
} }
export async function handlePagination(
this: IExecutePaginationFunctions,
resultOptions: DeclarativeRestApiSettings.ResultOptions,
): Promise<INodeExecutionData[]> {
const aggregatedResult: IDataObject[] = [];
let nextPageToken: string | undefined;
const returnAll = this.getNodeParameter('returnAll') as boolean;
let limit = 60;
if (!returnAll) {
limit = this.getNodeParameter('limit') as number;
resultOptions.maxResults = limit;
}
resultOptions.paginate = true;
do {
if (nextPageToken) {
resultOptions.options.headers = resultOptions.options.headers ?? {};
resultOptions.options.headers['x-ms-continuation'] = nextPageToken;
}
const responseData = await this.makeRoutingRequest(resultOptions);
if (Array.isArray(responseData)) {
for (const responsePage of responseData) {
aggregatedResult.push(responsePage);
if (!returnAll && aggregatedResult.length >= limit) {
return aggregatedResult.slice(0, limit).map((result) => ({ json: result }));
}
}
}
//TO-DO-check-if-works
if (responseData.length > 0) {
const lastItem = responseData[responseData.length - 1];
if ('headers' in lastItem) {
const headers = (lastItem as unknown as { headers: { [key: string]: string } }).headers;
if (headers) {
nextPageToken = headers['x-ms-continuation'] as string | undefined;
}
}
}
if (!nextPageToken) {
break;
}
} while (nextPageToken);
return aggregatedResult.map((result) => ({ json: result }));
}
export async function azureCosmosDbRequest( export async function azureCosmosDbRequest(
this: ILoadOptionsFunctions, this: ILoadOptionsFunctions,
opts: IHttpRequestOptions, opts: IHttpRequestOptions,
): Promise<IDataObject> { ): Promise<IDataObject> {
const credentials = await this.getCredentials('azureCosmosDb'); const credentials = await this.getCredentials('azureCosmosDbSharedKeyApi');
const databaseAccount = credentials?.database; const databaseAccount = credentials?.account;
if (!databaseAccount) { if (!databaseAccount) {
throw new ApplicationError('Database account not found in credentials!', { level: 'error' }); throw new ApplicationError('Database account not found in credentials!', { level: 'error' });
@ -93,7 +152,7 @@ export async function azureCosmosDbRequest(
try { try {
return (await this.helpers.requestWithAuthentication.call( return (await this.helpers.requestWithAuthentication.call(
this, this,
'azureCosmosDb', 'azureCosmosDbSharedKeyApi',
requestOptions, requestOptions,
)) as IDataObject; )) as IDataObject;
} catch (error) { } catch (error) {
@ -128,18 +187,17 @@ export async function azureCosmosDbRequest(
export async function searchCollections( export async function searchCollections(
this: ILoadOptionsFunctions, this: ILoadOptionsFunctions,
filter?: string, filter?: string,
paginationToken?: string,
): Promise<INodeListSearchResult> { ): Promise<INodeListSearchResult> {
const dbId = this.getNodeParameter('dbId') as string; const dbId = this.getNodeParameter('dbId') as string;
if (!dbId) { if (!dbId) {
throw new ApplicationError('Database ID is required'); throw new ApplicationError('Database ID is required');
} }
const credentials = await this.getCredentials('azureCosmosDb'); const credentials = await this.getCredentials('azureCosmosDbSharedKeyApi');
const databaseAccount = credentials?.databaseAccount; const databaseAccount = credentials?.account;
if (!databaseAccount) { if (!databaseAccount) {
throw new ApplicationError('Database account not found in credentials!', { level: 'error' }); throw new ApplicationError('Account name not found in credentials!', { level: 'error' });
} }
const opts: IHttpRequestOptions = { const opts: IHttpRequestOptions = {
@ -154,9 +212,9 @@ export async function searchCollections(
const responseData: IDataObject = await azureCosmosDbRequest.call(this, opts); const responseData: IDataObject = await azureCosmosDbRequest.call(this, opts);
const responseBody = responseData as { const responseBody = responseData as {
Collections: IDataObject[]; DocumentCollections: IDataObject[];
}; };
const collections = responseBody.Collections; const collections = responseBody.DocumentCollections;
if (!collections) { if (!collections) {
return { results: [] }; return { results: [] };

View file

@ -49,7 +49,7 @@ export const containerOperations: INodeProperties[] = [
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}', url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}',
}, },
}, },
action: 'Get item', action: 'Get container',
}, },
{ {
name: 'Get Many', name: 'Get Many',
@ -159,12 +159,9 @@ export const createFields: INodeProperties[] = [
}, },
}, },
{ {
displayName: 'Additional Keys', displayName: 'Additional Fields',
name: 'additionalKeys', name: 'additionalFields',
type: 'fixedCollection',
default: {}, default: {},
placeholder: '"paths": ["/AccountNumber"],"kind": "Hash", "Version": 2',
description: 'User-defined JSON object representing the document properties',
displayOptions: { displayOptions: {
show: { show: {
resource: ['container'], resource: ['container'],
@ -189,6 +186,8 @@ export const createFields: INodeProperties[] = [
}, },
}, },
], ],
placeholder: 'Add Option',
type: 'collection',
}, },
]; ];

View file

@ -1,4 +1,6 @@
import type { INodeProperties } from 'n8n-workflow'; import type { IExecuteSingleFunctions, IHttpRequestOptions, INodeProperties } from 'n8n-workflow';
import { handlePagination } from '../GenericFunctions';
export const itemOperations: INodeProperties[] = [ export const itemOperations: INodeProperties[] = [
{ {
@ -15,12 +17,12 @@ export const itemOperations: INodeProperties[] = [
{ {
name: 'Create', name: 'Create',
value: 'create', value: 'create',
description: 'Create an item', description: 'Create a new item',
routing: { routing: {
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'POST', method: 'POST',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item', url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs',
}, },
}, },
action: 'Create item', action: 'Create item',
@ -28,12 +30,12 @@ export const itemOperations: INodeProperties[] = [
{ {
name: 'Delete', name: 'Delete',
value: 'delete', value: 'delete',
description: 'Delete an item', description: 'Delete an existing item',
routing: { routing: {
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'DELETE', method: 'DELETE',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item/{{ $parameter["id"] }}', url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs/{{ $parameter["id"] }}',
}, },
}, },
action: 'Delete item', action: 'Delete item',
@ -46,7 +48,7 @@ export const itemOperations: INodeProperties[] = [
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'GET', method: 'GET',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item/{{ $parameter["id"] }}', url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs/{{ $parameter["id"] }}',
}, },
}, },
action: 'Get item', action: 'Get item',
@ -56,6 +58,12 @@ export const itemOperations: INodeProperties[] = [
value: 'getAll', value: 'getAll',
description: 'Retrieve a list of items', description: 'Retrieve a list of items',
routing: { routing: {
send: {
paginate: true,
},
operations: {
pagination: handlePagination,
},
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'GET', method: 'GET',
@ -72,7 +80,11 @@ export const itemOperations: INodeProperties[] = [
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'POST', method: 'POST',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item', url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs',
headers: {
'Content-Type': 'application/query+json',
'x-ms-documentdb-isquery': 'True',
},
}, },
}, },
action: 'Query items', action: 'Query items',
@ -80,15 +92,15 @@ export const itemOperations: INodeProperties[] = [
{ {
name: 'Update', name: 'Update',
value: 'update', value: 'update',
description: 'Update an item', description: 'Update an existing item',
routing: { routing: {
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'PATCH', method: 'PATCH',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item/{{ $parameter["id"] }}', url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs/{{ $parameter["id"] }}',
}, },
}, },
action: 'Create item', action: 'Update item',
}, },
], ],
default: 'getAll', default: 'getAll',
@ -538,9 +550,58 @@ export const getAllFields: INodeProperties[] = [
}, },
], ],
}, },
{
displayName: 'Return All',
name: 'returnAll',
default: false,
description: 'Whether to return all results or only up to a given limit',
displayOptions: {
show: {
resource: ['item'],
operation: ['getAll'],
},
},
routing: {
send: {
preSend: [
async function (
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
return requestOptions;
},
],
},
},
type: 'boolean',
},
{
displayName: 'Limit',
name: 'limit',
default: 50,
description: 'Max number of results to return',
displayOptions: {
show: {
resource: ['item'],
operation: ['getAll'],
returnAll: [false],
},
},
routing: {
send: {
property: 'x-ms-max-item-count',
type: 'query',
value: '={{ $value }}',
},
},
type: 'number',
typeOptions: {
minValue: 1,
},
validateType: 'number',
},
]; ];
//TO-DO-check-fields
export const queryFields: INodeProperties[] = [ export const queryFields: INodeProperties[] = [
{ {
displayName: 'Database ID', displayName: 'Database ID',
@ -631,23 +692,63 @@ export const queryFields: INodeProperties[] = [
], ],
}, },
{ {
displayName: 'ID', displayName: 'Query',
name: 'id', name: 'query',
type: 'string', type: 'string',
default: '', default: '',
placeholder: 'e.g. AndersenFamily',
description: "Item's ID",
required: true, required: true,
description: 'The SQL query text to execute',
displayOptions: { displayOptions: {
show: { show: {
resource: ['item'], resource: ['item'],
operation: ['query'], operation: ['query'],
}, },
}, },
placeholder: 'SELECT * FROM c WHERE c.name = @name',
routing: { routing: {
send: { send: {
type: 'body', type: 'body',
property: 'id', property: 'query',
value: '={{$value}}',
},
},
},
{
displayName: 'Parameters',
name: 'parameters',
type: 'fixedCollection',
required: true,
default: [],
placeholder: 'Add Parameter',
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'parameters',
displayName: 'Parameter',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
placeholder: 'e.g., @name',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
placeholder: 'e.g., John',
},
],
},
],
routing: {
send: {
type: 'body',
property: 'parameters',
value: '={{$value}}', value: '={{$value}}',
}, },
}, },