mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
* refactor saving * refactor api layer to be stateless * refactor header details * set variable for menu height * clean up scss * clean up indentation * clean up dropdown impl * refactor no tags view * split away header * Fix tslint issues * Refactor tag manager * add tags to patch request * clean up scss * ⚡ Refactor types to entities * fix issues * update no workflow error * clean up tagscontainer * use getters instead of state * remove imports * use custom colors * clean up tags container * clean up dropdown * clean up focusoncreate * ⚡ Ignore mistaken ID in POST /workflows * ⚡ Fix undefined tag ID in PATCH /workflows * ⚡ Shorten response for POST /tags * remove scss mixins * clean up imports * ⚡ Implement validation with class-validator * address ivan's comments * implement modals * Fix lint issues * fix disabling shortcuts * fix focus issues * fix focus issues * fix focus issues with modal * fix linting issues * use dispatch * use constants for modal keys * fix focus * fix lint issues * remove unused prop * add modal root * fix lint issues * remove unused methods * fix shortcut * remove max width * ⚡ Fix duplicate entry error for pg and MySQL * update rename messaging * update order of buttons * fix firefox overflow on windows * fix dropdown height * 🔨 refactor tag crud controllers * 🧹 remove unused imports * use variable for number of items * fix dropdown spacing * ⚡ Restore type to fix build * ⚡ Fix post-refactor PATCH /workflows/:id * ⚡ Fix PATCH /workflows/:id for zero tags * ⚡ Fix usage count becoming stringified * address max's comments * fix filter spacing * fix blur bug * address most of ivan's comments * address tags type concern * remove defaults * ⚡ return tag id as string * 🔨 add hooks to tag CUD operations * 🏎 simplify timestamp pruning * remove blur event * fix onblur bug * ⚡ Fix fs import to fix build * address max's comments * implement responsive tag container * fix lint issues * update tag limits * address ivan's comments * remove rename, refactor header, implement new designs for save, remove responsive tag container * update styling * update styling * implement responsive tag container * implement header tags edit * implement header tags edit * fix lint issues * implement expandable input * minor fixes * minor fixes * use variable * rename save as * duplicate fixes * minor edit fixes * lint fixes * style fixes * hook up saving name * hook up tags * clean up impl * fix dirty state bug * update limit * update notification messages * on click outside * fix minor bug with count * lint fixes * handle minor edge cases * handle minor edge cases * handle minor bugs; fix firefox dropdown issue * Fix min width * apply tags only after api success * remove count fix * clean up workflow tags impl, fix tags delete bug * fix minor issue * fix minor spacing issue * disable wrap for ops * fix viewport root; save on click in dropdown * save button loading when saving name/tags * implement max width on tags container * implement cleaner create experience * disable edit while updating * codacy hex color * refactor tags container * fix clickability * fix workflow open and count * clean up structure * fix up lint issues * fix button size * increase workflow name limit for larger screen * tslint fixes * disable responsiveness for workflow modal * rename event * change min width for tags * clean up pr * address max's comments on styles * remove success toasts * add hover mode to name * minor fixes * refactor name preview * fix name input not to jiggle * finish up name input * Fix up add tags * clean up param * clean up scss * fix resizing name * fix resizing name * fix resize bug * clean up edit spacing * ignore on esc * fix input bug * focus input on clear * build * fix up add tags clickablity * remove scrollbars * move into folders * clean up multiple patch req * remove padding top from edit * update tags on enter * build * rollout blur on enter behavior * rollout esc behavior * fix tags bug when duplicating tags * move key to reload tags * update header spacing * build * update hex case * refactor workflow title * remove unusued prop * keep focus on error, fix bug on error * Fix bug with name / tags toggle on error * fix connection push bug * :spakles: Implement wait functionality * 🐛 Do not delete waiting executions with prune * ⚡ Improve SQLite migration to not lose execution data anymore * ⚡ Make it possible to restart waiting execution via webhook * ⚡ Add missing file * 🐛 Some more merge fixes * ⚡ Do not show error for Wait-Nodes if in time-mode * ⚡ Make $executionId available in expressions * 👕 Fix lint issue * 👕 Fix lint issue * 👕 Fix lint issue * ⚡ Set the unlimited sleep time as a variable * ⚡ Add also sleeping webhook path to config * ⚡ Make it possible to retrieve restartUrl in workflow * ⚡ Add authentication to Wait-Node in Webhook-Mode * ⚡ Return 404 when trying to restart execution via webhook which does not support it * ✨ Make it possible to set absolute time on Wait-Node * ⚡ Remove not needed imports * ⚡ Fix description format * ✨ Implement missing webhook features on Wait-Node * ⚡ Display webhook variable in NodeWebhooks * ⚡ Include also date in displayed sleep time * ⚡ Make it possible to see sleep time on node * ⚡ Make sure that no executions does get executed twice * ⚡ Add comment * ⚡ Further improvements * ⚡ Make Wait-Node easier to use * ✨ Add support for "notice" parameter type * Fixing wait node to work with queue, improved logging and execution view * Added support for mysql and pg * ✨ Add support for webhook postfix path * ✨ Make it possible to stop sleeping executions * ⚡ Fix issue with webhook paths in not webhook mode * ⚡ Remove not needed console.log * ⚡ Update TODOs * ⚡ Increase min time of workflow staying active to descrease possible issue with overlap * 👕 Fix lint issue * 🐛 Fix issues with webhooks * ⚡ Make error message clearer * ⚡ Fix issue with missing execution ID in scaling mode * Fixed execution list to correctly display waiting executins * Feature: enable webhook wait workflows to continue after specified time * Fixed linting * ⚡ Improve waiting description text * ⚡ Fix parameter display issue and rename * ⚡ Remove comment * ⚡ Do not display webhooks on Wait-Node * Changed wording from restart to resume on wait node * Fixed wording and inconsistent screen when changing resume modes * Removed dots from the descriptions * Changed docs url and renaming postfix to suffix * Changed names from sleep to wait * ⚡ Apply suggestions from ben Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> * Some fixes by Ben * ⚡ Remove console.logs * ⚡ Fixes and improvements Co-authored-by: Mutasem <mutdmour@gmail.com> Co-authored-by: Iván Ovejero <ivov.src@gmail.com> Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com> Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> Co-authored-by: Omar Ajoue <krynble@gmail.com>
423 lines
12 KiB
TypeScript
423 lines
12 KiB
TypeScript
import {
|
|
IDataObject,
|
|
INodeExecutionData,
|
|
INodeParameters,
|
|
IRunExecutionData,
|
|
IWorkflowDataProxyAdditionalKeys,
|
|
IWorkflowDataProxyData,
|
|
NodeHelpers,
|
|
NodeParameterValue,
|
|
Workflow,
|
|
WorkflowExecuteMode,
|
|
} from './';
|
|
|
|
|
|
|
|
export class WorkflowDataProxy {
|
|
private workflow: Workflow;
|
|
private runExecutionData: IRunExecutionData | null;
|
|
private defaultReturnRunIndex: number;
|
|
private runIndex: number;
|
|
private itemIndex: number;
|
|
private activeNodeName: string;
|
|
private connectionInputData: INodeExecutionData[];
|
|
private siblingParameters: INodeParameters;
|
|
private mode: WorkflowExecuteMode;
|
|
private selfData: IDataObject;
|
|
private additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
|
|
|
|
|
|
|
constructor(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], siblingParameters: INodeParameters, mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, defaultReturnRunIndex = -1, selfData = {}) {
|
|
this.workflow = workflow;
|
|
this.runExecutionData = runExecutionData;
|
|
this.defaultReturnRunIndex = defaultReturnRunIndex;
|
|
this.runIndex = runIndex;
|
|
this.itemIndex = itemIndex;
|
|
this.activeNodeName = activeNodeName;
|
|
this.connectionInputData = connectionInputData;
|
|
this.siblingParameters = siblingParameters;
|
|
this.mode = mode;
|
|
this.selfData = selfData;
|
|
this.additionalKeys = additionalKeys;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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];
|
|
},
|
|
});
|
|
}
|
|
|
|
|
|
|
|
private selfGetter() {
|
|
const that = this;
|
|
|
|
return new Proxy({}, {
|
|
ownKeys(target) {
|
|
return Reflect.ownKeys(target);
|
|
},
|
|
get(target, name, receiver) {
|
|
name = name.toString();
|
|
return that.selfData[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();
|
|
|
|
let returnValue: INodeParameters | NodeParameterValue | NodeParameterValue[] | INodeParameters[];
|
|
if (name[0] === '&') {
|
|
const key = name.slice(1);
|
|
if (!that.siblingParameters.hasOwnProperty(key)) {
|
|
throw new Error(`Could not find sibling parameter "${key}" on node "${nodeName}"`);
|
|
|
|
}
|
|
returnValue = that.siblingParameters[key];
|
|
} else {
|
|
if (!node.parameters.hasOwnProperty(name)) {
|
|
// Parameter does not exist on node
|
|
throw new Error(`Could not find parameter "${name}" on node "${nodeName}"`);
|
|
}
|
|
|
|
returnValue = node.parameters[name];
|
|
}
|
|
|
|
if (typeof returnValue === 'string' && returnValue.charAt(0) === '=') {
|
|
// The found value is an expression so resolve it
|
|
return that.workflow.expression.getParameterValue(returnValue, that.runExecutionData, that.runIndex, that.itemIndex, that.activeNodeName, that.connectionInputData, that.mode, that.additionalKeys);
|
|
}
|
|
|
|
return returnValue;
|
|
},
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the node ExecutionData
|
|
*
|
|
* @private
|
|
* @param {string} nodeName The name of the node query data from
|
|
* @param {boolean} [shortSyntax=false] If short syntax got used
|
|
* @param {number} [outputIndex] The index of the output, if not given the first one gets used
|
|
* @param {number} [runIndex] The index of the run, if not given the current one does get used
|
|
* @returns {INodeExecutionData[]}
|
|
* @memberof WorkflowDataProxy
|
|
*/
|
|
private getNodeExecutionData(nodeName: string, shortSyntax = false, outputIndex?: number, runIndex?: number): INodeExecutionData[] {
|
|
const that = this;
|
|
|
|
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}"`);
|
|
}
|
|
|
|
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex;
|
|
runIndex = runIndex === -1 ? (that.runExecutionData.resultData.runData[nodeName].length -1) : runIndex;
|
|
|
|
if (that.runExecutionData.resultData.runData[nodeName].length < runIndex) {
|
|
throw new Error(`No execution data found for run "${runIndex}" of node "${nodeName}"`);
|
|
}
|
|
|
|
const taskData = that.runExecutionData.resultData.runData[nodeName][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.`);
|
|
}
|
|
|
|
// Check from which output to read the data.
|
|
// Depends on how the nodes are connected.
|
|
// (example "IF" node. If node is connected to "true" or to "false" output)
|
|
if (outputIndex === undefined) {
|
|
const outputIndex = that.workflow.getNodeConnectionOutputIndex(that.activeNodeName, nodeName, 'main');
|
|
|
|
if (outputIndex === undefined) {
|
|
throw new Error(`The node "${that.activeNodeName}" is not connected with node "${nodeName}" so no data can get returned from it.`);
|
|
}
|
|
}
|
|
|
|
if (outputIndex === undefined) {
|
|
outputIndex = 0;
|
|
}
|
|
|
|
if (taskData.main.length < outputIndex) {
|
|
throw new Error(`No data found from "main" input with index "${outputIndex}" via which node is connected with.`);
|
|
}
|
|
|
|
executionData = taskData.main[outputIndex] 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;
|
|
}
|
|
|
|
return executionData;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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', 'json'].includes(name)) {
|
|
const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined);
|
|
|
|
if (executionData.length <= that.itemIndex) {
|
|
throw new Error(`No data found for item-index: "${that.itemIndex}"`);
|
|
}
|
|
|
|
if (['data', 'json'].includes(name as string)) {
|
|
// JSON-Data
|
|
return executionData[that.itemIndex].json;
|
|
} else if (name === 'binary') {
|
|
// Binary-Data
|
|
const returnData: IDataObject = {};
|
|
|
|
if (!executionData[that.itemIndex].binary) {
|
|
return returnData;
|
|
}
|
|
|
|
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);
|
|
} else if (name === 'runIndex') {
|
|
if (that.runExecutionData === null || !that.runExecutionData.resultData.runData[nodeName]) {
|
|
return -1;
|
|
}
|
|
return that.runExecutionData.resultData.runData[nodeName].length - 1;
|
|
}
|
|
|
|
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 proxt to query data from the workflow
|
|
*
|
|
* @private
|
|
* @returns
|
|
* @memberof WorkflowDataProxy
|
|
*/
|
|
private workflowGetter() {
|
|
const allowedValues = [
|
|
'active',
|
|
'id',
|
|
'name',
|
|
];
|
|
const that = this;
|
|
|
|
return new Proxy({}, {
|
|
get(target, name, receiver) {
|
|
if (!allowedValues.includes(name.toString())) {
|
|
throw new Error(`The key "${name.toString()}" is not supported!`);
|
|
}
|
|
|
|
// @ts-ignore
|
|
return that.workflow[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(): IWorkflowDataProxyData {
|
|
const that = this;
|
|
|
|
const base = {
|
|
$binary: {}, // Placeholder
|
|
$data: {}, // Placeholder
|
|
$env: this.envGetter(),
|
|
$evaluateExpression: (expression: string, itemIndex?: number) => {
|
|
itemIndex = itemIndex || that.itemIndex;
|
|
return that.workflow.expression.getParameterValue('=' + expression, that.runExecutionData, that.runIndex, itemIndex, that.activeNodeName, that.connectionInputData, that.mode, that.additionalKeys);
|
|
},
|
|
$item: (itemIndex: number, runIndex?: number) => {
|
|
const defaultReturnRunIndex = runIndex === undefined ? -1 : runIndex;
|
|
const dataProxy = new WorkflowDataProxy(this.workflow, this.runExecutionData, this.runIndex, itemIndex, this.activeNodeName, this.connectionInputData, that.siblingParameters, that.mode, that.additionalKeys, defaultReturnRunIndex);
|
|
return dataProxy.getDataProxy();
|
|
},
|
|
$items: (nodeName?: string, outputIndex?: number, runIndex?: number) => {
|
|
let executionData: INodeExecutionData[];
|
|
|
|
if (nodeName === undefined) {
|
|
executionData = that.connectionInputData;
|
|
} else {
|
|
outputIndex = outputIndex || 0;
|
|
runIndex = runIndex === undefined ? -1 : runIndex;
|
|
executionData = that.getNodeExecutionData(nodeName, false, outputIndex, runIndex);
|
|
}
|
|
|
|
return executionData;
|
|
},
|
|
$json: {}, // Placeholder
|
|
$node: this.nodeGetter(),
|
|
$self: this.selfGetter(),
|
|
$parameter: this.nodeParameterGetter(this.activeNodeName),
|
|
$position: this.itemIndex,
|
|
$runIndex: this.runIndex,
|
|
$mode: this.mode,
|
|
$workflow: this.workflowGetter(),
|
|
...that.additionalKeys,
|
|
};
|
|
|
|
return new Proxy(base, {
|
|
get(target, name, receiver) {
|
|
if (['$data', '$json'].includes(name as string)) {
|
|
// @ts-ignore
|
|
return that.nodeDataGetter(that.activeNodeName, true).json;
|
|
} else if (name === '$binary') {
|
|
// @ts-ignore
|
|
return that.nodeDataGetter(that.activeNodeName, true).binary;
|
|
}
|
|
|
|
return Reflect.get(target, name, receiver);
|
|
},
|
|
});
|
|
}
|
|
}
|