fix(core): Make node execution order configurable, and backward-compatible (#6507)
* fix(core): Make node execution order configurable, and backward-compatible * ⚡ Also add new Merge-Node behaviour * ⚡ Fix typo * Fix lint issue * update labels * rename legacy to v0 * remove the unnecessary log * default all new workflows to use v1 execution-order * remove the controller changes * clone default settings to avoid it getting modified --------- Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
committed by
GitHub
parent
f0dfc3cf4e
commit
d97edbcffa
@@ -702,6 +702,7 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||
maxExecutionTimeout?: number;
|
||||
callerIds?: string;
|
||||
callerPolicy?: WorkflowSettings.CallerPolicy;
|
||||
executionOrder: NonNullable<IWorkflowSettingsWorkflow['executionOrder']>;
|
||||
}
|
||||
|
||||
export interface ITimeoutHMS {
|
||||
|
||||
@@ -7,6 +7,7 @@ export async function getNewWorkflow(context: IRestApiContext, name?: string) {
|
||||
return {
|
||||
name: response.name,
|
||||
onboardingFlowEnabled: response.onboardingFlowEnabled === true,
|
||||
settings: response.defaultSettings,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,31 @@
|
||||
>
|
||||
<template #content>
|
||||
<div v-loading="isLoading" class="workflow-settings" data-test-id="workflow-settings-dialog">
|
||||
<el-row>
|
||||
<el-col :span="10" class="setting-name">
|
||||
{{ $locale.baseText('workflowSettings.executionOrder') + ':' }}
|
||||
</el-col>
|
||||
<el-col :span="14" class="ignore-key-press">
|
||||
<n8n-select
|
||||
v-model="workflowSettings.executionOrder"
|
||||
placeholder="Select Execution Order"
|
||||
size="medium"
|
||||
filterable
|
||||
:disabled="readOnlyEnv"
|
||||
:limit-popper-width="true"
|
||||
data-test-id="workflow-settings-execution-order"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="option in executionOrderOptions"
|
||||
:key="option.key"
|
||||
:label="option.value"
|
||||
:value="option.key"
|
||||
>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="10" class="setting-name">
|
||||
{{ $locale.baseText('workflowSettings.errorWorkflow') + ':' }}
|
||||
@@ -421,9 +446,14 @@ export default defineComponent({
|
||||
saveDataSuccessExecutionOptions: [] as Array<{ key: string; value: string }>,
|
||||
saveExecutionProgressOptions: [] as Array<{ key: string | boolean; value: string }>,
|
||||
saveManualOptions: [] as Array<{ key: string | boolean; value: string }>,
|
||||
executionOrderOptions: [
|
||||
{ key: 'v0', value: 'v0 (legacy)' },
|
||||
{ key: 'v1', value: 'v1 (recommended)' },
|
||||
] as Array<{ key: string; value: string }>,
|
||||
timezones: [] as Array<{ key: string; value: string }>,
|
||||
workflowSettings: {} as IWorkflowSettings,
|
||||
workflows: [] as IWorkflowShortResponse[],
|
||||
executionOrder: 'v0',
|
||||
executionTimeout: 0,
|
||||
maxExecutionTimeout: 0,
|
||||
timeoutHMS: { hours: 0, minutes: 0, seconds: 0 } as ITimeoutHMS,
|
||||
@@ -535,6 +565,9 @@ export default defineComponent({
|
||||
if (workflowSettings.maxExecutionTimeout === undefined) {
|
||||
workflowSettings.maxExecutionTimeout = this.rootStore.maxExecutionTimeout;
|
||||
}
|
||||
if (workflowSettings.executionOrder === undefined) {
|
||||
workflowSettings.executionOrder = 'v0';
|
||||
}
|
||||
|
||||
this.workflowSettings = workflowSettings;
|
||||
this.timeoutHMS = this.convertToHMS(workflowSettings.executionTimeout);
|
||||
|
||||
@@ -1595,6 +1595,7 @@
|
||||
"workflowSettings.defaultTimezone": "Default - {defaultTimezoneValue}",
|
||||
"workflowSettings.defaultTimezoneNotValid": "Default Timezone not valid",
|
||||
"workflowSettings.errorWorkflow": "Error Workflow",
|
||||
"workflowSettings.executionOrder": "Execution Order",
|
||||
"workflowSettings.helpTexts.errorWorkflow": "A second workflow to run if the current one fails.<br />The second workflow should an 'Error Trigger' node.",
|
||||
"workflowSettings.helpTexts.executionTimeout": "How long the workflow should wait before timing out",
|
||||
"workflowSettings.helpTexts.executionTimeoutToggle": "Whether to cancel workflow execution after a defined time",
|
||||
|
||||
@@ -82,24 +82,29 @@ import {
|
||||
} from '@/utils';
|
||||
import { useNDVStore } from './ndv.store';
|
||||
import { useNodeTypesStore } from './nodeTypes.store';
|
||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import type { NodeMetadataMap } from '@/Interface';
|
||||
|
||||
const createEmptyWorkflow = (): IWorkflowDb => ({
|
||||
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
const defaults: Omit<IWorkflowDb, 'id'> & { settings: NonNullable<IWorkflowDb['settings']> } = {
|
||||
name: '',
|
||||
active: false,
|
||||
createdAt: -1,
|
||||
updatedAt: -1,
|
||||
connections: {},
|
||||
nodes: [],
|
||||
settings: {},
|
||||
settings: {
|
||||
executionOrder: 'v1',
|
||||
},
|
||||
tags: [],
|
||||
pinData: {},
|
||||
versionId: '',
|
||||
usedCredentials: [],
|
||||
};
|
||||
|
||||
const createEmptyWorkflow = (): IWorkflowDb => ({
|
||||
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
...defaults,
|
||||
});
|
||||
|
||||
let cachedWorkflowKey: string | null = '';
|
||||
@@ -135,10 +140,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
return this.workflow.versionId;
|
||||
},
|
||||
workflowSettings(): IWorkflowSettings {
|
||||
if (this.workflow.settings === undefined) {
|
||||
return {};
|
||||
}
|
||||
return this.workflow.settings;
|
||||
return this.workflow.settings ?? { ...defaults.settings };
|
||||
},
|
||||
workflowTags(): string[] {
|
||||
return this.workflow.tags as string[];
|
||||
@@ -318,7 +320,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
// This has the advantage that it is very fast and does not cause problems with vuex
|
||||
// when the workflow replaces the node-parameters.
|
||||
getNodes(): INodeUi[] {
|
||||
const nodes = useWorkflowsStore().allNodes;
|
||||
const nodes = this.allNodes;
|
||||
const returnNodes: INodeUi[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
@@ -331,23 +333,21 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
// Returns a workflow instance.
|
||||
getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow {
|
||||
const nodeTypes = this.getNodeTypes();
|
||||
let workflowId: string | undefined = useWorkflowsStore().workflowId;
|
||||
let workflowId: string | undefined = this.workflowId;
|
||||
if (workflowId && workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
||||
workflowId = undefined;
|
||||
}
|
||||
|
||||
const workflowName = useWorkflowsStore().workflowName;
|
||||
|
||||
cachedWorkflow = new Workflow({
|
||||
id: workflowId,
|
||||
name: workflowName,
|
||||
name: this.workflowName,
|
||||
nodes: copyData ? deepCopy(nodes) : nodes,
|
||||
connections: copyData ? deepCopy(connections) : connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
settings: useWorkflowsStore().workflowSettings,
|
||||
settings: this.workflowSettings,
|
||||
// @ts-ignore
|
||||
pinData: useWorkflowsStore().getPinData,
|
||||
pinData: this.getPinData,
|
||||
});
|
||||
|
||||
return cachedWorkflow;
|
||||
@@ -393,11 +393,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
},
|
||||
|
||||
async getNewWorkflowData(name?: string): Promise<INewWorkflowData> {
|
||||
const workflowsEEStore = useWorkflowsEEStore();
|
||||
|
||||
let workflowData = {
|
||||
name: '',
|
||||
onboardingFlowEnabled: false,
|
||||
settings: { ...defaults.settings },
|
||||
};
|
||||
try {
|
||||
const rootStore = useRootStore();
|
||||
@@ -426,6 +425,25 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
}
|
||||
},
|
||||
|
||||
resetState(): void {
|
||||
this.removeAllConnections({ setStateDirty: false });
|
||||
this.removeAllNodes({ setStateDirty: false, removePinData: true });
|
||||
|
||||
// Reset workflow execution data
|
||||
this.setWorkflowExecutionData(null);
|
||||
this.resetAllNodesIssues();
|
||||
|
||||
this.setActive(defaults.active);
|
||||
this.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
||||
this.setWorkflowName({ newName: '', setStateDirty: false });
|
||||
this.setWorkflowSettings({ ...defaults.settings });
|
||||
this.setWorkflowTagIds([]);
|
||||
|
||||
this.activeExecutionId = null;
|
||||
this.executingNode = null;
|
||||
this.executionWaitingForWebhook = false;
|
||||
},
|
||||
|
||||
setWorkflowId(id: string): void {
|
||||
this.workflow.id = id === 'new' ? PLACEHOLDER_EMPTY_WORKFLOW_ID : id;
|
||||
},
|
||||
@@ -632,7 +650,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
...(!this.workflow.hasOwnProperty('updatedAt') ? { updatedAt: -1 } : {}),
|
||||
...(!this.workflow.hasOwnProperty('id') ? { id: PLACEHOLDER_EMPTY_WORKFLOW_ID } : {}),
|
||||
...(!this.workflow.hasOwnProperty('nodes') ? { nodes: [] } : {}),
|
||||
...(!this.workflow.hasOwnProperty('settings') ? { settings: {} } : {}),
|
||||
...(!this.workflow.hasOwnProperty('settings')
|
||||
? { settings: { ...defaults.settings } }
|
||||
: {}),
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -3523,22 +3523,7 @@ export default defineComponent({
|
||||
// Ignore all errors
|
||||
});
|
||||
}
|
||||
this.workflowsStore.removeAllConnections({ setStateDirty: false });
|
||||
this.workflowsStore.removeAllNodes({ setStateDirty: false, removePinData: true });
|
||||
|
||||
// Reset workflow execution data
|
||||
this.workflowsStore.setWorkflowExecutionData(null);
|
||||
this.workflowsStore.resetAllNodesIssues();
|
||||
|
||||
this.workflowsStore.setActive(false);
|
||||
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
||||
this.workflowsStore.setWorkflowName({ newName: '', setStateDirty: false });
|
||||
this.workflowsStore.setWorkflowSettings({});
|
||||
this.workflowsStore.setWorkflowTagIds([]);
|
||||
|
||||
this.workflowsStore.activeExecutionId = null;
|
||||
this.workflowsStore.executingNode = null;
|
||||
this.workflowsStore.executionWaitingForWebhook = false;
|
||||
this.workflowsStore.resetState();
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
|
||||
this.uiStore.resetSelectedNodes();
|
||||
|
||||
Reference in New Issue
Block a user