diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index f7a2caf544..b8bd8512f7 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -360,22 +360,51 @@ export class WorkflowExecute { // is very slow so only do if needed startTime = new Date().getTime(); - try { - runExecutionData.resultData.lastNodeExecuted = executionData.node.name; - nodeSuccessData = await workflow.runNode(executionData.node, JSON.parse(JSON.stringify(executionData.data)), runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode); + let maxTries = 1; + if (executionData.node.retryOnFail === true) { + // TODO: Remove the hardcoded default-values here and also in NodeSettings.vue + maxTries = Math.min(5, Math.max(2, executionData.node.maxTries || 3)); + } - if (nodeSuccessData === null) { - // If null gets returned it means that the node did succeed - // but did not have any data. So the branch should end - // (meaning the nodes afterwards should not be processed) - continue; + let waitBetweenTries = 0; + if (executionData.node.retryOnFail === true) { + // TODO: Remove the hardcoded default-values here and also in NodeSettings.vue + waitBetweenTries = Math.min(5000, Math.max(0, executionData.node.waitBetweenTries || 1000)); + } + + for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) { + try { + if (tryIndex !== 0) { + // Reset executionError from previous error try + executionError = undefined; + if (waitBetweenTries !== 0) { + // TODO: Improve that in the future and check if other nodes can + // be executed in the meantime + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, waitBetweenTries); + }); + } + } + + runExecutionData.resultData.lastNodeExecuted = executionData.node.name; + nodeSuccessData = await workflow.runNode(executionData.node, JSON.parse(JSON.stringify(executionData.data)), runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode); + + if (nodeSuccessData === null) { + // If null gets returned it means that the node did succeed + // but did not have any data. So the branch should end + // (meaning the nodes afterwards should not be processed) + continue executionLoop; + } + + break; + } catch (error) { + executionError = { + message: error.message, + stack: error.stack, + }; } - - } catch (error) { - executionError = { - message: error.message, - stack: error.stack, - }; } // Add the data to return to the user diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index e2f380ef20..1e50c26b33 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -24,34 +24,7 @@ - - -
- -
- - - - - +
@@ -160,35 +133,88 @@ export default mixins( nodeValues: { color: '#ff0000', continueOnFail: false, + retryOnFail: false, + maxTries: 3, + waitBetweenTries: 1000, notes: '', parameters: {}, } as INodeParameters, - nodeSettingsParameterNotes: { - displayName: 'Notes', - name: 'notes', - type: 'string', - typeOptions: { - rows: 5, + + nodeSettings: [ + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + rows: 5, + }, + default: '', + noDataExpression: true, + description: 'Notes to save with the node.', }, - default: '', - description: 'Notes to save with the node.', - } as INodeProperties, - - nodeSettingsParameterColor: { - displayName: 'Node Color', - name: 'color', - type: 'color', - default: '', - description: 'The color of the node in the flow.', - } as INodeProperties, - - nodeSettingsParameterContinueOnFail: { - displayName: 'Continue On Fail', - name: 'continueOnFail', - type: 'boolean', - default: false, - description: 'If set and the node fails the workflow will simply continue running.
It will then simply pass through the input data so the workflow has
to be set up to handle the case that different data gets returned.', - } as INodeProperties, + { + displayName: 'Node Color', + name: 'color', + type: 'color', + default: '#ff0000', + noDataExpression: true, + description: 'The color of the node in the flow.', + }, + { + displayName: 'Retry On Fail', + name: 'retryOnFail', + type: 'boolean', + default: false, + noDataExpression: true, + description: 'If activated it will automatically retry the node again multiple times.', + }, + { + displayName: 'Max. Tries', + name: 'maxTries', + type: 'number', + typeOptions: { + minValue: 2, + maxValue: 5, + }, + default: 3, + displayOptions: { + show: { + retryOnFail: [ + true, + ], + }, + }, + noDataExpression: true, + description: 'How often it should try to execute the node before it should fail.', + }, + { + displayName: 'Wait Between Tries', + name: 'waitBetweenTries', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 5000, + }, + default: 1000, + displayOptions: { + show: { + retryOnFail: [ + true, + ], + }, + }, + noDataExpression: true, + description: 'How long to wait between ties. Value in ms.', + }, + { + displayName: 'Continue On Fail', + name: 'continueOnFail', + type: 'boolean', + default: false, + noDataExpression: true, + description: 'If activated and the node fails the workflow will simply continue running.
It will then simply pass through the input data so the workflow has
to be set up to handle the case that different data gets returned.', + }, + ] as INodeProperties[], }; }, @@ -199,14 +225,6 @@ export default mixins( }, methods: { noOp () {}, - resetColor () { - const activeNode = this.node as INodeUi; - const activeNodeType = this.nodeType; - if (activeNodeType !== null) { - this.setValue('color', activeNodeType.defaults.color as NodeParameterValue); - this.valueChanged({ name: 'color', value: activeNodeType.defaults.color } as IUpdateInformation); - } - }, setValue (name: string, value: NodeParameterValue) { const nameParts = name.split('.'); let lastNamePart: string | undefined = nameParts.pop(); @@ -405,20 +423,50 @@ export default mixins( if (this.nodeType !== null) { this.nodeValid = true; + const foundNodeSettings = []; if (this.node.color) { + foundNodeSettings.push('color'); Vue.set(this.nodeValues, 'color', this.node.color); - } else { - Vue.set(this.nodeValues, 'color', '#ff0000'); } if (this.node.notes) { + foundNodeSettings.push('notes'); Vue.set(this.nodeValues, 'notes', this.node.notes); } if (this.node.continueOnFail) { + foundNodeSettings.push('continueOnFail'); Vue.set(this.nodeValues, 'continueOnFail', this.node.continueOnFail); } + if (this.node.retryOnFail) { + foundNodeSettings.push('retryOnFail'); + Vue.set(this.nodeValues, 'retryOnFail', this.node.retryOnFail); + } + + if (this.node.maxTries) { + foundNodeSettings.push('maxTries'); + Vue.set(this.nodeValues, 'maxTries', this.node.maxTries); + } + + if (this.node.waitBetweenTries) { + foundNodeSettings.push('waitBetweenTries'); + Vue.set(this.nodeValues, 'waitBetweenTries', this.node.waitBetweenTries); + } + + // Set default node settings + for (const nodeSetting of this.nodeSettings) { + if (!foundNodeSettings.includes(nodeSetting.name)) { + // Set default value + Vue.set(this.nodeValues, nodeSetting.name, nodeSetting.default); + } + if (nodeSetting.name === 'color') { + // For color also apply the default node color to the node settings + nodeSetting.default = this.nodeType.defaults.color; + } + } + + Vue.set(this.nodeValues, 'parameters', JSON.parse(JSON.stringify(this.node.parameters))); } else { this.nodeValid = false; diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 22ef294835..ffbd904e48 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -77,8 +77,8 @@ - Add Expression - Remove Expression + Add Expression + Remove Expression Reset Value @@ -325,6 +325,9 @@ export default mixins( return this.parameter.default === this.value; }, isValueExpression () { + if (this.parameter.noDataExpression === true) { + return false; + } if (typeof this.value === 'string' && this.value.charAt(0) === '=') { return true; } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 93fc53b555..264a930a66 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -215,6 +215,9 @@ export interface INode { type: string; position: [number, number]; disabled?: boolean; + retryOnFail?: boolean; + maxTries?: number; + waitBetweenTries?: number; continueOnFail?: boolean; parameters: INodeParameters; credentials?: INodeCredentials;