mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-19 11:01:15 +00:00
Initial commit to release
This commit is contained in:
552
packages/workflow/src/Interfaces.ts
Normal file
552
packages/workflow/src/Interfaces.ts
Normal file
@@ -0,0 +1,552 @@
|
||||
import { Workflow } from './Workflow';
|
||||
import * as express from "express";
|
||||
|
||||
export interface IBinaryData {
|
||||
[key: string]: string | undefined;
|
||||
data: string;
|
||||
mimeType: string;
|
||||
fileName?: string;
|
||||
fileExtension?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IConnection {
|
||||
// The node the connection is to
|
||||
node: string;
|
||||
|
||||
// The type of the input on destination node (for example "main")
|
||||
type: string;
|
||||
|
||||
// The output/input-index of destination node (if node has multiple inputs/outputs of the same type)
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface IExecutionError {
|
||||
message: string;
|
||||
node?: string;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
// Get used to gives nodes access to credentials
|
||||
export interface IGetCredentials {
|
||||
get(type: string, name: string): Promise<ICredentialsEncrypted>;
|
||||
}
|
||||
|
||||
// Defines which nodes are allowed to access the credentials and
|
||||
// when that access got grented from which user
|
||||
export interface ICredentialNodeAccess {
|
||||
nodeType: string;
|
||||
user?: string;
|
||||
date?: number;
|
||||
}
|
||||
|
||||
export interface ICredentialsDecrypted {
|
||||
name: string;
|
||||
type: string;
|
||||
nodesAccess: ICredentialNodeAccess[];
|
||||
data?: ICredentialDataDecryptedObject;
|
||||
}
|
||||
|
||||
export interface ICredentialsEncrypted {
|
||||
name: string;
|
||||
type: string;
|
||||
nodesAccess: ICredentialNodeAccess[];
|
||||
data?: string;
|
||||
}
|
||||
|
||||
export interface ICredentialType {
|
||||
name: string;
|
||||
displayName: string;
|
||||
properties: INodeProperties[];
|
||||
}
|
||||
|
||||
export interface ICredentialTypes {
|
||||
credentialTypes?: {
|
||||
[key: string]: ICredentialType
|
||||
};
|
||||
init(credentialTypes?: { [key: string]: ICredentialType }): Promise<void>;
|
||||
getAll(): ICredentialType[];
|
||||
getByName(credentialType: string): ICredentialType;
|
||||
}
|
||||
|
||||
// The way the credentials get saved in the database (data encrypted)
|
||||
export interface ICredentialData {
|
||||
name: string;
|
||||
data: string; // Contains the access data as encrypted JSON string
|
||||
nodesAccess: ICredentialNodeAccess[];
|
||||
}
|
||||
|
||||
// The encrypted credentials which the nodes can access
|
||||
export type CredentialInformation = string | number | boolean;
|
||||
|
||||
|
||||
// The encrypted credentials which the nodes can access
|
||||
export interface ICredentialDataDecryptedObject {
|
||||
[key: string]: CredentialInformation;
|
||||
}
|
||||
|
||||
// First array index: The output/input-index (if node has multiple inputs/outputs of the same type)
|
||||
// Second array index: The different connections (if one node is connected to multiple nodes)
|
||||
export type NodeInputConnections = IConnection[][];
|
||||
|
||||
export interface INodeConnections {
|
||||
// Input name
|
||||
[key: string]: NodeInputConnections;
|
||||
}
|
||||
|
||||
export interface IConnections {
|
||||
// Node name
|
||||
[key: string]: INodeConnections;
|
||||
}
|
||||
|
||||
export type GenericValue = string | object | number | boolean | undefined | null;
|
||||
|
||||
export interface IDataObject {
|
||||
[key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[];
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteData {
|
||||
data: ITaskDataConnections;
|
||||
node: INode;
|
||||
}
|
||||
|
||||
|
||||
export type IContextObject = {
|
||||
[key: string]: any; // tslint:disable-line:no-any
|
||||
};
|
||||
|
||||
|
||||
export interface IExecuteContextData {
|
||||
// Keys are: "flow" | "node:<NODE_NAME>"
|
||||
[key: string]: IContextObject;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteFunctions {
|
||||
getContext(type: string): IContextObject;
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
|
||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[];
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNodeParameter(parameterName: string, itemIndex: number, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
getTimezone(): string;
|
||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteSingleFunctions {
|
||||
getContext(type: string): IContextObject;
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
|
||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData;
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getTimezone(): string;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface ILoadOptionsFunctions {
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getTimezone(): string;
|
||||
helpers: {
|
||||
[key: string]: ((...args: any[]) => any) | undefined; //tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface IHookFunctions {
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getTimezone(): string;
|
||||
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITriggerFunctions {
|
||||
emit(data: INodeExecutionData[][]): void;
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getTimezone(): string;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWebhookFunctions {
|
||||
getBodyData(): IDataObject;
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
|
||||
getHeaderData(): object;
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getQueryData(): object;
|
||||
getRequestObject(): express.Request;
|
||||
getResponseObject(): express.Response;
|
||||
getTimezone(): string;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface INodeCredentials {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface INode {
|
||||
name: string;
|
||||
typeVersion: number;
|
||||
type: string;
|
||||
position: [number, number];
|
||||
disabled?: boolean;
|
||||
continueOnFail?: boolean;
|
||||
parameters: INodeParameters;
|
||||
credentials?: INodeCredentials;
|
||||
}
|
||||
|
||||
|
||||
export interface INodes {
|
||||
[key: string]: INode;
|
||||
}
|
||||
|
||||
|
||||
export interface IObservableObject {
|
||||
[key: string]: any; // tslint:disable-line:no-any
|
||||
__dataChanged: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface IBinaryKeyData {
|
||||
[key: string]: IBinaryData;
|
||||
}
|
||||
|
||||
export interface INodeExecutionData {
|
||||
[key: string]: IDataObject | IBinaryKeyData | undefined;
|
||||
// TODO: Rename this one as json does not really fit as it is not json (which is a string) it is actually a JS object
|
||||
json: IDataObject;
|
||||
// json: object;
|
||||
// json?: object;
|
||||
binary?: IBinaryKeyData;
|
||||
}
|
||||
|
||||
|
||||
export interface INodeExecuteFunctions {
|
||||
getExecuteTriggerFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions;
|
||||
getExecuteFunctions(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteFunctions;
|
||||
getExecuteSingleFunctions(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, itemIndex: number, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteSingleFunctions;
|
||||
getExecuteHookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, isTest?: boolean): IHookFunctions;
|
||||
getExecuteWebhookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IWebhookFunctions;
|
||||
}
|
||||
|
||||
|
||||
// The values a node property can have
|
||||
export type NodeParameterValue = string | number | boolean;
|
||||
|
||||
export interface INodeParameters {
|
||||
// TODO: Later also has to be possible to add multiple ones with the name name. So array has to be possible
|
||||
[key: string]: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||
}
|
||||
|
||||
|
||||
export type NodePropertyTypes = 'boolean' | 'collection' | 'color' | 'dateTime' | 'fixedCollection' | 'json' | 'multiOptions' | 'number' | 'options' | 'string';
|
||||
|
||||
export interface INodePropertyTypeOptions {
|
||||
alwaysOpenEditWindow?: boolean; // Supported by: string
|
||||
loadOptionsMethod?: string; // Supported by: options
|
||||
maxValue?: number; // Supported by: number
|
||||
minValue?: number; // Supported by: number
|
||||
multipleValues?: boolean; // Supported by: <All>
|
||||
multipleValueButtonText?: string; // Supported when "multipleValues" set to true
|
||||
numberPrecision?: number; // Supported by: number
|
||||
numberStepSize?: number; // Supported by: number
|
||||
password?: boolean; // Supported by: string
|
||||
rows?: number; // Supported by: string
|
||||
[key: string]: boolean | number | string | undefined;
|
||||
}
|
||||
|
||||
export interface IDisplayOptions {
|
||||
hide?: {
|
||||
[key: string]: NodeParameterValue[];
|
||||
};
|
||||
show?: {
|
||||
[key: string]: NodeParameterValue[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface INodeProperties {
|
||||
displayName: string;
|
||||
name: string;
|
||||
type: NodePropertyTypes;
|
||||
typeOptions?: INodePropertyTypeOptions;
|
||||
default: NodeParameterValue | INodeParameters | INodeParameters[] | NodeParameterValue[];
|
||||
description?: string;
|
||||
displayOptions?: IDisplayOptions;
|
||||
options?: Array<INodePropertyOptions | INodeProperties | INodePropertyCollection >;
|
||||
placeholder?: string;
|
||||
isNodeSetting?: boolean;
|
||||
noDataExpression?: boolean;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface INodePropertyOptions {
|
||||
name: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface INodePropertyCollection {
|
||||
displayName: string;
|
||||
name: string;
|
||||
values: INodeProperties[];
|
||||
}
|
||||
|
||||
export interface ITriggerResponse {
|
||||
closeFunction?: () => Promise<void>;
|
||||
// To manually trigger the run
|
||||
manualTriggerFunction?: () => Promise<void>;
|
||||
// Gets added automatically at manual workflow runs resolves with
|
||||
// the first emitted data
|
||||
manualTriggerResponse?: Promise<INodeExecutionData[][]>;
|
||||
}
|
||||
|
||||
export interface INodeType {
|
||||
description: INodeTypeDescription;
|
||||
execute?(this: IExecuteFunctions): Promise<INodeExecutionData[][] | null>;
|
||||
executeSingle?(this: IExecuteSingleFunctions): Promise<INodeExecutionData>;
|
||||
trigger?(this: ITriggerFunctions): Promise<ITriggerResponse | undefined>;
|
||||
webhook?(this: IWebhookFunctions): Promise<IWebhookResonseData>;
|
||||
hooks?: {
|
||||
[key: string]: (this: IHookFunctions) => Promise<boolean>;
|
||||
};
|
||||
methods?: {
|
||||
loadOptions?: {
|
||||
[key: string]: (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
||||
}
|
||||
};
|
||||
webhookMethods?: {
|
||||
[key: string]: IWebhookSetupMethods;
|
||||
};
|
||||
}
|
||||
|
||||
export type WebhookSetupMethodNames = 'checkExists' | 'create' | 'delete';
|
||||
|
||||
|
||||
export interface IWebhookSetupMethods {
|
||||
[key: string]: ((this: IHookFunctions) => Promise<boolean>) | undefined;
|
||||
checkExists?: (this: IHookFunctions) => Promise<boolean>;
|
||||
create?: (this: IHookFunctions) => Promise<boolean>;
|
||||
delete?: (this: IHookFunctions) => Promise<boolean>;
|
||||
}
|
||||
|
||||
|
||||
export interface INodeCredentialDescription {
|
||||
name: string;
|
||||
required?: boolean;
|
||||
displayOptions?: IDisplayOptions;
|
||||
}
|
||||
|
||||
export type INodeIssueTypes = 'credentials' | 'execution' | 'parameters' | 'typeUnknown';
|
||||
|
||||
export interface INodeIssueObjectProperty {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
export interface INodeIssueData {
|
||||
node: string;
|
||||
type: INodeIssueTypes;
|
||||
value: boolean | string | string[] | INodeIssueObjectProperty;
|
||||
}
|
||||
|
||||
export interface INodeIssues {
|
||||
execution?: boolean;
|
||||
credentials?: INodeIssueObjectProperty;
|
||||
parameters?: INodeIssueObjectProperty;
|
||||
typeUnknown?: boolean;
|
||||
[key: string]: undefined | boolean | INodeIssueObjectProperty;
|
||||
}
|
||||
|
||||
export interface IWorfklowIssues {
|
||||
[key: string]: INodeIssues;
|
||||
}
|
||||
|
||||
export interface INodeTypeDescription {
|
||||
displayName: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
group: string[];
|
||||
version: number;
|
||||
description: string;
|
||||
defaults: INodeParameters;
|
||||
inputs: string[];
|
||||
outputs: string[];
|
||||
outputNames?: string[];
|
||||
properties: INodeProperties[];
|
||||
credentials?: INodeCredentialDescription[];
|
||||
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
||||
hooks?: {
|
||||
[key: string]: INodeHookDescription[] | undefined;
|
||||
activate?: INodeHookDescription[];
|
||||
deactivate?: INodeHookDescription[];
|
||||
};
|
||||
webhooks?: IWebhookDescription[];
|
||||
}
|
||||
|
||||
export interface INodeHookDescription {
|
||||
method: string;
|
||||
}
|
||||
|
||||
export interface IWebhookData {
|
||||
httpMethod: WebhookHttpMethod;
|
||||
node: string;
|
||||
path: string;
|
||||
webhookDescription: IWebhookDescription;
|
||||
workflow: Workflow;
|
||||
workflowExecuteAdditionalData: IWorkflowExecuteAdditionalData;
|
||||
}
|
||||
|
||||
export interface IWebhookDescription {
|
||||
[key: string]: WebhookHttpMethod | WebhookResponseMode | string | undefined;
|
||||
httpMethod: WebhookHttpMethod | string;
|
||||
name: string;
|
||||
path: string;
|
||||
responseBinaryPropertyName?: string;
|
||||
reponseMode?: WebhookResponseMode | string;
|
||||
reponseData?: WebhookResponseData | string;
|
||||
}
|
||||
|
||||
export type WebhookHttpMethod = 'GET' | 'POST';
|
||||
|
||||
export interface IWebhookResonseData {
|
||||
workflowData?: INodeExecutionData[][];
|
||||
webhookResponse?: any; // tslint:disable-line:no-any
|
||||
noWebhookResponse?: boolean;
|
||||
}
|
||||
|
||||
export type WebhookResponseData = 'allEntries' | 'firstEntryJson' | 'firstEntryBinary';
|
||||
export type WebhookResponseMode = 'onReceived' | 'lastNode';
|
||||
|
||||
export interface INodeTypesObject {
|
||||
[key: string]: INodeType;
|
||||
}
|
||||
|
||||
export interface INodeTypes {
|
||||
init(nodeTypes?: INodeTypesObject): Promise<void>;
|
||||
getAll(): INodeType[];
|
||||
getByName(nodeType: string): INodeType | undefined;
|
||||
}
|
||||
|
||||
|
||||
export interface IRun {
|
||||
data: IRunExecutionData;
|
||||
finished?: boolean;
|
||||
mode: WorkflowExecuteMode;
|
||||
startedAt: number;
|
||||
stoppedAt: number;
|
||||
}
|
||||
|
||||
|
||||
// Contains all the data which is needed to execute a workflow and so also to
|
||||
// start restart it again after it did fail.
|
||||
// The RunData, ExecuteData and WaitForExecution contain often the same data.
|
||||
export interface IRunExecutionData {
|
||||
startData?: {
|
||||
destinationNode?: string;
|
||||
runNodeFilter?: string[];
|
||||
};
|
||||
resultData: {
|
||||
error?: IExecutionError;
|
||||
runData: IRunData;
|
||||
lastNodeExecuted?: string;
|
||||
};
|
||||
executionData?: {
|
||||
contextData: IExecuteContextData;
|
||||
nodeExecutionStack: IExecuteData[];
|
||||
waitingExecution: IWaitingForExecution;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IRunData {
|
||||
// node-name: result-data
|
||||
[key: string]: ITaskData[];
|
||||
}
|
||||
|
||||
|
||||
// The data that gets returned when a node runs
|
||||
export interface ITaskData {
|
||||
startTime: number;
|
||||
executionTime: number;
|
||||
data?: ITaskDataConnections;
|
||||
error?: IExecutionError;
|
||||
}
|
||||
|
||||
|
||||
// The data for al the different kind of connectons (like main) and all the indexes
|
||||
export interface ITaskDataConnections {
|
||||
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
||||
// null is also allowed because if we still need data for a later while executing the workflow set teompoary to null
|
||||
// the nodes get as input TaskDataConnections which is identical to this one except that no null is allowed.
|
||||
[key: string]: Array<INodeExecutionData[] | null>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Keeps data while workflow gets executed and allows when provided to restart execution
|
||||
export interface IWaitingForExecution {
|
||||
// Node name
|
||||
[key: string]: {
|
||||
// Run index
|
||||
[key: number]: ITaskDataConnections
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowCredentials {
|
||||
// Credential type
|
||||
[key: string]: {
|
||||
// Name
|
||||
[key: string]: ICredentialsEncrypted;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWorkflowExecuteHooks {
|
||||
afterExecute? (data: IRun, waitingExecutionData: IWaitingForExecution): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IWorkflowExecuteAdditionalData {
|
||||
credentials: IWorkflowCredentials;
|
||||
encryptionKey: string;
|
||||
hooks?: {
|
||||
[key: string]: Array<((...args: any[]) => Promise<void>)> | undefined; // tslint:disable-line:no-any
|
||||
nodeExecuteAfter?: Array<((executionId: string, nodeName: string, data: ITaskData) => Promise<void>)>;
|
||||
nodeExecuteBefore?: Array<((nodeName: string, executionId: string) => Promise<void>)>;
|
||||
workflowExecuteAfter?: Array<((data: IRun, executionId: string) => Promise<void>)>;
|
||||
workflowExecuteBefore?: Array<((executionId: string) => Promise<void>)>;
|
||||
};
|
||||
httpResponse?: express.Response;
|
||||
httpRequest?: express.Request;
|
||||
timezone: string;
|
||||
webhookBaseUrl: string;
|
||||
webhookTestBaseUrl: string;
|
||||
}
|
||||
|
||||
export type WorkflowExecuteMode = 'cli' | 'error' | 'internal' | 'manual' | 'retry' | 'trigger' | 'webhook';
|
||||
|
||||
export interface IWorkflowSettings {
|
||||
[key: string]: IDataObject | string | number | boolean | undefined;
|
||||
}
|
||||
703
packages/workflow/src/NodeHelpers.ts
Normal file
703
packages/workflow/src/NodeHelpers.ts
Normal file
@@ -0,0 +1,703 @@
|
||||
import {
|
||||
IContextObject,
|
||||
INodeCredentialDescription,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeIssues,
|
||||
INodeIssueObjectProperty,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodePropertyCollection,
|
||||
INodeType,
|
||||
IRunExecutionData,
|
||||
IWebhookData,
|
||||
NodeParameterValue,
|
||||
WebhookHttpMethod,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
} from './Interfaces';
|
||||
|
||||
import {
|
||||
Workflow
|
||||
} from './Workflow';
|
||||
|
||||
import { get } from 'lodash';
|
||||
|
||||
/**
|
||||
* Returns if the parameter should be displayed or not
|
||||
*
|
||||
* @export
|
||||
* @param {INodeParameters} nodeValues The data on the node which decides if the parameter
|
||||
* should be displayed
|
||||
* @param {(INodeProperties | INodeCredentialDescription)} parameter The parameter to check if it should be displayed
|
||||
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
|
||||
* @returns
|
||||
*/
|
||||
export function displayParameter(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, nodeValuesRoot?: INodeParameters) {
|
||||
if (!parameter.displayOptions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nodeValuesRoot = nodeValuesRoot || nodeValues;
|
||||
|
||||
let value;
|
||||
if (parameter.displayOptions.show) {
|
||||
// All the defined rules have to match to display parameter
|
||||
for (const propertyName of Object.keys(parameter.displayOptions.show)) {
|
||||
if (propertyName.charAt(0) === '/') {
|
||||
// Get the value from the root of the node
|
||||
value = nodeValuesRoot[propertyName.slice(1)];
|
||||
} else {
|
||||
// Get the value from current level
|
||||
value = nodeValues[propertyName];
|
||||
}
|
||||
|
||||
if (value === undefined || !parameter.displayOptions.show[propertyName].includes(value as string)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parameter.displayOptions.hide) {
|
||||
// Any of the defined hide rules have to match to hide the paramter
|
||||
for (const propertyName of Object.keys(parameter.displayOptions.hide)) {
|
||||
if (propertyName.charAt(0) === '/') {
|
||||
// Get the value from the root of the node
|
||||
value = nodeValuesRoot[propertyName.slice(1)];
|
||||
} else {
|
||||
// Get the value from current level
|
||||
value = nodeValues[propertyName];
|
||||
}
|
||||
if (value !== undefined && parameter.displayOptions.hide[propertyName].includes(value as string)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given parameter should be displayed or not considering the path
|
||||
* to the properties
|
||||
*
|
||||
* @export
|
||||
* @param {INodeParameters} nodeValues The data on the node which decides if the parameter
|
||||
* should be displayed
|
||||
* @param {(INodeProperties | INodeCredentialDescription)} parameter The parameter to check if it should be displayed
|
||||
* @param {string} path The path to the property
|
||||
* @returns
|
||||
*/
|
||||
export function displayParameterPath(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, path: string) {
|
||||
let resolvedNodeValues = nodeValues;
|
||||
if (path !== '') {
|
||||
resolvedNodeValues = get(
|
||||
nodeValues,
|
||||
path,
|
||||
) as INodeParameters;
|
||||
}
|
||||
|
||||
// Get the root parameter data
|
||||
let nodeValuesRoot = nodeValues;
|
||||
if (path && path.split('.').indexOf('parameters') === 0) {
|
||||
nodeValuesRoot = get(
|
||||
nodeValues,
|
||||
'parameters',
|
||||
) as INodeParameters;
|
||||
}
|
||||
|
||||
return displayParameter(resolvedNodeValues, parameter, nodeValuesRoot);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the context data
|
||||
*
|
||||
* @export
|
||||
* @param {IRunExecutionData} runExecutionData The run execution data
|
||||
* @param {string} type The data type. "node"/"flow"
|
||||
* @param {INode} [node] If type "node" is set the node to return the context of has to be supplied
|
||||
* @returns {IContextObject}
|
||||
*/
|
||||
export function getContext(runExecutionData: IRunExecutionData, type: string, node?: INode): IContextObject {
|
||||
if (runExecutionData.executionData === undefined) {
|
||||
// TODO: Should not happen leave it for test now
|
||||
throw new Error('The "executionData" is not initialized!');
|
||||
}
|
||||
|
||||
let key: string;
|
||||
if (type === 'flow') {
|
||||
key = 'flow';
|
||||
} else if (type === 'node') {
|
||||
if (node === undefined) {
|
||||
throw new Error(`The request data of context type "node" the node parameter has to be set!`);
|
||||
}
|
||||
key = `node:${node.name}`;
|
||||
} else {
|
||||
throw new Error(`The context type "${type}" is not know. Only "flow" and node" are supported!`);
|
||||
}
|
||||
|
||||
if (runExecutionData.executionData.contextData[key] === undefined) {
|
||||
runExecutionData.executionData.contextData[key] = {};
|
||||
}
|
||||
|
||||
return runExecutionData.executionData.contextData[key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node parameter values. Depending on the settings it either just returns the none
|
||||
* default values or it applies all the default values.
|
||||
*
|
||||
* @export
|
||||
* @param {INodeProperties[]} nodePropertiesArray The properties which exist and their settings
|
||||
* @param {INodeParameters} nodeValues The node parameter data
|
||||
* @param {boolean} returnDefaults If default values get added or only none default values returned
|
||||
* @param {boolean} returnNoneDisplayed If also values which should not be displayed should be returned
|
||||
* @param {boolean} [onlySimpleTypes=false] If only simple types should be resolved
|
||||
* @param {boolean} [dataIsResolved=false] If nodeValues are already fully resolved (so that all default values got added already)
|
||||
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
|
||||
* @returns {(INodeParameters | null)}
|
||||
*/
|
||||
export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeValues: INodeParameters, returnDefaults: boolean, returnNoneDisplayed: boolean, onlySimpleTypes = false, dataIsResolved = false, nodeValuesRoot?: INodeParameters, parentType?: string): INodeParameters | null {
|
||||
const nodeParameters: INodeParameters = {};
|
||||
|
||||
let nodeValuesDisplayCheck = nodeValues;
|
||||
if (dataIsResolved !== true && returnNoneDisplayed === false) {
|
||||
nodeValuesDisplayCheck = getNodeParameters(nodePropertiesArray, nodeValues, true, true, true, true, nodeValuesRoot, parentType) as INodeParameters;
|
||||
}
|
||||
|
||||
nodeValuesRoot = nodeValuesRoot || nodeValuesDisplayCheck;
|
||||
|
||||
for (const nodeProperties of nodePropertiesArray) {
|
||||
if (nodeValues[nodeProperties.name] === undefined && (returnDefaults === false || parentType === 'collection')) {
|
||||
// The value is not defined so go to the next
|
||||
continue;
|
||||
}
|
||||
|
||||
if (returnNoneDisplayed === false && !displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)) {
|
||||
if (returnNoneDisplayed === false) {
|
||||
continue;
|
||||
}
|
||||
if (returnDefaults === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!['collection', 'fixedCollection'].includes(nodeProperties.type)) {
|
||||
// Is a simple property so can be set as it is
|
||||
|
||||
if (returnDefaults === true) {
|
||||
// Set also when it has the default value
|
||||
if (['boolean', 'number'].includes(nodeProperties.type)) {
|
||||
// Boolean and numbers are special as false and 0 are valid values
|
||||
// and should not be replaced with default value
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] !== undefined ? nodeValues[nodeProperties.name] : nodeProperties.default;
|
||||
} else {
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] || nodeProperties.default;
|
||||
}
|
||||
} else if (nodeValues[nodeProperties.name] !== nodeProperties.default || (nodeValues[nodeProperties.name] !== undefined && parentType === 'collection')) {
|
||||
// Set only if it is different to the default value
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (onlySimpleTypes === true) {
|
||||
// It is only supposed to resolve the simple types. So continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is a complex property so check lower levels
|
||||
let tempValue: INodeParameters | null;
|
||||
if (nodeProperties.type === 'collection') {
|
||||
// Is collection
|
||||
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues === true) {
|
||||
// Multiple can be set so will be an array
|
||||
|
||||
// Return directly the values like they are
|
||||
if (nodeValues[nodeProperties.name] !== undefined) {
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
||||
} else if (returnDefaults === true) {
|
||||
// Does not have values defined but defaults should be returned which is in the
|
||||
// case of a collection with multipleValues always an empty array
|
||||
nodeParameters[nodeProperties.name] = [];
|
||||
}
|
||||
|
||||
} else {
|
||||
if (nodeValues[nodeProperties.name] !== undefined) {
|
||||
// Has values defined so get them
|
||||
const tempNodeParameters = getNodeParameters(nodeProperties.options as INodeProperties[], nodeValues[nodeProperties.name] as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
||||
|
||||
if (tempNodeParameters !== null) {
|
||||
nodeParameters[nodeProperties.name] = tempNodeParameters;
|
||||
}
|
||||
} else if (returnDefaults === true) {
|
||||
// Does not have values defined but defaults should be returned
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
}
|
||||
}
|
||||
} else if (nodeProperties.type === 'fixedCollection') {
|
||||
// Is fixedCollection
|
||||
|
||||
const collectionValues: INodeParameters = {};
|
||||
let tempNodeParameters: INodeParameters;
|
||||
let tempNodePropertiesArray: INodeProperties[];
|
||||
let nodePropertyOptions: INodePropertyCollection | undefined;
|
||||
|
||||
let propertyValues = nodeValues[nodeProperties.name];
|
||||
if (returnDefaults === true) {
|
||||
if (propertyValues === undefined) {
|
||||
propertyValues = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
}
|
||||
}
|
||||
|
||||
// Itterate over all collections
|
||||
for (const itemName of Object.keys(propertyValues)) {
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues === true) {
|
||||
// Multiple can be set so will be an array
|
||||
|
||||
const tempArrayValue: INodeParameters[] = [];
|
||||
// Itterate over all items as it contains multiple ones
|
||||
for (const nodeValue of (propertyValues as INodeParameters)[itemName] as INodeParameters[]) {
|
||||
nodePropertyOptions = nodeProperties!.options!.find((nodePropertyOptions) => nodePropertyOptions.name === itemName) as INodePropertyCollection;
|
||||
|
||||
if (nodePropertyOptions === undefined) {
|
||||
throw new Error(`Could not find property option "${itemName}" for "${nodeProperties.name}"`);
|
||||
}
|
||||
|
||||
tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
|
||||
tempValue = getNodeParameters(tempNodePropertiesArray, nodeValue as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
||||
if (tempValue !== null) {
|
||||
tempArrayValue.push(tempValue);
|
||||
}
|
||||
}
|
||||
collectionValues[itemName] = tempArrayValue;
|
||||
} else {
|
||||
// Only one can be set so is an object of objects
|
||||
tempNodeParameters = {};
|
||||
|
||||
// Get the options of the current item
|
||||
const nodePropertyOptions = nodeProperties!.options!.find((data) => data.name === itemName);
|
||||
|
||||
if (nodePropertyOptions !== undefined) {
|
||||
tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
|
||||
tempValue = getNodeParameters(tempNodePropertiesArray, (nodeValues[nodeProperties.name] as INodeParameters)[itemName] as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
||||
if (tempValue !== null) {
|
||||
Object.assign(tempNodeParameters, tempValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(tempNodeParameters).length !== 0) {
|
||||
collectionValues[itemName] = tempNodeParameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(collectionValues).length !== 0 || returnDefaults === true) {
|
||||
// Set only if value got found
|
||||
|
||||
if (returnDefaults === true) {
|
||||
// Set also when it has the default value
|
||||
if (collectionValues === undefined) {
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
} else {
|
||||
nodeParameters[nodeProperties.name] = collectionValues;
|
||||
}
|
||||
} else if (collectionValues !== nodeProperties.default) {
|
||||
// Set only if values got found and it is not the default
|
||||
nodeParameters[nodeProperties.name] = collectionValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodeParameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Brings the output data in a format that can be returned from a node
|
||||
*
|
||||
* @export
|
||||
* @param {INodeExecutionData[]} outputData
|
||||
* @param {number} [outputIndex=0]
|
||||
* @returns {Promise<INodeExecutionData[][]>}
|
||||
*/
|
||||
export async function prepareOutputData(outputData: INodeExecutionData[], outputIndex = 0): Promise<INodeExecutionData[][]> {
|
||||
// TODO: Check if node has output with that index
|
||||
const returnData = [];
|
||||
|
||||
for (let i = 0; i < outputIndex; i++) {
|
||||
returnData.push([]);
|
||||
}
|
||||
|
||||
returnData.push(outputData);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the webhooks which should be created for the give node
|
||||
*
|
||||
* @export
|
||||
*
|
||||
* @param {INode} node
|
||||
* @returns {IWebhookData[]}
|
||||
*/
|
||||
export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData): IWebhookData[] {
|
||||
if (node.disabled === true) {
|
||||
// Node is disabled so webhooks will also not be enabled
|
||||
return [];
|
||||
}
|
||||
|
||||
if (workflow.id === undefined) {
|
||||
// Workflow has no id which means it is not saved and so webhooks
|
||||
// will not be enabled
|
||||
return [];
|
||||
}
|
||||
|
||||
const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;
|
||||
|
||||
if (nodeType.description.webhooks === undefined) {
|
||||
// Node does not have any webhooks so return
|
||||
return [];
|
||||
}
|
||||
|
||||
const returnData: IWebhookData[] = [];
|
||||
for (const webhookDescription of nodeType.description.webhooks) {
|
||||
let nodeWebhookPath = workflow.getWebhookParameterValue(node, webhookDescription, 'path', 'GET');
|
||||
if (nodeWebhookPath === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflow.id}".`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodeWebhookPath.charAt(0) === '/') {
|
||||
nodeWebhookPath = nodeWebhookPath.slice(1);
|
||||
}
|
||||
|
||||
const path = getNodeWebhookPath(workflow.id, node, nodeWebhookPath);
|
||||
|
||||
const httpMethod = workflow.getWebhookParameterValue(node, webhookDescription, 'httpMethod', 'GET');
|
||||
|
||||
if (httpMethod === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflow.id}" could not be added because the httpMethod is not defined.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
httpMethod: httpMethod as WebhookHttpMethod,
|
||||
node: node.name,
|
||||
path,
|
||||
webhookDescription,
|
||||
workflow,
|
||||
workflowExecuteAdditionalData: additionalData,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the webhook path
|
||||
*
|
||||
* @export
|
||||
* @param {string} workflowId
|
||||
* @param {string} nodeTypeName
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getNodeWebhookPath(workflowId: string, node: INode, path: string): string {
|
||||
return `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the webhook URL
|
||||
*
|
||||
* @export
|
||||
* @param {string} baseUrl
|
||||
* @param {string} workflowId
|
||||
* @param {string} nodeTypeName
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INode, path: string): string {
|
||||
// return `${baseUrl}/${workflowId}/${nodeTypeName}/${path}`;
|
||||
return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path)}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the parameter-issues of the node
|
||||
*
|
||||
* @export
|
||||
* @param {INodeProperties[]} nodePropertiesArray The properties of the node
|
||||
* @param {INode} node The data of the node
|
||||
* @returns {(INodeIssues | null)}
|
||||
*/
|
||||
export function getNodeParametersIssues(nodePropertiesArray: INodeProperties[], node: INode): INodeIssues | null {
|
||||
const foundIssues: INodeIssues = {};
|
||||
let propertyIssues: INodeIssues;
|
||||
|
||||
for (const nodeProperty of nodePropertiesArray) {
|
||||
propertyIssues = getParameterIssues(nodeProperty, node.parameters, '');
|
||||
mergeIssues(foundIssues, propertyIssues);
|
||||
}
|
||||
|
||||
if (Object.keys(foundIssues).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return foundIssues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the issues of the node as string
|
||||
*
|
||||
* @export
|
||||
* @param {INodeIssues} issues The issues of the node
|
||||
* @param {INode} node The node
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[] {
|
||||
const nodeIssues = [];
|
||||
|
||||
if (issues.execution !== undefined) {
|
||||
nodeIssues.push(`Execution Error.`);
|
||||
}
|
||||
|
||||
const objectProperties = [
|
||||
'parameters',
|
||||
'credentials',
|
||||
];
|
||||
|
||||
let issueText: string, parameterName: string;
|
||||
for (const propertyName of objectProperties) {
|
||||
if (issues[propertyName] !== undefined) {
|
||||
for (parameterName of Object.keys(issues[propertyName] as object)) {
|
||||
for (issueText of (issues[propertyName] as INodeIssueObjectProperty)[parameterName]) {
|
||||
nodeIssues.push(issueText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (issues.typeUnknown !== undefined) {
|
||||
if (node !== undefined) {
|
||||
nodeIssues.push(`Node Type "${node.type}" is not known.`);
|
||||
} else {
|
||||
nodeIssues.push(`Node Type is not known.`);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeIssues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an issue if the parameter is not defined
|
||||
*
|
||||
* @export
|
||||
* @param {INodeIssues} foundIssues The already found issues
|
||||
* @param {INodeProperties} nodeProperties The properties of the node
|
||||
* @param {NodeParameterValue} value The value of the parameter
|
||||
*/
|
||||
export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: INodeProperties, value: NodeParameterValue) {
|
||||
// TODO: Check what it really has when undefined
|
||||
if ((nodeProperties.type === 'string' && (value === '' || value === undefined)) ||
|
||||
(nodeProperties.type === 'multiOptions' && Array.isArray(value) && value.length === 0) ||
|
||||
(nodeProperties.type === 'dateTime' && value === undefined)) {
|
||||
// Parameter is requried but empty
|
||||
if (foundIssues.parameters === undefined) {
|
||||
foundIssues.parameters = {};
|
||||
}
|
||||
if (foundIssues.parameters[nodeProperties.name] === undefined) {
|
||||
foundIssues.parameters[nodeProperties.name] = [];
|
||||
}
|
||||
|
||||
foundIssues.parameters[nodeProperties.name].push(`Parameter "${nodeProperties.displayName}" is required.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the parameter value
|
||||
*
|
||||
* @export
|
||||
* @param {INodeParameters} nodeValues The values of the node
|
||||
* @param {string} parameterName The name of the parameter to return the value of
|
||||
* @param {string} path The path to the properties
|
||||
* @returns
|
||||
*/
|
||||
export function getParameterValueByPath(nodeValues: INodeParameters, parameterName: string, path: string) {
|
||||
return get(
|
||||
nodeValues,
|
||||
path ? path + '.' + parameterName : parameterName,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the issues with the given node-values
|
||||
*
|
||||
* @export
|
||||
* @param {INodeProperties} nodeProperties The properties of the node
|
||||
* @param {INodeParameters} nodeValues The values of the node
|
||||
* @param {string} path The path to the properties
|
||||
* @returns {INodeIssues}
|
||||
*/
|
||||
export function getParameterIssues(nodeProperties: INodeProperties, nodeValues: INodeParameters, path: string): INodeIssues {
|
||||
const foundIssues: INodeIssues = {};
|
||||
let value;
|
||||
|
||||
if (nodeProperties.required === true) {
|
||||
if (displayParameterPath(nodeValues, nodeProperties, path)) {
|
||||
value = getParameterValueByPath(nodeValues, nodeProperties.name, path);
|
||||
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues !== undefined) {
|
||||
// Multiple can be set so will be an array
|
||||
if (Array.isArray(value)) {
|
||||
for (const singleValue of value as NodeParameterValue[]) {
|
||||
addToIssuesIfMissing(foundIssues, nodeProperties, singleValue as NodeParameterValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only one can be set so will be a single value
|
||||
addToIssuesIfMissing(foundIssues, nodeProperties, value as NodeParameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are any child parameters
|
||||
if (nodeProperties.options === undefined) {
|
||||
// There are none so nothing else to check
|
||||
return foundIssues;
|
||||
}
|
||||
|
||||
// Check the child parameters
|
||||
|
||||
// Important:
|
||||
// Checks the child properties only if the property is defined on current level.
|
||||
// That means that the required flag works only for the current level only. If
|
||||
// it is set on a lower level it means that the property is only required in case
|
||||
// the parent property got set.
|
||||
|
||||
let basePath = path ? `${path}.` : '';
|
||||
|
||||
const checkChildNodeProperties: Array<{
|
||||
basePath: string;
|
||||
data: INodeProperties;
|
||||
}> = [];
|
||||
|
||||
// Collect all the properties to check
|
||||
if (nodeProperties.type === 'collection') {
|
||||
for (const option of nodeProperties.options) {
|
||||
checkChildNodeProperties.push({
|
||||
basePath,
|
||||
data: option as INodeProperties,
|
||||
});
|
||||
}
|
||||
} else if (nodeProperties.type === 'fixedCollection') {
|
||||
basePath = basePath ? `${basePath}.` : '' + nodeProperties.name + '.';
|
||||
|
||||
let propertyOptions: INodePropertyCollection;
|
||||
for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||
// Check if the option got set and if not skip it
|
||||
value = getParameterValueByPath(nodeValues, propertyOptions.name, basePath.slice(0, -1));
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues !== undefined) {
|
||||
// Multiple can be set so will be an array of objects
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < (value as INodeParameters[]).length; i++) {
|
||||
for (const option of propertyOptions.values) {
|
||||
checkChildNodeProperties.push({
|
||||
basePath: `${basePath}${propertyOptions.name}[${i}]`,
|
||||
data: option as INodeProperties,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only one can be set so will be an object
|
||||
for (const option of propertyOptions.values) {
|
||||
checkChildNodeProperties.push({
|
||||
basePath: basePath + propertyOptions.name,
|
||||
data: option as INodeProperties,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For all other types there is nothing to check so return
|
||||
return foundIssues;
|
||||
}
|
||||
|
||||
let propertyIssues;
|
||||
|
||||
for (const optionData of checkChildNodeProperties) {
|
||||
propertyIssues = getParameterIssues(optionData.data as INodeProperties, nodeValues, optionData.basePath);
|
||||
mergeIssues(foundIssues, propertyIssues);
|
||||
}
|
||||
|
||||
return foundIssues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Merges multiple NodeIssues together
|
||||
*
|
||||
* @export
|
||||
* @param {INodeIssues} destination The issues to merge into
|
||||
* @param {(INodeIssues | null)} source The issues to merge
|
||||
* @returns
|
||||
*/
|
||||
export function mergeIssues(destination: INodeIssues, source: INodeIssues | null) {
|
||||
if (source === null) {
|
||||
// Nothing to merge
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.execution === true) {
|
||||
destination.execution = true;
|
||||
}
|
||||
|
||||
const objectProperties = [
|
||||
'parameters',
|
||||
'credentials',
|
||||
];
|
||||
|
||||
let destinationProperty: INodeIssueObjectProperty;
|
||||
for (const propertyName of objectProperties) {
|
||||
if (source[propertyName] !== undefined) {
|
||||
if (destination[propertyName] === undefined) {
|
||||
destination[propertyName] = {};
|
||||
}
|
||||
|
||||
let parameterName: string;
|
||||
for (parameterName of Object.keys(source[propertyName] as INodeIssueObjectProperty)) {
|
||||
destinationProperty = destination[propertyName] as INodeIssueObjectProperty;
|
||||
if (destinationProperty[parameterName] === undefined) {
|
||||
destinationProperty[parameterName] = [];
|
||||
}
|
||||
destinationProperty[parameterName].push.apply(destinationProperty[parameterName], (source[propertyName] as INodeIssueObjectProperty)[parameterName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (source.typeUnknown === true) {
|
||||
destination.typeUnknown = true;
|
||||
}
|
||||
}
|
||||
54
packages/workflow/src/ObservableObject.ts
Normal file
54
packages/workflow/src/ObservableObject.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IObservableObject,
|
||||
} from './';
|
||||
|
||||
export interface IObservableOptions {
|
||||
ignoreEmptyOnFirstChild?: boolean;
|
||||
}
|
||||
|
||||
export function create(target: IDataObject, parent?: IObservableObject, option?: IObservableOptions, depth?: number): IDataObject {
|
||||
depth = depth || 0;
|
||||
|
||||
// Make all the children of target also observeable
|
||||
for (const key in target) {
|
||||
if (typeof target[key] === 'object') {
|
||||
target[key] = create(target[key] as IDataObject, (parent || target) as IObservableObject, option, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(target, '__dataChanged', {
|
||||
value: false,
|
||||
writable: true,
|
||||
});
|
||||
return new Proxy(target, {
|
||||
deleteProperty(target, name) {
|
||||
if (parent === undefined) {
|
||||
// If no parent is given mark current data as changed
|
||||
(target as IObservableObject).__dataChanged = true;
|
||||
} else {
|
||||
// If parent is given mark the parent data as changed
|
||||
parent.__dataChanged = true;
|
||||
}
|
||||
return Reflect.deleteProperty(target, name);
|
||||
},
|
||||
get(target, name, receiver) {
|
||||
return Reflect.get(target, name, receiver);
|
||||
},
|
||||
set(target, name, value) {
|
||||
if (parent === undefined) {
|
||||
// If no parent is given mark current data as changed
|
||||
if (option !== undefined && option.ignoreEmptyOnFirstChild === true && depth === 0
|
||||
&& target[name.toString()] === undefined && typeof value === 'object' && Object.keys(value).length === 0) {
|
||||
} else {
|
||||
(target as IObservableObject).__dataChanged = true;
|
||||
}
|
||||
} else {
|
||||
// If parent is given mark the parent data as changed
|
||||
parent.__dataChanged = true;
|
||||
}
|
||||
return Reflect.set(target, name, value);
|
||||
},
|
||||
});
|
||||
}
|
||||
1021
packages/workflow/src/Workflow.ts
Normal file
1021
packages/workflow/src/Workflow.ts
Normal file
File diff suppressed because it is too large
Load Diff
280
packages/workflow/src/WorkflowDataProxy.ts
Normal file
280
packages/workflow/src/WorkflowDataProxy.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
NodeHelpers,
|
||||
IRunExecutionData,
|
||||
Workflow,
|
||||
} from './';
|
||||
|
||||
|
||||
|
||||
export class WorkflowDataProxy {
|
||||
private workflow: Workflow;
|
||||
private runExecutionData: IRunExecutionData | null;
|
||||
private runIndex: number;
|
||||
private itemIndex: number;
|
||||
private activeNodeName: string;
|
||||
private connectionInputData: INodeExecutionData[];
|
||||
|
||||
|
||||
|
||||
constructor(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[]) {
|
||||
this.workflow = workflow;
|
||||
this.runExecutionData = runExecutionData;
|
||||
this.runIndex = runIndex;
|
||||
this.itemIndex = itemIndex;
|
||||
this.activeNodeName = activeNodeName;
|
||||
this.connectionInputData = connectionInputData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy which allows to query context data of a given node
|
||||
*
|
||||
* @private
|
||||
* @param {string} nodeName The name of the node to get the context from
|
||||
* @returns
|
||||
* @memberof WorkflowDataProxy
|
||||
*/
|
||||
private nodeContextGetter(nodeName: string) {
|
||||
const that = this;
|
||||
const node = this.workflow.nodes[nodeName];
|
||||
|
||||
return new Proxy({}, {
|
||||
ownKeys(target) {
|
||||
if (Reflect.ownKeys(target).length === 0) {
|
||||
// Target object did not get set yet
|
||||
Object.assign(target, NodeHelpers.getContext(that.runExecutionData!, 'node', node));
|
||||
}
|
||||
|
||||
return Reflect.ownKeys(target);
|
||||
},
|
||||
get(target, name, receiver) {
|
||||
name = name.toString();
|
||||
const contextData = NodeHelpers.getContext(that.runExecutionData!, 'node', node);
|
||||
|
||||
if (!contextData.hasOwnProperty(name)) {
|
||||
// Parameter does not exist on node
|
||||
throw new Error(`Could not find parameter "${name}" on context of node "${nodeName}"`);
|
||||
}
|
||||
|
||||
return contextData[name];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy which allows to query parameter data of a given node
|
||||
*
|
||||
* @private
|
||||
* @param {string} nodeName The name of the node to query data from
|
||||
* @returns
|
||||
* @memberof WorkflowDataGetter
|
||||
*/
|
||||
private nodeParameterGetter(nodeName: string) {
|
||||
const that = this;
|
||||
const node = this.workflow.nodes[nodeName];
|
||||
|
||||
return new Proxy(node.parameters, {
|
||||
ownKeys(target) {
|
||||
return Reflect.ownKeys(target);
|
||||
},
|
||||
get(target, name, receiver) {
|
||||
name = name.toString();
|
||||
|
||||
if (!node.parameters.hasOwnProperty(name)) {
|
||||
// Parameter does not exist on node
|
||||
throw new Error(`Could not find parameter "${name}" on node "${nodeName}"`);
|
||||
}
|
||||
|
||||
const returnValue = node.parameters[name];
|
||||
|
||||
if (typeof returnValue === 'string' && returnValue.charAt(0) === '=') {
|
||||
// The found value is an expression so resolve it
|
||||
return that.workflow.getParameterValue(returnValue, that.runExecutionData, that.runIndex, that.itemIndex, that.activeNodeName, that.connectionInputData);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy which allows to query data of a given node
|
||||
*
|
||||
* @private
|
||||
* @param {string} nodeName The name of the node query data from
|
||||
* @param {boolean} [shortSyntax=false] If short syntax got used
|
||||
* @returns
|
||||
* @memberof WorkflowDataGetter
|
||||
*/
|
||||
private nodeDataGetter(nodeName: string, shortSyntax = false) {
|
||||
|
||||
const that = this;
|
||||
const node = this.workflow.nodes[nodeName];
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`The node "${nodeName}" does not exist!`);
|
||||
}
|
||||
|
||||
return new Proxy({}, {
|
||||
get(target, name, receiver) {
|
||||
name = name.toString();
|
||||
|
||||
if (['binary', 'data'].includes(name)) {
|
||||
let executionData: INodeExecutionData[];
|
||||
if (shortSyntax === false) {
|
||||
// Long syntax got used to return data from node in path
|
||||
|
||||
if (that.runExecutionData === null) {
|
||||
throw new Error(`Workflow did not run so do not have any execution-data.`);
|
||||
}
|
||||
|
||||
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
|
||||
throw new Error(`No execution data found for node "${nodeName}"`);
|
||||
}
|
||||
|
||||
if (that.runExecutionData.resultData.runData[nodeName].length < that.runIndex) {
|
||||
throw new Error(`No execution data found for run "${that.runIndex}" of node "${nodeName}"`);
|
||||
}
|
||||
|
||||
const taskData = that.runExecutionData.resultData.runData[nodeName][that.runIndex].data!;
|
||||
|
||||
if (taskData.main === null || !taskData.main.length || taskData.main[0] === null) {
|
||||
// throw new Error(`No data found for item-index: "${itemIndex}"`);
|
||||
throw new Error(`No data found from "main" input.`);
|
||||
}
|
||||
|
||||
// Currently it is only possible to reference data from the first input
|
||||
// so we hardcode it.
|
||||
executionData = taskData.main[0] as INodeExecutionData[];
|
||||
} else {
|
||||
// Short syntax got used to return data from active node
|
||||
|
||||
// TODO: Here have to generate connection Input data for the current node by itself
|
||||
// Data needed:
|
||||
// #- the run-index
|
||||
// - node which did send data (has to be the one from last recent execution)
|
||||
// - later also the name of the input and its index (currently not needed as it is always "main" and index "0")
|
||||
executionData = that.connectionInputData;
|
||||
}
|
||||
|
||||
if (executionData.length <= that.itemIndex) {
|
||||
throw new Error(`No data found for item-index: "${that.itemIndex}"`);
|
||||
}
|
||||
|
||||
if (name === 'data') {
|
||||
// JSON-Data
|
||||
return executionData[that.itemIndex].json;
|
||||
} else if (name === 'binary') {
|
||||
// Binary-Data
|
||||
if (!executionData[that.itemIndex].binary) {
|
||||
throw new Error(`No binary data for node "${nodeName}" has been found!`);
|
||||
}
|
||||
|
||||
const returnData: IDataObject = {};
|
||||
|
||||
const binaryKeyData = executionData[that.itemIndex].binary!;
|
||||
for (const keyName of Object.keys(binaryKeyData)) {
|
||||
|
||||
returnData[keyName] = {};
|
||||
|
||||
const binaryData = binaryKeyData[keyName];
|
||||
for (const propertyName in binaryData) {
|
||||
if (propertyName === 'data') {
|
||||
// Skip the data property
|
||||
continue;
|
||||
}
|
||||
(returnData[keyName] as IDataObject)[propertyName] = binaryData[propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
} else if (name === 'context') {
|
||||
return that.nodeContextGetter(nodeName);
|
||||
} else if (name === 'parameter') {
|
||||
// Get node parameter data
|
||||
return that.nodeParameterGetter(nodeName);
|
||||
}
|
||||
|
||||
return Reflect.get(target, name, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy to query data from the environment
|
||||
*
|
||||
* @private
|
||||
* @returns
|
||||
* @memberof WorkflowDataGetter
|
||||
*/
|
||||
private envGetter() {
|
||||
return new Proxy({}, {
|
||||
get(target, name, receiver) {
|
||||
return process.env[name.toString()];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy to query data of all nodes
|
||||
*
|
||||
* @private
|
||||
* @returns
|
||||
* @memberof WorkflowDataGetter
|
||||
*/
|
||||
private nodeGetter() {
|
||||
const that = this;
|
||||
return new Proxy({}, {
|
||||
get(target, name, receiver) {
|
||||
return that.nodeDataGetter(name.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data proxy object which allows to query data from current run
|
||||
*
|
||||
* @returns
|
||||
* @memberof WorkflowDataGetter
|
||||
*/
|
||||
getDataProxy() {
|
||||
const that = this;
|
||||
|
||||
const base = {
|
||||
$binary: {}, // Placeholder
|
||||
$data: {}, // Placeholder
|
||||
$env: this.envGetter(),
|
||||
$node: this.nodeGetter(),
|
||||
$parameter: this.nodeParameterGetter(this.activeNodeName),
|
||||
};
|
||||
|
||||
return new Proxy(base, {
|
||||
get(target, name, receiver) {
|
||||
if (name === '$data') {
|
||||
// @ts-ignore
|
||||
return that.nodeDataGetter(that.activeNodeName, true).data;
|
||||
} else if (name === '$binary') {
|
||||
// @ts-ignore
|
||||
return that.nodeDataGetter(that.activeNodeName, true).binary;
|
||||
}
|
||||
|
||||
return Reflect.get(target, name, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
10
packages/workflow/src/index.ts
Normal file
10
packages/workflow/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from './Interfaces';
|
||||
export * from './Workflow';
|
||||
export * from './WorkflowDataProxy';
|
||||
|
||||
import * as NodeHelpers from './NodeHelpers';
|
||||
import * as ObservableObject from './ObservableObject';
|
||||
export {
|
||||
NodeHelpers,
|
||||
ObservableObject,
|
||||
};
|
||||
Reference in New Issue
Block a user