Make it possible to retry node on error

This commit is contained in:
Jan Oberhauser 2019-07-18 19:26:16 +02:00
parent 9c0b1a9cec
commit 32204d35d1
4 changed files with 162 additions and 79 deletions

View file

@ -360,7 +360,34 @@ export class WorkflowExecute {
// is very slow so only do if needed // is very slow so only do if needed
startTime = new Date().getTime(); startTime = new Date().getTime();
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));
}
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 { 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; 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); nodeSuccessData = await workflow.runNode(executionData.node, JSON.parse(JSON.stringify(executionData.data)), runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
@ -368,15 +395,17 @@ export class WorkflowExecute {
// If null gets returned it means that the node did succeed // If null gets returned it means that the node did succeed
// but did not have any data. So the branch should end // but did not have any data. So the branch should end
// (meaning the nodes afterwards should not be processed) // (meaning the nodes afterwards should not be processed)
continue; continue executionLoop;
} }
break;
} catch (error) { } catch (error) {
executionError = { executionError = {
message: error.message, message: error.message,
stack: error.stack, stack: error.stack,
}; };
} }
}
// Add the data to return to the user // Add the data to return to the user
// (currently does not get cloned as data does not get changed, maybe later we should do that?!?!) // (currently does not get cloned as data does not get changed, maybe later we should do that?!?!)

View file

@ -24,34 +24,7 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Node"> <el-tab-pane label="Node">
<parameter-input-list :parameters="nodeSettings" :hideDelete="true" :nodeValues="nodeValues" path="" @valueChanged="valueChanged" />
<parameter-input-full
:parameter="nodeSettingsParameterColor"
:value="nodeValues.color"
:displayOptions="false"
path="color"
@valueChanged="valueChanged"
/>
<div v-if="!isColorDefaultValue" class="color-reset-button-wrapper">
<font-awesome-icon icon="redo" @click="resetColor('color')" class="color-reset-button clickable" title="Reset node color" />
</div>
<parameter-input-full
:parameter="nodeSettingsParameterNotes"
:value="nodeValues.notes"
:displayOptions="false"
path="notes"
@valueChanged="valueChanged"
/>
<parameter-input-full
:parameter="nodeSettingsParameterContinueOnFail"
:value="nodeValues.continueOnFail"
:displayOptions="false"
path="continueOnFail"
@valueChanged="valueChanged"
/>
<parameter-input-list :parameters="parametersSetting" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" /> <parameter-input-list :parameters="parametersSetting" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -160,10 +133,15 @@ export default mixins(
nodeValues: { nodeValues: {
color: '#ff0000', color: '#ff0000',
continueOnFail: false, continueOnFail: false,
retryOnFail: false,
maxTries: 3,
waitBetweenTries: 1000,
notes: '', notes: '',
parameters: {}, parameters: {},
} as INodeParameters, } as INodeParameters,
nodeSettingsParameterNotes: {
nodeSettings: [
{
displayName: 'Notes', displayName: 'Notes',
name: 'notes', name: 'notes',
type: 'string', type: 'string',
@ -171,24 +149,72 @@ export default mixins(
rows: 5, rows: 5,
}, },
default: '', default: '',
noDataExpression: true,
description: 'Notes to save with the node.', description: 'Notes to save with the node.',
} as INodeProperties, },
{
nodeSettingsParameterColor: {
displayName: 'Node Color', displayName: 'Node Color',
name: 'color', name: 'color',
type: 'color', type: 'color',
default: '', default: '#ff0000',
noDataExpression: true,
description: 'The color of the node in the flow.', description: 'The color of the node in the flow.',
} as INodeProperties, },
{
nodeSettingsParameterContinueOnFail: { 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', displayName: 'Continue On Fail',
name: 'continueOnFail', name: 'continueOnFail',
type: 'boolean', type: 'boolean',
default: false, default: false,
description: 'If set and the node fails the workflow will simply continue running.<br />It will then simply pass through the input data so the workflow has<br />to be set up to handle the case that different data gets returned.', noDataExpression: true,
} as INodeProperties, description: 'If activated and the node fails the workflow will simply continue running.<br />It will then simply pass through the input data so the workflow has<br />to be set up to handle the case that different data gets returned.',
},
] as INodeProperties[],
}; };
}, },
@ -199,14 +225,6 @@ export default mixins(
}, },
methods: { methods: {
noOp () {}, 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) { setValue (name: string, value: NodeParameterValue) {
const nameParts = name.split('.'); const nameParts = name.split('.');
let lastNamePart: string | undefined = nameParts.pop(); let lastNamePart: string | undefined = nameParts.pop();
@ -405,20 +423,50 @@ export default mixins(
if (this.nodeType !== null) { if (this.nodeType !== null) {
this.nodeValid = true; this.nodeValid = true;
const foundNodeSettings = [];
if (this.node.color) { if (this.node.color) {
foundNodeSettings.push('color');
Vue.set(this.nodeValues, 'color', this.node.color); Vue.set(this.nodeValues, 'color', this.node.color);
} else {
Vue.set(this.nodeValues, 'color', '#ff0000');
} }
if (this.node.notes) { if (this.node.notes) {
foundNodeSettings.push('notes');
Vue.set(this.nodeValues, 'notes', this.node.notes); Vue.set(this.nodeValues, 'notes', this.node.notes);
} }
if (this.node.continueOnFail) { if (this.node.continueOnFail) {
foundNodeSettings.push('continueOnFail');
Vue.set(this.nodeValues, 'continueOnFail', this.node.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))); Vue.set(this.nodeValues, 'parameters', JSON.parse(JSON.stringify(this.node.parameters)));
} else { } else {
this.nodeValid = false; this.nodeValid = false;

View file

@ -77,8 +77,8 @@
<font-awesome-icon icon="cogs" class="reset-icon clickable" title="Parameter Options"/> <font-awesome-icon icon="cogs" class="reset-icon clickable" title="Parameter Options"/>
</span> </span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item command="addExpression" v-if="!isValueExpression">Add Expression</el-dropdown-item> <el-dropdown-item command="addExpression" v-if="parameter.noDataExpression !== true && !isValueExpression">Add Expression</el-dropdown-item>
<el-dropdown-item command="removeExpression" v-if="isValueExpression">Remove Expression</el-dropdown-item> <el-dropdown-item command="removeExpression" v-if="parameter.noDataExpression !== true && isValueExpression">Remove Expression</el-dropdown-item>
<el-dropdown-item command="resetValue" :disabled="isDefault" divided>Reset Value</el-dropdown-item> <el-dropdown-item command="resetValue" :disabled="isDefault" divided>Reset Value</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
@ -325,6 +325,9 @@ export default mixins(
return this.parameter.default === this.value; return this.parameter.default === this.value;
}, },
isValueExpression () { isValueExpression () {
if (this.parameter.noDataExpression === true) {
return false;
}
if (typeof this.value === 'string' && this.value.charAt(0) === '=') { if (typeof this.value === 'string' && this.value.charAt(0) === '=') {
return true; return true;
} }

View file

@ -215,6 +215,9 @@ export interface INode {
type: string; type: string;
position: [number, number]; position: [number, number];
disabled?: boolean; disabled?: boolean;
retryOnFail?: boolean;
maxTries?: number;
waitBetweenTries?: number;
continueOnFail?: boolean; continueOnFail?: boolean;
parameters: INodeParameters; parameters: INodeParameters;
credentials?: INodeCredentials; credentials?: INodeCredentials;