mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 19:32:15 +00:00
✨ Nodes as JSON and authentication redesign (#2401)
* ✨ change FE to handle new object type * 🚸 improve UX of handling invalid credentials * 🚧 WIP * 🎨 fix typescript issues * 🐘 add migrations for all supported dbs * ✏️ add description to migrations * ⚡ add credential update on import * ⚡ resolve after merge issues * 👕 fix lint issues * ⚡ check credentials on workflow create/update * update interface * 👕 fix ts issues * ⚡ adaption to new credentials UI * 🐛 intialize cache on BE for credentials check * 🐛 fix undefined oldCredentials * 🐛 fix deleting credential * 🐛 fix check for undefined keys * 🐛 fix disabling edit in execution * 🎨 just show credential name on execution view * ✏️ remove TODO * ⚡ implement review suggestions * ⚡ add cache to getCredentialsByType * ⏪ use getter instead of cache * ✏️ fix variable name typo * 🐘 include waiting nodes to migrations * 🐛 fix reverting migrations command * ⚡ update typeorm command * ✨ create db:revert command * 👕 fix lint error * ✨ Add optional authenticate method to credentials * ⚡ Simplify code and add authentication support to MattermostApi * 👕 Fix lint issue * ⚡ Add support to own-mode * 👕 Fix lint issue * ✨ Add support for predefined auth types bearer and headerAuth * ⚡ Make sure that DateTime Node always returns strings * ⚡ Add support for moment types to If Node * ⚡ Make it possible for HTTP Request Node to use all credential types * ✨ Add basicAuth support * Add a new dropcontact node * ✨ First basic implementation of mainly JSON based nodes * ✨ Add fixedCollection support, added value parameter and expression support for value and property * Improvements to #2389 * ⚡ Add credentials verification * ⚡ Small improvement * ⚡ set default time to 45 seconds * ✨ Add support for preSend and postReceive methods * ➕ Add lodash merge and set depedency to workflow * 👕 Fix lint issue * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * 🐛 Set siren and language correctly * ⚡ Add support for requestDefaults * ⚡ Add support for baseURL to httpRequest * ⚡ Move baseURL to correct location * ✨ Add support for options loading * 🐛 Fix error with fullAccess nodes * ✨ Add credential test functionality * 🐛 Fix issue with OAuth autentication and lint issue * ⚡ Fix build issue * 🐛 Fix issue that url got always overwritten to empty * ✨ Add pagination support * ⚡ Code fix required after merge * ⚡ Remove not needed imports * ⚡ Fix credential test * ✨ Add expression support for request properties and $self support on properties * ⚡ Rename $self to $value * 👕 Fix lint issue * ⚡ Add example how to send data in path * ✨ Make it possible to not sent in dot notation * ✨ Add support for postReceive:rootProperty * ⚡ Fix typo * ✨ Add support for postReceive:set * ⚡ Some fixes * ⚡ Small improvement * ;zap: Separate RoutingNode code * ⚡ Simplify code and fix bug * ⚡ Remove unused code * ✨ Make it possible to define "request" and "requestProperty" on options * 👕 Fix lint issue * ⚡ Change $credentials variables name * ✨ Enable expressions and access to credentials in requestDefaults * ⚡ Make parameter option loading use RoutingNode.makeRoutingRequest * ✨ Allow requestOperations overwrite on LoadOptions * ✨ Make it possible to access current node parameters in loadOptions * ⚡ Rename parameters variable to make future proof * ⚡ Make it possible to use offset-pagination with body * ✨ Add support for queryAuth * ⚡ Never return more items than requested * ✨ Make it possible to overwrite requestOperations on parameter and option level * 👕 Fix lint issue * ✨ Allow simplified auth also with regular nodes * ✨ Add support for receiving binary data * 🐛 Fix example node * ⚡ Rename property "name" to "displayName" in loadOptions * ⚡ Send data by default as "query" if nothing is set * ⚡ Rename $self to $parent * ⚡ Change to work with INodeExecutionData instead of IDataObject * ⚡ Improve binaryData handling * ⚡ Property design improvements * ⚡ Fix property name * 🚨 Add some tests * ⚡ Add also test for request * ⚡ Improve test and fix issues * ⚡ Improvements to loadOptions * ⚡ Normalize loadOptions with rest of code * ⚡ Add info text * ✨ Add support for $value in postReceive * 🚨 Add tests for RoutingNode.runNode * ⚡ Remove TODOs and make url property optional * ⚡ Fix bug and lint issue * 🐛 Fix bug that not the correct property got used * 🚨 Add tests for CredentialsHelper.authenticate * ⚡ Improve code and resolve expressions also everywhere for loadOptions and credential test requests * ✨ Make it possible to define multiple preSend and postReceive actions * ✨ Allow to define tests on credentials * ⚡ Remove test data * ⬆️ Update package-lock.json file * ⚡ Remove old not longer used code Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com> Co-authored-by: Mutasem <mutdmour@gmail.com> Co-authored-by: PaulineDropcontact <pauline@dropcontact.io> Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
814
packages/workflow/src/RoutingNode.ts
Normal file
814
packages/workflow/src/RoutingNode.ts
Normal file
@@ -0,0 +1,814 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import get from 'lodash.get';
|
||||
import merge from 'lodash.merge';
|
||||
import set from 'lodash.set';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
IHttpRequestOptions,
|
||||
IN8nHttpFullResponse,
|
||||
INode,
|
||||
INodeExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
IRequestOptionsFromParameters,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IExecuteSingleFunctions,
|
||||
IN8nRequestOperations,
|
||||
INodeProperties,
|
||||
INodePropertyCollection,
|
||||
PostReceiveAction,
|
||||
} from './Interfaces';
|
||||
|
||||
export class RoutingNode {
|
||||
additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
connectionInputData: INodeExecutionData[];
|
||||
|
||||
node: INode;
|
||||
|
||||
mode: WorkflowExecuteMode;
|
||||
|
||||
runExecutionData: IRunExecutionData;
|
||||
|
||||
workflow: Workflow;
|
||||
|
||||
constructor(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
runExecutionData: IRunExecutionData,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
) {
|
||||
this.additionalData = additionalData;
|
||||
this.connectionInputData = connectionInputData;
|
||||
this.runExecutionData = runExecutionData;
|
||||
this.mode = mode;
|
||||
this.node = node;
|
||||
this.workflow = workflow;
|
||||
}
|
||||
|
||||
async runNode(
|
||||
inputData: ITaskDataConnections,
|
||||
runIndex: number,
|
||||
nodeType: INodeType,
|
||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
): Promise<INodeExecutionData[][] | null | undefined> {
|
||||
const items = inputData.main[0] as INodeExecutionData[];
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
let responseData;
|
||||
|
||||
let credentialType: string | undefined;
|
||||
|
||||
if (nodeType.description.credentials?.length) {
|
||||
credentialType = nodeType.description.credentials[0].name;
|
||||
}
|
||||
const executeFunctions = nodeExecuteFunctions.getExecuteFunctions(
|
||||
this.workflow,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.connectionInputData,
|
||||
inputData,
|
||||
this.node,
|
||||
this.additionalData,
|
||||
this.mode,
|
||||
);
|
||||
|
||||
let credentials: ICredentialDataDecryptedObject | undefined;
|
||||
if (credentialsDecrypted) {
|
||||
credentials = credentialsDecrypted.data;
|
||||
} else if (credentialType) {
|
||||
credentials = (await executeFunctions.getCredentials(credentialType)) || {};
|
||||
}
|
||||
|
||||
// TODO: Think about how batching could be handled for REST APIs which support it
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
|
||||
this.workflow,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.connectionInputData,
|
||||
inputData,
|
||||
this.node,
|
||||
i,
|
||||
this.additionalData,
|
||||
this.mode,
|
||||
);
|
||||
|
||||
const requestData: IRequestOptionsFromParameters = {
|
||||
options: {
|
||||
qs: {},
|
||||
body: {},
|
||||
},
|
||||
preSend: [],
|
||||
postReceive: [],
|
||||
requestOperations: {},
|
||||
};
|
||||
|
||||
if (nodeType.description.requestOperations) {
|
||||
requestData.requestOperations = { ...nodeType.description.requestOperations };
|
||||
}
|
||||
|
||||
if (nodeType.description.requestDefaults) {
|
||||
for (const key of Object.keys(nodeType.description.requestDefaults)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let value = (nodeType.description.requestDefaults as Record<string, any>)[key];
|
||||
// If the value is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
value,
|
||||
i,
|
||||
runIndex,
|
||||
{ $credentials: credentials },
|
||||
true,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(requestData.options as Record<string, any>)[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const property of nodeType.description.properties) {
|
||||
let value = get(this.node.parameters, property.name, []) as string | NodeParameterValue;
|
||||
// If the value is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
value,
|
||||
i,
|
||||
runIndex,
|
||||
{ $credentials: credentials },
|
||||
true,
|
||||
) as string | NodeParameterValue;
|
||||
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
thisArgs,
|
||||
property,
|
||||
i,
|
||||
runIndex,
|
||||
'',
|
||||
{ $credentials: credentials, $value: value },
|
||||
);
|
||||
|
||||
this.mergeOptions(requestData, tempOptions);
|
||||
}
|
||||
|
||||
// TODO: Change to handle some requests in parallel (should be configurable)
|
||||
responseData = await this.makeRoutingRequest(
|
||||
requestData,
|
||||
thisArgs,
|
||||
i,
|
||||
runIndex,
|
||||
credentialType,
|
||||
requestData.requestOperations,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
|
||||
if (requestData.maxResults) {
|
||||
// Remove not needed items in case APIs return to many
|
||||
responseData.splice(requestData.maxResults as number);
|
||||
}
|
||||
|
||||
returnData.push(...responseData);
|
||||
} catch (error) {
|
||||
if (get(this.node, 'continueOnFail', false)) {
|
||||
returnData.push({ json: {}, error: error.message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
|
||||
mergeOptions(
|
||||
destinationOptions: IRequestOptionsFromParameters,
|
||||
sourceOptions?: IRequestOptionsFromParameters,
|
||||
): void {
|
||||
if (sourceOptions) {
|
||||
destinationOptions.paginate = destinationOptions.paginate ?? sourceOptions.paginate;
|
||||
destinationOptions.maxResults = sourceOptions.maxResults
|
||||
? sourceOptions.maxResults
|
||||
: destinationOptions.maxResults;
|
||||
merge(destinationOptions.options, sourceOptions.options);
|
||||
destinationOptions.preSend.push(...sourceOptions.preSend);
|
||||
destinationOptions.postReceive.push(...sourceOptions.postReceive);
|
||||
if (sourceOptions.requestOperations) {
|
||||
destinationOptions.requestOperations = Object.assign(
|
||||
destinationOptions.requestOperations,
|
||||
sourceOptions.requestOperations,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runPostReceiveAction(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
action: PostReceiveAction,
|
||||
inputData: INodeExecutionData[],
|
||||
responseData: IN8nHttpFullResponse,
|
||||
parameterValue: string | IDataObject | undefined,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
if (typeof action === 'function') {
|
||||
return action.call(executeSingleFunctions, inputData, responseData);
|
||||
}
|
||||
if (action.type === 'rootProperty') {
|
||||
try {
|
||||
return inputData.flatMap((item) => {
|
||||
// let itemContent = item.json[action.properties.property];
|
||||
let itemContent = get(item.json, action.properties.property);
|
||||
|
||||
if (!Array.isArray(itemContent)) {
|
||||
itemContent = [itemContent];
|
||||
}
|
||||
return (itemContent as IDataObject[]).map((json) => {
|
||||
return {
|
||||
json,
|
||||
};
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`The rootProperty "${action.properties.property}" could not be found on item.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (action.type === 'set') {
|
||||
const { value } = action.properties;
|
||||
// If the value is an expression resolve it
|
||||
return [
|
||||
{
|
||||
json: this.getParameterValue(
|
||||
value,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ $response: responseData, $value: parameterValue },
|
||||
false,
|
||||
) as IDataObject,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (action.type === 'sort') {
|
||||
// Sort the returned options
|
||||
const sortKey = action.properties.key;
|
||||
inputData.sort((a, b) => {
|
||||
const aSortValue = a.json[sortKey]
|
||||
? (a.json[sortKey]?.toString().toLowerCase() as string)
|
||||
: '';
|
||||
const bSortValue = b.json[sortKey]
|
||||
? (b.json[sortKey]?.toString().toLowerCase() as string)
|
||||
: '';
|
||||
if (aSortValue < bSortValue) {
|
||||
return -1;
|
||||
}
|
||||
if (aSortValue > bSortValue) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return inputData;
|
||||
}
|
||||
if (action.type === 'setKeyValue') {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
inputData.forEach((item) => {
|
||||
const returnItem: IDataObject = {};
|
||||
for (const key of Object.keys(action.properties)) {
|
||||
let propertyValue = (
|
||||
action.properties as Record<
|
||||
string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
any
|
||||
>
|
||||
)[key];
|
||||
// If the value is an expression resolve it
|
||||
propertyValue = this.getParameterValue(
|
||||
propertyValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{
|
||||
$response: responseData,
|
||||
$responseItem: item.json,
|
||||
$value: parameterValue,
|
||||
},
|
||||
true,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(returnItem as Record<string, any>)[key] = propertyValue;
|
||||
}
|
||||
returnData.push({ json: returnItem });
|
||||
});
|
||||
|
||||
return returnData;
|
||||
}
|
||||
if (action.type === 'binaryData') {
|
||||
responseData.body = Buffer.from(responseData.body as string);
|
||||
let { destinationProperty } = action.properties;
|
||||
|
||||
destinationProperty = this.getParameterValue(
|
||||
destinationProperty,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ $response: responseData, $value: parameterValue },
|
||||
false,
|
||||
) as string;
|
||||
|
||||
const binaryData = await executeSingleFunctions.helpers.prepareBinaryData(responseData.body);
|
||||
|
||||
return inputData.map((item) => {
|
||||
if (typeof item.json === 'string') {
|
||||
// By default is probably the binary data as string set, in this case remove it
|
||||
item.json = {};
|
||||
}
|
||||
|
||||
item.binary = {
|
||||
[destinationProperty]: binaryData,
|
||||
};
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async rawRoutingRequest(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
requestData: IRequestOptionsFromParameters,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
credentialType?: string,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let responseData: IN8nHttpFullResponse;
|
||||
requestData.options.returnFullResponse = true;
|
||||
|
||||
if (credentialType) {
|
||||
responseData = (await executeSingleFunctions.helpers.httpRequestWithAuthentication.call(
|
||||
executeSingleFunctions,
|
||||
credentialType,
|
||||
requestData.options as IHttpRequestOptions,
|
||||
{ credentialsDecrypted },
|
||||
)) as IN8nHttpFullResponse;
|
||||
} else {
|
||||
responseData = (await executeSingleFunctions.helpers.httpRequest(
|
||||
requestData.options as IHttpRequestOptions,
|
||||
)) as IN8nHttpFullResponse;
|
||||
}
|
||||
|
||||
let returnData: INodeExecutionData[] = [
|
||||
{
|
||||
json: responseData.body as IDataObject,
|
||||
},
|
||||
];
|
||||
|
||||
if (requestData.postReceive.length) {
|
||||
// If postReceive functionality got defined execute all of them
|
||||
for (const postReceiveMethod of requestData.postReceive) {
|
||||
for (const action of postReceiveMethod.actions) {
|
||||
returnData = await this.runPostReceiveAction(
|
||||
executeSingleFunctions,
|
||||
action,
|
||||
returnData,
|
||||
responseData,
|
||||
postReceiveMethod.data.parameterValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No postReceive functionality got defined so simply add data as it is
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (Array.isArray(responseData.body)) {
|
||||
returnData = responseData.body.map((json) => {
|
||||
return {
|
||||
json,
|
||||
} as INodeExecutionData;
|
||||
});
|
||||
} else {
|
||||
returnData[0].json = responseData.body as IDataObject;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
async makeRoutingRequest(
|
||||
requestData: IRequestOptionsFromParameters,
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
credentialType?: string,
|
||||
requestOperations?: IN8nRequestOperations,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let responseData: INodeExecutionData[];
|
||||
for (const preSendMethod of requestData.preSend) {
|
||||
requestData.options = await preSendMethod.call(
|
||||
executeSingleFunctions,
|
||||
requestData.options as IHttpRequestOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const executePaginationFunctions = {
|
||||
...executeSingleFunctions,
|
||||
makeRoutingRequest: async (requestOptions: IRequestOptionsFromParameters) => {
|
||||
return this.rawRoutingRequest(
|
||||
executeSingleFunctions,
|
||||
requestOptions,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
credentialType,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
if (requestData.paginate && requestOperations?.pagination) {
|
||||
// Has pagination
|
||||
|
||||
if (typeof requestOperations.pagination === 'function') {
|
||||
// Pagination via function
|
||||
responseData = await requestOperations.pagination.call(
|
||||
executePaginationFunctions,
|
||||
requestData,
|
||||
);
|
||||
} else {
|
||||
// Pagination via JSON properties
|
||||
const { properties } = requestOperations.pagination;
|
||||
responseData = [];
|
||||
if (!requestData.options.qs) {
|
||||
requestData.options.qs = {};
|
||||
}
|
||||
|
||||
// Different predefined pagination types
|
||||
if (requestOperations.pagination.type === 'offset') {
|
||||
const optionsType = properties.type === 'body' ? 'body' : 'qs';
|
||||
if (properties.type === 'body' && !requestData.options.body) {
|
||||
requestData.options.body = {};
|
||||
}
|
||||
|
||||
(requestData.options[optionsType] as IDataObject)[properties.limitParameter] =
|
||||
properties.pageSize;
|
||||
(requestData.options[optionsType] as IDataObject)[properties.offsetParameter] = 0;
|
||||
let tempResponseData: INodeExecutionData[];
|
||||
do {
|
||||
if (requestData?.maxResults) {
|
||||
// Only request as many results as needed
|
||||
const resultsMissing = (requestData?.maxResults as number) - responseData.length;
|
||||
if (resultsMissing < 1) {
|
||||
break;
|
||||
}
|
||||
(requestData.options[optionsType] as IDataObject)[properties.limitParameter] =
|
||||
Math.min(properties.pageSize, resultsMissing);
|
||||
}
|
||||
|
||||
tempResponseData = await this.rawRoutingRequest(
|
||||
executeSingleFunctions,
|
||||
requestData,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
credentialType,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
|
||||
(requestData.options[optionsType] as IDataObject)[properties.offsetParameter] =
|
||||
((requestData.options[optionsType] as IDataObject)[
|
||||
properties.offsetParameter
|
||||
] as number) + properties.pageSize;
|
||||
|
||||
if (properties.rootProperty) {
|
||||
const tempResponseValue = get(tempResponseData[0].json, properties.rootProperty) as
|
||||
| IDataObject[]
|
||||
| undefined;
|
||||
if (tempResponseValue === undefined) {
|
||||
throw new Error(
|
||||
`The rootProperty "${properties.rootProperty}" could not be found on item.`,
|
||||
);
|
||||
}
|
||||
|
||||
tempResponseData = tempResponseValue.map((item) => {
|
||||
return {
|
||||
json: item,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
responseData.push(...tempResponseData);
|
||||
} while (tempResponseData.length && tempResponseData.length === properties.pageSize);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No pagination
|
||||
responseData = await this.rawRoutingRequest(
|
||||
executeSingleFunctions,
|
||||
requestData,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
credentialType,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
}
|
||||
return responseData;
|
||||
}
|
||||
|
||||
getParameterValue(
|
||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||
returnObjectAsString = false,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | string {
|
||||
if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
|
||||
return this.workflow.expression.getParameterValue(
|
||||
parameterValue,
|
||||
this.runExecutionData ?? null,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
this.node.name,
|
||||
this.connectionInputData,
|
||||
this.mode,
|
||||
additionalKeys ?? {},
|
||||
returnObjectAsString,
|
||||
);
|
||||
}
|
||||
|
||||
return parameterValue;
|
||||
}
|
||||
|
||||
getRequestOptionsFromParameters(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
nodeProperties: INodeProperties | INodePropertyOptions,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
path: string,
|
||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||
): IRequestOptionsFromParameters | undefined {
|
||||
const returnData: IRequestOptionsFromParameters = {
|
||||
options: {
|
||||
qs: {},
|
||||
body: {},
|
||||
},
|
||||
preSend: [],
|
||||
postReceive: [],
|
||||
requestOperations: {},
|
||||
};
|
||||
let basePath = path ? `${path}.` : '';
|
||||
|
||||
if (!NodeHelpers.displayParameter(this.node.parameters, nodeProperties, this.node.parameters)) {
|
||||
return undefined;
|
||||
}
|
||||
if (nodeProperties.routing) {
|
||||
let parameterValue: string | undefined;
|
||||
if (basePath + nodeProperties.name && 'type' in nodeProperties) {
|
||||
parameterValue = executeSingleFunctions.getNodeParameter(
|
||||
basePath + nodeProperties.name,
|
||||
) as string;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.operations) {
|
||||
returnData.requestOperations = { ...nodeProperties.routing.operations };
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.request) {
|
||||
for (const key of Object.keys(nodeProperties.routing.request)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let propertyValue = (nodeProperties.routing.request as Record<string, any>)[key];
|
||||
// If the value is an expression resolve it
|
||||
propertyValue = this.getParameterValue(
|
||||
propertyValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: parameterValue },
|
||||
true,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(returnData.options as Record<string, any>)[key] = propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send) {
|
||||
let propertyName = nodeProperties.routing.send.property;
|
||||
if (propertyName !== undefined) {
|
||||
// If the propertyName is an expression resolve it
|
||||
propertyName = this.getParameterValue(
|
||||
propertyName,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
additionalKeys,
|
||||
true,
|
||||
) as string;
|
||||
|
||||
let value = parameterValue;
|
||||
|
||||
if (nodeProperties.routing.send.value) {
|
||||
const valueString = nodeProperties.routing.send.value;
|
||||
// Special value got set
|
||||
// If the valueString is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
valueString,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: value },
|
||||
true,
|
||||
) as string;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send.type === 'body') {
|
||||
// Send in "body"
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (nodeProperties.routing.send.propertyInDotNotation === false) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(returnData.options.body as Record<string, any>)![propertyName] = value;
|
||||
} else {
|
||||
set(returnData.options.body as object, propertyName, value);
|
||||
}
|
||||
} else {
|
||||
// Send in "query"
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (nodeProperties.routing.send.propertyInDotNotation === false) {
|
||||
returnData.options.qs![propertyName] = value;
|
||||
} else {
|
||||
set(returnData.options.qs as object, propertyName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send.paginate !== undefined) {
|
||||
let paginateValue = nodeProperties.routing.send.paginate;
|
||||
if (typeof paginateValue === 'string' && paginateValue.charAt(0) === '=') {
|
||||
// If the propertyName is an expression resolve it
|
||||
paginateValue = this.getParameterValue(
|
||||
paginateValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: parameterValue },
|
||||
true,
|
||||
) as string;
|
||||
}
|
||||
|
||||
returnData.paginate = !!paginateValue;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send.preSend) {
|
||||
returnData.preSend.push(...nodeProperties.routing.send.preSend);
|
||||
}
|
||||
}
|
||||
if (nodeProperties.routing.output) {
|
||||
if (nodeProperties.routing.output.maxResults !== undefined) {
|
||||
let maxResultsValue = nodeProperties.routing.output.maxResults;
|
||||
if (typeof maxResultsValue === 'string' && maxResultsValue.charAt(0) === '=') {
|
||||
// If the propertyName is an expression resolve it
|
||||
maxResultsValue = this.getParameterValue(
|
||||
maxResultsValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: parameterValue },
|
||||
true,
|
||||
) as string;
|
||||
}
|
||||
|
||||
returnData.maxResults = maxResultsValue;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.output.postReceive) {
|
||||
returnData.postReceive.push({
|
||||
data: {
|
||||
parameterValue,
|
||||
},
|
||||
actions: nodeProperties.routing.output.postReceive,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are any child properties
|
||||
if (!Object.prototype.hasOwnProperty.call(nodeProperties, 'options')) {
|
||||
// There are none so nothing else to check
|
||||
return returnData;
|
||||
}
|
||||
|
||||
// Everything after this point can only be of type INodeProperties
|
||||
nodeProperties = nodeProperties as INodeProperties;
|
||||
|
||||
// Check the child parameters
|
||||
let value;
|
||||
if (nodeProperties.type === 'options') {
|
||||
const optionValue = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
nodeProperties.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
||||
// Find the selected option
|
||||
const selectedOption = (nodeProperties.options as INodePropertyOptions[]).filter(
|
||||
(option) => option.value === optionValue,
|
||||
);
|
||||
|
||||
if (selectedOption.length) {
|
||||
// Check only if option is set and if of type INodeProperties
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
executeSingleFunctions,
|
||||
selectedOption[0],
|
||||
itemIndex,
|
||||
runIndex,
|
||||
`${basePath}${nodeProperties.name}`,
|
||||
{ $value: optionValue },
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
} else if (nodeProperties.type === 'collection') {
|
||||
value = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
nodeProperties.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
||||
for (const propertyOption of nodeProperties.options as INodeProperties[]) {
|
||||
if (
|
||||
Object.keys(value as IDataObject).includes(propertyOption.name) &&
|
||||
propertyOption.type !== undefined
|
||||
) {
|
||||
// Check only if option is set and if of type INodeProperties
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
executeSingleFunctions,
|
||||
propertyOption,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
`${basePath}${nodeProperties.name}`,
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
}
|
||||
} else if (nodeProperties.type === 'fixedCollection') {
|
||||
basePath = `${basePath}${nodeProperties.name}.`;
|
||||
for (const propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||
// Check if the option got set and if not skip it
|
||||
value = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
propertyOptions.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure that it is always an array to be able to use the same code for multi and single
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
|
||||
const loopBasePath = `${basePath}${propertyOptions.name}`;
|
||||
for (let i = 0; i < (value as INodeParameters[]).length; i++) {
|
||||
for (const option of propertyOptions.values) {
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
executeSingleFunctions,
|
||||
option,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
nodeProperties.typeOptions?.multipleValues ? `${loopBasePath}[${i}]` : loopBasePath,
|
||||
{ ...(additionalKeys || {}), $index: i, $parent: value[i] },
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user