Initial commit to release

This commit is contained in:
Jan Oberhauser
2019-06-23 12:35:23 +02:00
commit 9cb9804eee
257 changed files with 42436 additions and 0 deletions

View 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;
}

View 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;
}
}

View 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);
},
});
}

File diff suppressed because it is too large Load Diff

View 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);
}
});
}
}

View 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,
};