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[] = [
{
displayName: 'Database',
name: 'databaseAccount',
description: 'Database account',
displayName: 'Account',
name: 'account',
description: 'Account name',
type: 'string',
default: '',
},

View file

@ -2,7 +2,8 @@ import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
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 {
description: INodeTypeDescription = {
@ -33,7 +34,7 @@ export class AzureCosmosDb implements INodeType {
},
],
requestDefaults: {
baseURL: '=https://{$credentials.databaseAccount}.documents.azure.com',
baseURL: '=https://{$credentials.account}.documents.azure.com',
headers: {
Accept: 'application/json',
},
@ -68,8 +69,8 @@ export class AzureCosmosDb implements INodeType {
],
default: 'container',
},
...itemFields,
...itemOperations,
...itemFields,
...containerOperations,
...containerFields,
],
@ -77,8 +78,8 @@ export class AzureCosmosDb implements INodeType {
methods = {
listSearch: {
// searchCollections,
// searchDatabases,
searchCollections,
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 type {
DeclarativeRestApiSettings,
IDataObject,
IExecutePaginationFunctions,
IHttpRequestOptions,
ILoadOptionsFunctions,
INodeExecutionData,
INodeListSearchItems,
INodeListSearchResult,
} from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow';
import * as querystring from 'querystring';
export const HeaderConstants = {
// Required
AUTHORIZATION: 'Authorization',
CONTENT_TYPE: 'Content-Type',
X_MS_DATE: 'x-ms-date',
X_MS_VERSION: 'x-ms-version',
// export const HeaderConstants = {
// // Required
// AUTHORIZATION: 'Authorization',
// CONTENT_TYPE: 'Content-Type',
// X_MS_DATE: 'x-ms-date',
// X_MS_VERSION: 'x-ms-version',
//Required - for session consistency only
X_MS_SESSION_TOKEN: 'x-ms-session-token',
// //Required - for session consistency only
// X_MS_SESSION_TOKEN: 'x-ms-session-token',
// Optional
IF_MATCH: 'If-Match',
IF_NONE_MATCH: 'If-None-Match',
IF_MODIFIED_SINCE: 'If-Modified-Since',
USER_AGENT: 'User-Agent',
X_MS_ACTIVITY_ID: 'x-ms-activity-id',
X_MS_CONSISTENCY_LEVEL: 'x-ms-consistency-level',
X_MS_CONTINUATION: 'x-ms-continuation',
X_MS_MAX_ITEM_COUNT: 'x-ms-max-item-count',
X_MS_DOCUMENTDB_PARTITIONKEY: 'x-ms-documentdb-partitionkey',
X_MS_DOCUMENTDB_QUERY_ENABLECROSSPARTITION: 'x-ms-documentdb-query-enablecrosspartition',
A_IM: 'A-IM',
X_MS_DOCUMENTDB_PARTITIONKEYRANGEID: 'x-ms-documentdb-partitionkeyrangeid',
X_MS_COSMOS_ALLOW_TENTATIVE_WRITES: 'x-ms-cosmos-allow-tentative-writes',
// // Optional
// IF_MATCH: 'If-Match',
// IF_NONE_MATCH: 'If-None-Match',
// IF_MODIFIED_SINCE: 'If-Modified-Since',
// USER_AGENT: 'User-Agent',
// X_MS_ACTIVITY_ID: 'x-ms-activity-id',
// X_MS_CONSISTENCY_LEVEL: 'x-ms-consistency-level',
// X_MS_CONTINUATION: 'x-ms-continuation',
// X_MS_MAX_ITEM_COUNT: 'x-ms-max-item-count',
// X_MS_DOCUMENTDB_PARTITIONKEY: 'x-ms-documentdb-partitionkey',
// X_MS_DOCUMENTDB_ISQUERY: 'x-ms-documentdb-isquery',
// X_MS_DOCUMENTDB_QUERY_ENABLECROSSPARTITION: 'x-ms-documentdb-query-enablecrosspartition',
// A_IM: 'A-IM',
// 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(
verb: string,
@ -61,12 +65,67 @@ export function getAuthorizationTokenUsingMasterKey(
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(
this: ILoadOptionsFunctions,
opts: IHttpRequestOptions,
): Promise<IDataObject> {
const credentials = await this.getCredentials('azureCosmosDb');
const databaseAccount = credentials?.database;
const credentials = await this.getCredentials('azureCosmosDbSharedKeyApi');
const databaseAccount = credentials?.account;
if (!databaseAccount) {
throw new ApplicationError('Database account not found in credentials!', { level: 'error' });
@ -93,7 +152,7 @@ export async function azureCosmosDbRequest(
try {
return (await this.helpers.requestWithAuthentication.call(
this,
'azureCosmosDb',
'azureCosmosDbSharedKeyApi',
requestOptions,
)) as IDataObject;
} catch (error) {
@ -128,18 +187,17 @@ export async function azureCosmosDbRequest(
export async function searchCollections(
this: ILoadOptionsFunctions,
filter?: string,
paginationToken?: string,
): Promise<INodeListSearchResult> {
const dbId = this.getNodeParameter('dbId') as string;
if (!dbId) {
throw new ApplicationError('Database ID is required');
}
const credentials = await this.getCredentials('azureCosmosDb');
const databaseAccount = credentials?.databaseAccount;
const credentials = await this.getCredentials('azureCosmosDbSharedKeyApi');
const databaseAccount = credentials?.account;
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 = {
@ -154,9 +212,9 @@ export async function searchCollections(
const responseData: IDataObject = await azureCosmosDbRequest.call(this, opts);
const responseBody = responseData as {
Collections: IDataObject[];
DocumentCollections: IDataObject[];
};
const collections = responseBody.Collections;
const collections = responseBody.DocumentCollections;
if (!collections) {
return { results: [] };

View file

@ -49,7 +49,7 @@ export const containerOperations: INodeProperties[] = [
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}',
},
},
action: 'Get item',
action: 'Get container',
},
{
name: 'Get Many',
@ -159,12 +159,9 @@ export const createFields: INodeProperties[] = [
},
},
{
displayName: 'Additional Keys',
name: 'additionalKeys',
type: 'fixedCollection',
displayName: 'Additional Fields',
name: 'additionalFields',
default: {},
placeholder: '"paths": ["/AccountNumber"],"kind": "Hash", "Version": 2',
description: 'User-defined JSON object representing the document properties',
displayOptions: {
show: {
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[] = [
{
@ -15,12 +17,12 @@ export const itemOperations: INodeProperties[] = [
{
name: 'Create',
value: 'create',
description: 'Create an item',
description: 'Create a new item',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'POST',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs',
},
},
action: 'Create item',
@ -28,12 +30,12 @@ export const itemOperations: INodeProperties[] = [
{
name: 'Delete',
value: 'delete',
description: 'Delete an item',
description: 'Delete an existing item',
routing: {
request: {
ignoreHttpStatusErrors: true,
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',
@ -46,7 +48,7 @@ export const itemOperations: INodeProperties[] = [
request: {
ignoreHttpStatusErrors: true,
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',
@ -56,6 +58,12 @@ export const itemOperations: INodeProperties[] = [
value: 'getAll',
description: 'Retrieve a list of items',
routing: {
send: {
paginate: true,
},
operations: {
pagination: handlePagination,
},
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
@ -72,7 +80,11 @@ export const itemOperations: INodeProperties[] = [
request: {
ignoreHttpStatusErrors: true,
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',
@ -80,15 +92,15 @@ export const itemOperations: INodeProperties[] = [
{
name: 'Update',
value: 'update',
description: 'Update an item',
description: 'Update an existing item',
routing: {
request: {
ignoreHttpStatusErrors: true,
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',
@ -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[] = [
{
displayName: 'Database ID',
@ -631,23 +692,63 @@ export const queryFields: INodeProperties[] = [
],
},
{
displayName: 'ID',
name: 'id',
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: "Item's ID",
required: true,
description: 'The SQL query text to execute',
displayOptions: {
show: {
resource: ['item'],
operation: ['query'],
},
},
placeholder: 'SELECT * FROM c WHERE c.name = @name',
routing: {
send: {
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}}',
},
},