mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +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:
@@ -30,6 +30,7 @@
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.merge": "^4.6.6",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/xml2js": "^0.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||
@@ -48,6 +49,8 @@
|
||||
"dependencies": {
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"riot-tmpl": "^3.0.8",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
|
||||
@@ -12,8 +12,14 @@ import { WorkflowHooks } from './WorkflowHooks';
|
||||
import { WorkflowOperationError } from './WorkflowErrors';
|
||||
import { NodeApiError, NodeOperationError } from './NodeErrors';
|
||||
|
||||
export interface IAdditionalCredentialOptions {
|
||||
oauth2?: IOAuth2Options;
|
||||
credentialsDecrypted?: ICredentialsDecrypted;
|
||||
}
|
||||
|
||||
export type IAllExecuteFunctions =
|
||||
| IExecuteFunctions
|
||||
| IExecutePaginationFunctions
|
||||
| IExecuteSingleFunctions
|
||||
| IHookFunctions
|
||||
| ILoadOptionsFunctions
|
||||
@@ -128,6 +134,17 @@ export interface ICredentialsExpressionResolveValues {
|
||||
workflow: Workflow;
|
||||
}
|
||||
|
||||
// Simplified options of request library
|
||||
export interface IRequestOptionsSimplified {
|
||||
auth?: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
body: IDataObject;
|
||||
headers: IDataObject;
|
||||
qs: IDataObject;
|
||||
}
|
||||
|
||||
export abstract class ICredentialsHelper {
|
||||
encryptionKey: string;
|
||||
|
||||
@@ -135,6 +152,16 @@ export abstract class ICredentialsHelper {
|
||||
this.encryptionKey = encryptionKey;
|
||||
}
|
||||
|
||||
abstract getParentTypes(name: string): string[];
|
||||
|
||||
abstract authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestOptions: IHttpRequestOptions | IRequestOptionsSimplified,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
): Promise<IHttpRequestOptions>;
|
||||
|
||||
abstract getCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
@@ -155,6 +182,80 @@ export abstract class ICredentialsHelper {
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAuthenticateBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateBasicAuth extends IAuthenticateBase {
|
||||
type: 'basicAuth';
|
||||
properties: {
|
||||
userPropertyName?: string;
|
||||
passwordPropertyName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateBearer extends IAuthenticateBase {
|
||||
type: 'bearer';
|
||||
properties: {
|
||||
tokenPropertyName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateHeaderAuth extends IAuthenticateBase {
|
||||
type: 'headerAuth';
|
||||
properties: {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateQueryAuth extends IAuthenticateBase {
|
||||
type: 'queryAuth';
|
||||
properties: {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type IAuthenticate =
|
||||
| ((
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
) => Promise<IHttpRequestOptions>)
|
||||
| IAuthenticateBasicAuth
|
||||
| IAuthenticateBearer
|
||||
| IAuthenticateHeaderAuth
|
||||
| IAuthenticateQueryAuth;
|
||||
|
||||
export interface IAuthenticateRuleBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface IAuthenticateRuleResponseCode extends IAuthenticateRuleBase {
|
||||
type: 'responseCode';
|
||||
properties: {
|
||||
value: number;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICredentialTestRequest {
|
||||
request: IHttpRequestOptions;
|
||||
rules?: IAuthenticateRuleResponseCode[];
|
||||
}
|
||||
|
||||
export interface ICredentialTestRequestData {
|
||||
nodeType?: INodeType;
|
||||
testRequest: ICredentialTestRequest;
|
||||
}
|
||||
|
||||
export interface ICredentialType {
|
||||
name: string;
|
||||
displayName: string;
|
||||
@@ -163,13 +264,13 @@ export interface ICredentialType {
|
||||
properties: INodeProperties[];
|
||||
documentationUrl?: string;
|
||||
__overwrittenProperties?: string[];
|
||||
authenticate?: IAuthenticate;
|
||||
test?: ICredentialTestRequest;
|
||||
}
|
||||
|
||||
export interface ICredentialTypes {
|
||||
credentialTypes?: {
|
||||
[key: string]: ICredentialType;
|
||||
};
|
||||
init(credentialTypes?: { [key: string]: ICredentialType }): Promise<void>;
|
||||
credentialTypes?: ICredentialTypeData;
|
||||
init(credentialTypes?: ICredentialTypeData): Promise<void>;
|
||||
getAll(): ICredentialType[];
|
||||
getByName(credentialType: string): ICredentialType;
|
||||
}
|
||||
@@ -301,10 +402,13 @@ export interface IExecuteContextData {
|
||||
[key: string]: IContextObject;
|
||||
}
|
||||
|
||||
export type IHttpRequestMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT';
|
||||
|
||||
export interface IHttpRequestOptions {
|
||||
url: string;
|
||||
baseURL?: string;
|
||||
headers?: IDataObject;
|
||||
method?: 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT';
|
||||
method?: IHttpRequestMethods;
|
||||
body?: FormData | GenericValue | GenericValue[] | Buffer | URLSearchParams;
|
||||
qs?: IDataObject;
|
||||
arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma';
|
||||
@@ -338,6 +442,33 @@ export interface IN8nHttpFullResponse {
|
||||
statusMessage?: string;
|
||||
}
|
||||
|
||||
export interface IN8nRequestOperations {
|
||||
pagination?:
|
||||
| IN8nRequestOperationPaginationOffset
|
||||
| ((
|
||||
this: IExecutePaginationFunctions,
|
||||
requestOptions: IRequestOptionsFromParameters,
|
||||
) => Promise<INodeExecutionData[]>);
|
||||
}
|
||||
|
||||
export interface IN8nRequestOperationPaginationBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IN8nRequestOperationPaginationOffset extends IN8nRequestOperationPaginationBase {
|
||||
type: 'offset';
|
||||
properties: {
|
||||
limitParameter: string;
|
||||
offsetParameter: string;
|
||||
pageSize: number;
|
||||
rootProperty?: string; // Optional Path to option array
|
||||
type: 'body' | 'query';
|
||||
};
|
||||
}
|
||||
|
||||
export interface IExecuteFunctions {
|
||||
continueOnFail(): boolean;
|
||||
evaluateExpression(
|
||||
@@ -382,6 +513,12 @@ export interface IExecuteFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
@@ -410,15 +547,32 @@ export interface IExecuteSingleFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface IExecutePaginationFunctions extends IExecuteSingleFunctions {
|
||||
makeRoutingRequest(
|
||||
this: IAllExecuteFunctions,
|
||||
requestOptions: IRequestOptionsFromParameters,
|
||||
): Promise<INodeExecutionData[]>;
|
||||
}
|
||||
export interface IExecuteWorkflowInfo {
|
||||
code?: IWorkflowBase;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export type ICredentialTestFunction = (
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
) => Promise<INodeCredentialTestResult>;
|
||||
|
||||
export interface ICredentialTestFunctions {
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any;
|
||||
@@ -448,6 +602,20 @@ export interface ILoadOptionsFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
// TODO: Remove from here. Add it only now to LoadOptions as many nodes do import
|
||||
// from n8n-workflow instead of n8n-core
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: any, // tslint:disable-line:no-any
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: ((...args: any[]) => any) | undefined; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
@@ -471,6 +639,12 @@ export interface IHookFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
@@ -493,6 +667,12 @@ export interface IPollFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
@@ -518,6 +698,12 @@ export interface ITriggerFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
@@ -549,6 +735,12 @@ export interface IWebhookFunctions {
|
||||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
@@ -636,12 +828,21 @@ export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||
|
||||
export type EditorTypes = 'code' | 'json';
|
||||
|
||||
export interface ILoadOptions {
|
||||
routing?: {
|
||||
operations?: IN8nRequestOperations;
|
||||
output?: INodeRequestOutput;
|
||||
request?: IHttpRequestOptionsFromParameters;
|
||||
};
|
||||
}
|
||||
|
||||
export interface INodePropertyTypeOptions {
|
||||
alwaysOpenEditWindow?: boolean; // Supported by: string
|
||||
codeAutocomplete?: CodeAutocompleteTypes; // Supported by: string
|
||||
editor?: EditorTypes; // Supported by: string
|
||||
loadOptionsDependsOn?: string[]; // Supported by: options
|
||||
loadOptionsMethod?: string; // Supported by: options
|
||||
loadOptions?: ILoadOptions; // Supported by: options
|
||||
maxValue?: number; // Supported by: number
|
||||
minValue?: number; // Supported by: number
|
||||
multipleValues?: boolean; // Supported by: <All>
|
||||
@@ -651,7 +852,7 @@ export interface INodePropertyTypeOptions {
|
||||
rows?: number; // Supported by: string
|
||||
showAlpha?: boolean; // Supported by: color
|
||||
sortable?: boolean; // Supported when "multipleValues" set to true
|
||||
[key: string]: boolean | number | string | EditorTypes | undefined | string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IDisplayOptions {
|
||||
@@ -677,11 +878,13 @@ export interface INodeProperties {
|
||||
isNodeSetting?: boolean;
|
||||
noDataExpression?: boolean;
|
||||
required?: boolean;
|
||||
routing?: INodePropertyRouting;
|
||||
}
|
||||
export interface INodePropertyOptions {
|
||||
name: string;
|
||||
value: string | number | boolean;
|
||||
description?: string;
|
||||
routing?: INodePropertyRouting;
|
||||
}
|
||||
|
||||
export interface INodePropertyCollection {
|
||||
@@ -723,10 +926,7 @@ export interface INodeType {
|
||||
};
|
||||
credentialTest?: {
|
||||
// Contains a group of functins that test credentials.
|
||||
[functionName: string]: (
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
) => Promise<NodeCredentialTestResult>;
|
||||
[functionName: string]: ICredentialTestFunction;
|
||||
};
|
||||
};
|
||||
webhookMethods?: {
|
||||
@@ -742,12 +942,12 @@ export interface INodeVersionedType {
|
||||
description: INodeTypeBaseDescription;
|
||||
getNodeType: (version?: number) => INodeType;
|
||||
}
|
||||
export interface NodeCredentialTestResult {
|
||||
export interface INodeCredentialTestResult {
|
||||
status: 'OK' | 'Error';
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface NodeCredentialTestRequest {
|
||||
export interface INodeCredentialTestRequest {
|
||||
nodeToTestWith?: string; // node name i.e. slack
|
||||
credentials: ICredentialsDecrypted;
|
||||
}
|
||||
@@ -765,7 +965,7 @@ export interface INodeCredentialDescription {
|
||||
name: string;
|
||||
required?: boolean;
|
||||
displayOptions?: IDisplayOptions;
|
||||
testedBy?: string; // Name of a function inside `loadOptions.credentialTest`
|
||||
testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest`
|
||||
}
|
||||
|
||||
export type INodeIssueTypes = 'credentials' | 'execution' | 'parameters' | 'typeUnknown';
|
||||
@@ -804,6 +1004,105 @@ export interface INodeTypeBaseDescription {
|
||||
codex?: CodexData;
|
||||
}
|
||||
|
||||
export interface INodePropertyRouting {
|
||||
operations?: IN8nRequestOperations; // Should be changed, does not sound right
|
||||
output?: INodeRequestOutput;
|
||||
request?: IHttpRequestOptionsFromParameters;
|
||||
send?: INodeRequestSend;
|
||||
}
|
||||
|
||||
export type PostReceiveAction =
|
||||
| ((
|
||||
this: IExecuteSingleFunctions,
|
||||
items: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
) => Promise<INodeExecutionData[]>)
|
||||
| IPostReceiveBinaryData
|
||||
| IPostReceiveRootProperty
|
||||
| IPostReceiveSet
|
||||
| IPostReceiveSetKeyValue
|
||||
| IPostReceiveSort;
|
||||
|
||||
export type PreSendAction = (
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
) => Promise<IHttpRequestOptions>;
|
||||
|
||||
export interface INodeRequestOutput {
|
||||
maxResults?: number | string;
|
||||
postReceive?: PostReceiveAction[];
|
||||
}
|
||||
|
||||
export interface INodeRequestSend {
|
||||
preSend?: PreSendAction[];
|
||||
paginate?: boolean | string; // Where should this life?
|
||||
property?: string; // Maybe: propertyName, destinationProperty?
|
||||
propertyInDotNotation?: boolean; // Enabled by default
|
||||
type?: 'body' | 'query';
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface IPostReceiveBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string | number | IDataObject;
|
||||
};
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface IPostReceiveBinaryData extends IPostReceiveBase {
|
||||
type: 'binaryData';
|
||||
properties: {
|
||||
destinationProperty: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveRootProperty extends IPostReceiveBase {
|
||||
type: 'rootProperty';
|
||||
properties: {
|
||||
property: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveSet extends IPostReceiveBase {
|
||||
type: 'set';
|
||||
properties: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveSetKeyValue extends IPostReceiveBase {
|
||||
type: 'setKeyValue';
|
||||
properties: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveSort extends IPostReceiveBase {
|
||||
type: 'sort';
|
||||
properties: {
|
||||
key: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IHttpRequestOptionsFromParameters extends Partial<IHttpRequestOptions> {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface IRequestOptionsFromParameters {
|
||||
maxResults?: number | string;
|
||||
options: IHttpRequestOptionsFromParameters;
|
||||
paginate?: boolean | string;
|
||||
preSend: PreSendAction[];
|
||||
postReceive: Array<{
|
||||
data: {
|
||||
parameterValue: string | IDataObject | undefined;
|
||||
};
|
||||
actions: PostReceiveAction[];
|
||||
}>;
|
||||
requestOperations?: IN8nRequestOperations;
|
||||
}
|
||||
|
||||
export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||
version: number;
|
||||
defaults: INodeParameters;
|
||||
@@ -817,6 +1116,8 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||
credentials?: INodeCredentialDescription[];
|
||||
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
||||
polling?: boolean;
|
||||
requestDefaults?: IHttpRequestOptionsFromParameters;
|
||||
requestOperations?: IN8nRequestOperations;
|
||||
hooks?: {
|
||||
[key: string]: INodeHookDescription[] | undefined;
|
||||
activate?: INodeHookDescription[];
|
||||
@@ -869,9 +1170,7 @@ export interface IWorkflowDataProxyData {
|
||||
$workflow: any;
|
||||
}
|
||||
|
||||
export interface IWorkflowDataProxyAdditionalKeys {
|
||||
[key: string]: string | number | undefined;
|
||||
}
|
||||
export type IWorkflowDataProxyAdditionalKeys = IDataObject;
|
||||
|
||||
export interface IWorkflowMetadata {
|
||||
id?: number | string;
|
||||
@@ -898,6 +1197,13 @@ export interface INodeTypes {
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType | undefined;
|
||||
}
|
||||
|
||||
export interface ICredentialTypeData {
|
||||
[key: string]: {
|
||||
type: ICredentialType;
|
||||
sourcePath: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface INodeTypeData {
|
||||
[key: string]: {
|
||||
type: INodeType | INodeVersionedType;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
@@ -12,7 +13,7 @@
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable import/no-cycle */
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
|
||||
import {
|
||||
Expression,
|
||||
IConnections,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
ObservableObject,
|
||||
RoutingNode,
|
||||
WebhookSetupMethodNames,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
@@ -1078,7 +1080,11 @@ export class Workflow {
|
||||
}
|
||||
|
||||
let connectionInputData: INodeExecutionData[] = [];
|
||||
if (nodeType.execute || nodeType.executeSingle) {
|
||||
if (
|
||||
nodeType.execute ||
|
||||
nodeType.executeSingle ||
|
||||
(!nodeType.poll && !nodeType.trigger && !nodeType.webhook)
|
||||
) {
|
||||
// Only stop if first input is empty for execute & executeSingle runs. For all others run anyways
|
||||
// because then it is a trigger node. As they only pass data through and so the input-data
|
||||
// becomes output-data it has to be possible.
|
||||
@@ -1217,6 +1223,19 @@ export class Workflow {
|
||||
} else if (nodeType.webhook) {
|
||||
// For webhook nodes always simply pass the data through
|
||||
return inputData.main as INodeExecutionData[][];
|
||||
} else {
|
||||
// For nodes which have routing information on properties
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
this,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
return routingNode.runNode(inputData, runIndex, nodeType, nodeExecuteFunctions);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -8,6 +8,7 @@ export * from './Interfaces';
|
||||
export * from './Expression';
|
||||
export * from './NodeErrors';
|
||||
export * as TelemetryHelpers from './TelemetryHelpers';
|
||||
export * from './RoutingNode';
|
||||
export * from './Workflow';
|
||||
export * from './WorkflowDataProxy';
|
||||
export * from './WorkflowErrors';
|
||||
|
||||
@@ -1,14 +1,526 @@
|
||||
import get from 'lodash.get';
|
||||
import {
|
||||
CredentialInformation,
|
||||
IAdditionalCredentialOptions,
|
||||
IAllExecuteFunctions,
|
||||
IContextObject,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentials,
|
||||
ICredentialsEncrypted,
|
||||
ICredentialsHelper,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteResponsePromiseData,
|
||||
IExecuteSingleFunctions,
|
||||
IExecuteWorkflowInfo,
|
||||
IHttpRequestOptions,
|
||||
IN8nHttpFullResponse,
|
||||
IN8nHttpResponse,
|
||||
INode,
|
||||
INodeCredentialsDetails,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
IWorkflowBase,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowDataProxyData,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowDataProxy,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowHooks,
|
||||
} from '../src';
|
||||
|
||||
export interface INodeTypesObject {
|
||||
[key: string]: INodeType;
|
||||
}
|
||||
|
||||
export class Credentials extends ICredentials {
|
||||
hasNodeAccess(nodeType: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void {
|
||||
this.data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void {
|
||||
let fullData;
|
||||
try {
|
||||
fullData = this.getData(encryptionKey);
|
||||
} catch (e) {
|
||||
fullData = {};
|
||||
}
|
||||
|
||||
fullData[key] = data;
|
||||
|
||||
return this.setData(fullData, encryptionKey);
|
||||
}
|
||||
|
||||
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
||||
if (this.data === undefined) {
|
||||
throw new Error('No data is set so nothing can be returned.');
|
||||
}
|
||||
return JSON.parse(this.data);
|
||||
}
|
||||
|
||||
getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation {
|
||||
const fullData = this.getData(encryptionKey, nodeType);
|
||||
|
||||
if (fullData === null) {
|
||||
throw new Error(`No data was set.`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!fullData.hasOwnProperty(key)) {
|
||||
throw new Error(`No data for key "${key}" exists.`);
|
||||
}
|
||||
|
||||
return fullData[key];
|
||||
}
|
||||
|
||||
getDataToSave(): ICredentialsEncrypted {
|
||||
if (this.data === undefined) {
|
||||
throw new Error(`No credentials were set to save.`);
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
data: this.data,
|
||||
nodesAccess: this.nodesAccess,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
async authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestParams: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
getParentTypes(name: string): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getDecrypted(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
): Promise<ICredentialDataDecryptedObject> {
|
||||
return {};
|
||||
}
|
||||
|
||||
async getCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
): Promise<ICredentials> {
|
||||
return new Credentials({ id: null, name: '' }, '', [], '');
|
||||
}
|
||||
|
||||
async updateCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): Promise<void> {}
|
||||
}
|
||||
|
||||
export function getNodeParameter(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData | null,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
node: INode,
|
||||
parameterName: string,
|
||||
itemIndex: number,
|
||||
mode: WorkflowExecuteMode,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
||||
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||
if (nodeType === undefined) {
|
||||
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
|
||||
}
|
||||
|
||||
const value = get(node.parameters, parameterName, fallbackValue);
|
||||
|
||||
if (value === undefined) {
|
||||
throw new Error(`Could not get parameter "${parameterName}"!`);
|
||||
}
|
||||
|
||||
let returnData;
|
||||
try {
|
||||
returnData = workflow.expression.getParameterValue(
|
||||
value,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
);
|
||||
} catch (e) {
|
||||
e.message += ` [Error in parameter: "${parameterName}"]`;
|
||||
throw e;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function getExecuteFunctions(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
inputData: ITaskDataConnections,
|
||||
node: INode,
|
||||
itemIndex: number,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
): IExecuteFunctions {
|
||||
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
|
||||
return {
|
||||
continueOnFail: () => {
|
||||
return false;
|
||||
},
|
||||
evaluateExpression: (expression: string, itemIndex: number) => {
|
||||
return expression;
|
||||
},
|
||||
async executeWorkflow(
|
||||
workflowInfo: IExecuteWorkflowInfo,
|
||||
inputData?: INodeExecutionData[],
|
||||
): Promise<any> {
|
||||
return additionalData.executeWorkflow(workflowInfo, additionalData, inputData);
|
||||
},
|
||||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
},
|
||||
async getCredentials(
|
||||
type: string,
|
||||
itemIndex?: number,
|
||||
): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return {
|
||||
apiKey: '12345',
|
||||
};
|
||||
},
|
||||
getExecutionId: (): string => {
|
||||
return additionalData.executionId!;
|
||||
},
|
||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
||||
if (!inputData.hasOwnProperty(inputName)) {
|
||||
// Return empty array because else it would throw error when nothing is connected to input
|
||||
return [];
|
||||
}
|
||||
|
||||
if (inputData[inputName].length < inputIndex) {
|
||||
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
||||
}
|
||||
|
||||
if (inputData[inputName][inputIndex] === null) {
|
||||
// return [];
|
||||
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
||||
}
|
||||
|
||||
return inputData[inputName][inputIndex] as INodeExecutionData[];
|
||||
},
|
||||
getNodeParameter: (
|
||||
parameterName: string,
|
||||
itemIndex: number,
|
||||
fallbackValue?: any,
|
||||
):
|
||||
| NodeParameterValue
|
||||
| INodeParameters
|
||||
| NodeParameterValue[]
|
||||
| INodeParameters[]
|
||||
| object => {
|
||||
return getNodeParameter(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
node,
|
||||
parameterName,
|
||||
itemIndex,
|
||||
mode,
|
||||
{},
|
||||
fallbackValue,
|
||||
);
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getNode: () => {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
},
|
||||
getRestApiUrl: (): string => {
|
||||
return additionalData.restApiUrl;
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return additionalData.timezone;
|
||||
},
|
||||
getWorkflow: () => {
|
||||
return {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
};
|
||||
},
|
||||
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
|
||||
const dataProxy = new WorkflowDataProxy(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
{},
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
getWorkflowStaticData(type: string): IDataObject {
|
||||
return workflow.getStaticData(type, node);
|
||||
},
|
||||
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||
async putExecutionToWait(waitTill: Date): Promise<void> {
|
||||
runExecutionData.waitTill = waitTill;
|
||||
},
|
||||
sendMessageToUI(...args: any[]): void {
|
||||
if (mode !== 'manual') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (additionalData.sendMessageToUI) {
|
||||
additionalData.sendMessageToUI(node.name, args);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
console.error(`There was a problem sending messsage to UI: ${error.message}`);
|
||||
}
|
||||
},
|
||||
async sendResponse(response: IExecuteResponsePromiseData): Promise<void> {
|
||||
await additionalData.hooks?.executeHookFunctions('sendResponse', [response]);
|
||||
},
|
||||
helpers: {
|
||||
async httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
requestOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
})(workflow, runExecutionData, connectionInputData, inputData, node);
|
||||
}
|
||||
|
||||
export function getExecuteSingleFunctions(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
inputData: ITaskDataConnections,
|
||||
node: INode,
|
||||
itemIndex: number,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
): IExecuteSingleFunctions {
|
||||
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
||||
return {
|
||||
continueOnFail: () => {
|
||||
return false;
|
||||
},
|
||||
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
|
||||
return expression;
|
||||
},
|
||||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
},
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return {
|
||||
apiKey: '12345',
|
||||
};
|
||||
},
|
||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
||||
if (!inputData.hasOwnProperty(inputName)) {
|
||||
// Return empty array because else it would throw error when nothing is connected to input
|
||||
return { json: {} };
|
||||
}
|
||||
|
||||
if (inputData[inputName].length < inputIndex) {
|
||||
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
||||
}
|
||||
|
||||
const allItems = inputData[inputName][inputIndex];
|
||||
|
||||
if (allItems === null) {
|
||||
// return [];
|
||||
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
||||
}
|
||||
|
||||
if (allItems[itemIndex] === null) {
|
||||
// return [];
|
||||
throw new Error(
|
||||
`Value "${inputIndex}" of input "${inputName}" with itemIndex "${itemIndex}" did not get set!`,
|
||||
);
|
||||
}
|
||||
|
||||
return allItems[itemIndex];
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getNode: () => {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
},
|
||||
getRestApiUrl: (): string => {
|
||||
return additionalData.restApiUrl;
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return additionalData.timezone;
|
||||
},
|
||||
getNodeParameter: (
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
):
|
||||
| NodeParameterValue
|
||||
| INodeParameters
|
||||
| NodeParameterValue[]
|
||||
| INodeParameters[]
|
||||
| object => {
|
||||
return getNodeParameter(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
node,
|
||||
parameterName,
|
||||
itemIndex,
|
||||
mode,
|
||||
{},
|
||||
fallbackValue,
|
||||
);
|
||||
},
|
||||
getWorkflow: () => {
|
||||
return {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
};
|
||||
},
|
||||
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
|
||||
const dataProxy = new WorkflowDataProxy(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
{},
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
getWorkflowStaticData(type: string): IDataObject {
|
||||
return workflow.getStaticData(type, node);
|
||||
},
|
||||
helpers: {
|
||||
async httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
requestOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
|
||||
}
|
||||
|
||||
class NodeTypesClass implements INodeTypes {
|
||||
nodeTypes: INodeTypeData = {
|
||||
'test.set': {
|
||||
@@ -120,3 +632,27 @@ export function NodeTypes(): NodeTypesClass {
|
||||
|
||||
return nodeTypesInstance;
|
||||
}
|
||||
|
||||
export function WorkflowExecuteAdditionalData(): IWorkflowExecuteAdditionalData {
|
||||
const workflowData: IWorkflowBase = {
|
||||
name: '',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
active: true,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
};
|
||||
|
||||
return {
|
||||
credentialsHelper: new CredentialsHelper(''),
|
||||
hooks: new WorkflowHooks({}, 'trigger', '1', workflowData),
|
||||
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {},
|
||||
sendMessageToUI: (message: string) => {},
|
||||
restApiUrl: '',
|
||||
encryptionKey: 'test',
|
||||
timezone: 'America/New_York',
|
||||
webhookBaseUrl: 'webhook',
|
||||
webhookWaitingBaseUrl: 'webhook-waiting',
|
||||
webhookTestBaseUrl: 'webhook-test',
|
||||
};
|
||||
}
|
||||
|
||||
1680
packages/workflow/test/RoutingNode.test.ts
Normal file
1680
packages/workflow/test/RoutingNode.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2017"
|
||||
"es2019"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
@@ -11,13 +11,14 @@
|
||||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/",
|
||||
"target": "es2017",
|
||||
"target": "es2019",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user