n8n/packages/nodes-base/nodes/Gong/GenericFunctions.ts
2024-10-16 10:23:09 +01:00

228 lines
6.8 KiB
TypeScript

import get from 'lodash/get';
import type {
DeclarativeRestApiSettings,
IDataObject,
IExecuteFunctions,
IExecutePaginationFunctions,
IExecuteSingleFunctions,
IHttpRequestMethods,
IHttpRequestOptions,
ILoadOptionsFunctions,
IN8nHttpFullResponse,
INodeExecutionData,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
export async function gongApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: IDataObject = {},
query: IDataObject = {},
) {
const authentication = this.getNodeParameter('authentication', 0) as 'accessToken' | 'oAuth2';
const credentialsType = authentication === 'oAuth2' ? 'gongOAuth2Api' : 'gongApi';
const { baseUrl } = await this.getCredentials<{
baseUrl: string;
}>(credentialsType);
const options: IHttpRequestOptions = {
method,
url: baseUrl.replace(new RegExp('/$'), '') + endpoint,
json: true,
headers: {
'Content-Type': 'application/json',
},
body,
qs: query,
};
if (Object.keys(body).length === 0) {
delete options.body;
}
return await this.helpers.requestWithAuthentication.call(this, credentialsType, options);
}
export async function gongApiPaginateRequest(
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: IDataObject = {},
query: IDataObject = {},
itemIndex: number = 0,
rootProperty: string | undefined = undefined,
): Promise<any> {
const authentication = this.getNodeParameter('authentication', 0) as 'accessToken' | 'oAuth2';
const credentialsType = authentication === 'oAuth2' ? 'gongOAuth2Api' : 'gongApi';
const { baseUrl } = await this.getCredentials<{
baseUrl: string;
}>(credentialsType);
const options: IHttpRequestOptions = {
method,
url: baseUrl.replace(new RegExp('/$'), '') + endpoint,
json: true,
headers: {
'Content-Type': 'application/json',
},
body,
qs: query,
};
if (Object.keys(body).length === 0) {
delete options.body;
}
const pages = await this.helpers.requestWithAuthenticationPaginated.call(
this,
options,
itemIndex,
{
requestInterval: 340, // Rate limit 3 calls per second
continue: '={{ $response.body.records.cursor }}',
request: {
[method === 'POST' ? 'body' : 'qs']:
'={{ $if($response.body?.records.cursor, { cursor: $response.body.records.cursor }, {}) }}',
url: options.url,
},
},
credentialsType,
);
if (rootProperty) {
let results: IDataObject[] = [];
for (const page of pages) {
const items = page.body[rootProperty];
if (items) {
results = results.concat(items);
}
}
return results;
} else {
return pages.flat();
}
}
const getCursorPaginator = (
extractItems: (items: INodeExecutionData[]) => INodeExecutionData[],
) => {
return async function cursorPagination(
this: IExecutePaginationFunctions,
requestOptions: DeclarativeRestApiSettings.ResultOptions,
): Promise<INodeExecutionData[]> {
let executions: INodeExecutionData[] = [];
let responseData: INodeExecutionData[];
let nextCursor: string | undefined = undefined;
const returnAll = this.getNodeParameter('returnAll', true) as boolean;
do {
(requestOptions.options.body as IDataObject).cursor = nextCursor;
responseData = await this.makeRoutingRequest(requestOptions);
const lastItem = responseData[responseData.length - 1].json;
nextCursor = (lastItem.records as IDataObject)?.cursor as string | undefined;
executions = executions.concat(extractItems(responseData));
} while (returnAll && nextCursor);
return executions;
};
};
export const extractCalls = (items: INodeExecutionData[]): INodeExecutionData[] => {
const calls: IDataObject[] = items.flatMap((item) => get(item.json, 'calls') as IDataObject[]);
return calls.map((call) => {
const { metaData, ...rest } = call ?? {};
return { json: { ...(metaData as IDataObject), ...rest } };
});
};
export const extractUsers = (items: INodeExecutionData[]): INodeExecutionData[] => {
const users: IDataObject[] = items.flatMap((item) => get(item.json, 'users') as IDataObject[]);
return users.map((user) => ({ json: user }));
};
export const getCursorPaginatorCalls = () => {
return getCursorPaginator(extractCalls);
};
export const getCursorPaginatorUsers = () => {
return getCursorPaginator(extractUsers);
};
export async function handleErrorPostReceive(
this: IExecuteSingleFunctions,
data: INodeExecutionData[],
response: IN8nHttpFullResponse,
): Promise<INodeExecutionData[]> {
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
const { resource, operation } = this.getNode().parameters;
if (resource === 'call') {
if (operation === 'get') {
if (response.statusCode === 404) {
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
message: "The required call doesn't match any existing one",
description: "Double-check the value in the parameter 'Call to Get' and try again",
});
}
} else if (operation === 'getAll') {
if (response.statusCode === 404) {
const primaryUserId = this.getNodeParameter('filters.primaryUserIds', {}) as IDataObject;
if (Object.keys(primaryUserId).length !== 0) {
return [{ json: {} }];
}
} else if (response.statusCode === 400 || response.statusCode === 500) {
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
description: 'Double-check the value(s) in the parameter(s)',
});
}
}
} else if (resource === 'user') {
if (operation === 'get') {
if (response.statusCode === 404) {
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
message: "The required user doesn't match any existing one",
description: "Double-check the value in the parameter 'User to Get' and try again",
});
}
} else if (operation === 'getAll') {
if (response.statusCode === 404) {
const userIds = this.getNodeParameter('filters.userIds', '') as string;
if (userIds) {
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
message: "The Users IDs don't match any existing user",
description: "Double-check the values in the parameter 'Users IDs' and try again",
});
}
}
}
}
throw new NodeApiError(this.getNode(), response as unknown as JsonObject);
}
return data;
}
export function isValidNumberIds(value: number | number[] | string | string[]): boolean {
if (typeof value === 'number') {
return true;
}
if (Array.isArray(value) && value.every((item) => typeof item === 'number')) {
return true;
}
if (typeof value === 'string') {
const parts = value.split(',');
return parts.every((part) => !isNaN(Number(part.trim())));
}
if (Array.isArray(value) && value.every((item) => typeof item === 'string')) {
return true;
}
return false;
}