2023-04-24 03:18:24 -07:00
|
|
|
import type { IExecutionPushResponse, IExecutionResponse, IStartRunData } from '@/Interface';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2023-04-24 03:18:24 -07:00
|
|
|
import type { IRunData, IRunExecutionData, IWorkflowBase } from 'n8n-workflow';
|
|
|
|
import { NodeHelpers, TelemetryHelpers } from 'n8n-workflow';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2022-11-23 04:41:53 -08:00
|
|
|
import { externalHooks } from '@/mixins/externalHooks';
|
|
|
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
|
|
|
import { showMessage } from '@/mixins/showMessage';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
import mixins from 'vue-typed-mixins';
|
2023-04-21 06:48:07 -07:00
|
|
|
import { useTitleChange } from '@/composables/useTitleChange';
|
2022-11-04 06:04:31 -07:00
|
|
|
import { mapStores } from 'pinia';
|
|
|
|
import { useUIStore } from '@/stores/ui';
|
|
|
|
import { useWorkflowsStore } from '@/stores/workflows';
|
|
|
|
import { useRootStore } from '@/stores/n8nRootStore';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2023-04-24 01:50:49 -07:00
|
|
|
export const workflowRun = mixins(externalHooks, workflowHelpers, showMessage).extend({
|
2023-04-21 06:48:07 -07:00
|
|
|
setup() {
|
|
|
|
return {
|
|
|
|
...useTitleChange(),
|
|
|
|
};
|
|
|
|
},
|
2022-11-04 06:04:31 -07:00
|
|
|
computed: {
|
2022-12-14 01:04:10 -08:00
|
|
|
...mapStores(useRootStore, useUIStore, useWorkflowsStore),
|
2022-11-04 06:04:31 -07:00
|
|
|
},
|
2019-06-23 03:35:23 -07:00
|
|
|
methods: {
|
|
|
|
// Starts to executes a workflow on server.
|
2022-12-14 01:04:10 -08:00
|
|
|
async runWorkflowApi(runData: IStartRunData): Promise<IExecutionPushResponse> {
|
2022-11-04 06:04:31 -07:00
|
|
|
if (this.rootStore.pushConnectionActive === false) {
|
2019-06-23 03:35:23 -07:00
|
|
|
// Do not start if the connection to server is not active
|
|
|
|
// because then it can not receive the data as it executes.
|
2022-12-14 01:04:10 -08:00
|
|
|
throw new Error(this.$locale.baseText('workflowRun.noActiveConnectionToTheServer'));
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
2022-11-04 06:04:31 -07:00
|
|
|
this.workflowsStore.subWorkflowExecutionError = null;
|
feat(editor, core, cli): implement new workflow experience (#4358)
* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node (#4108)
* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node
* feat(editor): Do not show duplicate button if canvas contains `maxNodes` amount of nodes
* feat(ManualTrigger node): Implement ManualTrigger node (#4110)
* feat(ManualTrigger node): Implement ManualTrigger node
* :memo: Remove generics doc items from ManualTrigger node
* feat(editor-ui): Trigger tab redesign (#4150)
* :construction: Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory
* :construction: Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations
* :sparkles: Implement MainPanel background scrim
* :recycle: Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType`
* :bug: Fix SlideTransition for all the NodeCreato panels
* :lipstick: Fix cursos for CategoryItem and NodeItem
* :bug: Make sure ALL_NODE_FILTER is always set when MainPanel is mounted
* :art: Address PR comments
* label: Use Array type for CategorizedItems props
* :label: Add proper types for Vue props
* 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel
* 🎨 Use kebab case for main-panel and icon component
* :label: Improve types
* feat(editor-ui): Redesign search input inside node creator panel (#4204)
* :construction: Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory
* :construction: Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations
* :sparkles: Implement MainPanel background scrim
* :recycle: Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType`
* :bug: Fix SlideTransition for all the NodeCreato panels
* :lipstick: Fix cursos for CategoryItem and NodeItem
* :bug: Make sure ALL_NODE_FILTER is always set when MainPanel is mounted
* :art: Address PR comments
* label: Use Array type for CategorizedItems props
* :label: Add proper types for Vue props
* 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel
* :sparkles: Redesign search input and unify usage of categorized items
* :label: Use lowercase "Boolean" as `isSearchVisible` computed return type
* :fire: Remove useless emit
* :sparkles: Implement no result view based on subcategory, minor fixes
* :art: Remove unused properties
* feat(node-email): Change EmailReadImap display name and name (#4239)
* feat(editor-ui): Implement "Choose a Triger" action and related behaviour (#4226)
* :sparkles: Implement "Choose a Triger" action and related behaviour
* :mute: Lint fix
* :recycle: Remove PlaceholderTrigger node, add a button instead
* :art: Merge onMouseEnter and onMouseLeave to a single function
* :bulb: Add comment
* :fire: Remove PlaceholderNode registration
* :art: Rename TriggerPlaceholderButton to CanvasAddButton
* :sparkles: Add method to unregister custom action and rework CanvasAddButton centering logic
* :art: Run `setRecenteredCanvasAddButtonPosition` on `CanvasAddButton` mount
* fix(editor): Fix selecting of node from node-creator panel by clicking
* :twisted_rightwards_arrows: Merge fixes
* fix(editor): Show execute workflow trigger instead of workflow trigger in the trigger helper panel
* feat(editor): Fix node creator panel slide transition (#4261)
* fix(editor): Fix node creator panel slide-in/slide-out transitions
* :art: Fix naming
* :art: Use kebab-case for transition component name
* feat(editor): Disable execution and show notice when user tries to run workflow without enabled triggers
* fix(editor): Address first batch of new WF experience review (#4279)
* fix(editor): Fix first batch of review items
* bug(editor): Fix nodeview canvas add button centering
* :mute: Fix linter errors
* bug(ManualTrigger Node): Fix manual trigger node execution
* fix(editor): Do not show canvas add button in execution or demo mode and prevent clicking if creator is open
* fix(editor): do not show pin data tooltip for manual trigger node
* fix(editor): do not use nodeViewOffset on zoomToFit
* :lipstick: Add margin for last node creator item and set font-weight to 700 for category title
* :sparkles: Position welcome note next to the added trigger node
* :bug: Remve always true welcome note
* feat(editor): Minor UI and UX tweaks (#4328)
* :lipstick: Make top viewport buttons less prominent
* :sparkles: Allow user to switch to all tabs if it contains filter results, move nodecreator state props to its own module
* :mute: Fix linting errors
* :mute: Fix linting errors
* :mute: Fix linting errors
* chore(build): Ping Turbo version to 1.5.5
* :lipstick: Minor traigger panel and node view style changes
* :speech_balloon: Update display name of execute workflow trigger
* feat(core, editor): Update subworkflow execution logic (#4269)
* :sparkles: Implement `findWorkflowStart`
* :zap: Extend `WorkflowOperationError`
* :zap: Add `WorkflowOperationError` to toast
* :blue_book: Extend interface
* :sparkles: Add `subworkflowExecutionError` to store
* :sparkles: Create `SubworkflowOperationError`
* :zap: Render subworkflow error as node error
* :truck: Move subworkflow start validation to `cli`
* :zap: Reset subworkflow execution error state
* :fire: Remove unused import
* :zap: Adjust CLI commands
* :fire: Remove unneeded check
* :fire: Remove stray log
* :zap: Simplify syntax
* :zap: Sort in case both Start and EWT present
* :recycle: Address Omar's feedback
* :fire: Remove unneeded lint exception
* :pencil2: Fix copy
* :shirt: Fix lint
* fix: moved find start node function to catchable place
Co-authored-by: Omar Ajoue <krynble@gmail.com>
* :lipstick: Change ExecuteWorkflow node to primary
* :sparkles: Allow user to navigate to all tab if it contains search results
* :bug: Fixed canvas control button while in demo, disable workflow activation for non-activavle nodes and revert zoomToFit bottom offset
* :fix: Do not chow request text if there's results
* :speech_balloon: Update noResults text
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
2022-10-18 05:23:22 -07:00
|
|
|
|
2022-11-04 06:04:31 -07:00
|
|
|
this.uiStore.addActiveAction('workflowRunning');
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
let response: IExecutionPushResponse;
|
|
|
|
|
|
|
|
try {
|
2023-04-24 01:50:49 -07:00
|
|
|
response = await this.workflowsStore.runWorkflow(runData);
|
2019-06-23 03:35:23 -07:00
|
|
|
} catch (error) {
|
2022-11-04 06:04:31 -07:00
|
|
|
this.uiStore.removeActiveAction('workflowRunning');
|
2019-06-23 03:35:23 -07:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.executionId !== undefined) {
|
2022-11-04 06:04:31 -07:00
|
|
|
this.workflowsStore.activeExecutionId = response.executionId;
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (response.waitingForWebhook === true) {
|
2022-11-04 06:04:31 -07:00
|
|
|
this.workflowsStore.executionWaitingForWebhook = true;
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
},
|
2022-12-14 01:04:10 -08:00
|
|
|
async runWorkflow(
|
|
|
|
nodeName?: string,
|
|
|
|
source?: string,
|
|
|
|
): Promise<IExecutionPushResponse | undefined> {
|
2022-08-12 07:06:57 -07:00
|
|
|
const workflow = this.getCurrentWorkflow();
|
2021-10-18 20:57:49 -07:00
|
|
|
|
2022-11-04 06:04:31 -07:00
|
|
|
if (this.uiStore.isActionActive('workflowRunning')) {
|
2019-06-23 03:35:23 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-21 06:48:07 -07:00
|
|
|
this.titleSet(workflow.name as string, 'EXECUTING');
|
2020-08-25 11:38:09 -07:00
|
|
|
|
2021-09-22 00:23:37 -07:00
|
|
|
this.clearAllStickyNotifications();
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
try {
|
|
|
|
// Check first if the workflow has any issues before execute it
|
2022-11-04 06:04:31 -07:00
|
|
|
const issuesExist = this.workflowsStore.nodesIssuesExist;
|
2019-06-23 03:35:23 -07:00
|
|
|
if (issuesExist === true) {
|
|
|
|
// If issues exist get all of the issues of all nodes
|
2021-10-06 10:02:31 -07:00
|
|
|
const workflowIssues = this.checkReadyForExecution(workflow, nodeName);
|
2019-06-23 03:35:23 -07:00
|
|
|
if (workflowIssues !== null) {
|
|
|
|
const errorMessages = [];
|
|
|
|
let nodeIssues: string[];
|
2022-07-09 23:53:04 -07:00
|
|
|
const trackNodeIssues: Array<{
|
|
|
|
node_type: string;
|
|
|
|
error: string;
|
|
|
|
}> = [];
|
|
|
|
const trackErrorNodeTypes: string[] = [];
|
2019-06-23 03:35:23 -07:00
|
|
|
for (const nodeName of Object.keys(workflowIssues)) {
|
|
|
|
nodeIssues = NodeHelpers.nodeIssuesToString(workflowIssues[nodeName]);
|
2022-07-09 23:53:04 -07:00
|
|
|
let issueNodeType = 'UNKNOWN';
|
2022-11-04 06:04:31 -07:00
|
|
|
const issueNode = this.workflowsStore.getNodeByName(nodeName);
|
2022-07-09 23:53:04 -07:00
|
|
|
|
|
|
|
if (issueNode) {
|
|
|
|
issueNodeType = issueNode.type;
|
|
|
|
}
|
|
|
|
|
|
|
|
trackErrorNodeTypes.push(issueNodeType);
|
|
|
|
const trackNodeIssue = {
|
|
|
|
node_type: issueNodeType,
|
|
|
|
error: '',
|
|
|
|
caused_by_credential: !!workflowIssues[nodeName].credentials,
|
|
|
|
};
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
for (const nodeIssue of nodeIssues) {
|
2022-09-21 01:20:29 -07:00
|
|
|
errorMessages.push(`<strong>${nodeName}</strong>: ${nodeIssue}`);
|
2022-07-09 23:53:04 -07:00
|
|
|
trackNodeIssue.error = trackNodeIssue.error.concat(', ', nodeIssue);
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
2022-07-09 23:53:04 -07:00
|
|
|
trackNodeIssues.push(trackNodeIssue);
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
this.$showMessage({
|
2021-12-15 04:16:53 -08:00
|
|
|
title: this.$locale.baseText('workflowRun.showMessage.title'),
|
2022-09-21 01:20:29 -07:00
|
|
|
message: errorMessages.join('<br />'),
|
2019-06-23 03:35:23 -07:00
|
|
|
type: 'error',
|
|
|
|
duration: 0,
|
|
|
|
});
|
2023-04-21 06:48:07 -07:00
|
|
|
this.titleSet(workflow.name as string, 'ERROR');
|
2021-05-05 17:46:33 -07:00
|
|
|
this.$externalHooks().run('workflowRun.runError', { errorMessages, nodeName });
|
2022-07-09 23:53:04 -07:00
|
|
|
|
|
|
|
this.getWorkflowDataToSave().then((workflowData) => {
|
|
|
|
this.$telemetry.track('Workflow execution preflight failed', {
|
|
|
|
workflow_id: workflow.id,
|
|
|
|
workflow_name: workflow.name,
|
|
|
|
execution_type: nodeName ? 'node' : 'workflow',
|
2022-12-14 01:04:10 -08:00
|
|
|
node_graph_string: JSON.stringify(
|
|
|
|
TelemetryHelpers.generateNodesGraph(
|
|
|
|
workflowData as IWorkflowBase,
|
|
|
|
this.getNodeTypes(),
|
|
|
|
).nodeGraph,
|
|
|
|
),
|
2022-07-09 23:53:04 -07:00
|
|
|
error_node_types: JSON.stringify(trackErrorNodeTypes),
|
|
|
|
errors: JSON.stringify(trackNodeIssues),
|
|
|
|
});
|
|
|
|
});
|
2019-06-23 03:35:23 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the direct parents of the node
|
2021-10-06 10:02:31 -07:00
|
|
|
let directParentNodes: string[] = [];
|
|
|
|
if (nodeName !== undefined) {
|
|
|
|
directParentNodes = workflow.getParentNodes(nodeName, 'main', 1);
|
|
|
|
}
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2022-11-04 06:04:31 -07:00
|
|
|
const runData = this.workflowsStore.getWorkflowRunData;
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
let newRunData: IRunData | undefined;
|
|
|
|
|
|
|
|
const startNodes: string[] = [];
|
|
|
|
|
|
|
|
if (runData !== null && Object.keys(runData).length !== 0) {
|
|
|
|
newRunData = {};
|
|
|
|
|
|
|
|
// Go over the direct parents of the node
|
|
|
|
for (const directParentNode of directParentNodes) {
|
|
|
|
// Go over the parents of that node so that we can get a start
|
|
|
|
// node for each of the branches
|
|
|
|
const parentNodes = workflow.getParentNodes(directParentNode, 'main');
|
|
|
|
|
2022-09-01 06:43:48 -07:00
|
|
|
// Add also the enabled direct parent to be checked
|
|
|
|
if (workflow.nodes[directParentNode].disabled) continue;
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
parentNodes.push(directParentNode);
|
|
|
|
|
|
|
|
for (const parentNode of parentNodes) {
|
|
|
|
if (runData[parentNode] === undefined || runData[parentNode].length === 0) {
|
|
|
|
// When we hit a node which has no data we stop and set it
|
|
|
|
// as a start node the execution from and then go on with other
|
|
|
|
// direct input nodes
|
|
|
|
startNodes.push(parentNode);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
newRunData[parentNode] = runData[parentNode].slice(0, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(newRunData).length === 0) {
|
|
|
|
// If there is no data for any of the parent nodes make sure
|
|
|
|
// that run data is empty that it runs regularly
|
|
|
|
newRunData = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-06 10:02:31 -07:00
|
|
|
if (startNodes.length === 0 && nodeName !== undefined) {
|
2019-06-23 03:35:23 -07:00
|
|
|
startNodes.push(nodeName);
|
|
|
|
}
|
|
|
|
|
2022-11-04 06:04:31 -07:00
|
|
|
const isNewWorkflow = this.workflowsStore.isNewWorkflow;
|
|
|
|
const hasWebhookNode = this.workflowsStore.currentWorkflowHasWebhookNode;
|
2021-10-06 10:06:33 -07:00
|
|
|
if (isNewWorkflow && hasWebhookNode) {
|
|
|
|
await this.saveCurrentWorkflow();
|
|
|
|
}
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
const workflowData = await this.getWorkflowDataToSave();
|
|
|
|
|
|
|
|
const startRunData: IStartRunData = {
|
|
|
|
workflowData,
|
|
|
|
runData: newRunData,
|
2022-07-20 08:50:39 -07:00
|
|
|
pinData: workflowData.pinData,
|
2019-06-23 03:35:23 -07:00
|
|
|
startNodes,
|
|
|
|
};
|
|
|
|
if (nodeName) {
|
|
|
|
startRunData.destinationNode = nodeName;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init the execution data to represent the start of the execution
|
|
|
|
// that data which gets reused is already set and data of newly executed
|
|
|
|
// nodes can be added as it gets pushed in
|
|
|
|
const executionData: IExecutionResponse = {
|
|
|
|
id: '__IN_PROGRESS__',
|
|
|
|
finished: false,
|
|
|
|
mode: 'manual',
|
2019-07-22 11:29:06 -07:00
|
|
|
startedAt: new Date(),
|
|
|
|
stoppedAt: undefined,
|
2019-06-23 03:35:23 -07:00
|
|
|
workflowId: workflow.id,
|
2022-05-23 08:56:15 -07:00
|
|
|
executedNode: nodeName,
|
2019-06-23 03:35:23 -07:00
|
|
|
data: {
|
|
|
|
resultData: {
|
|
|
|
runData: newRunData || {},
|
2022-07-20 08:50:39 -07:00
|
|
|
pinData: workflowData.pinData,
|
2019-06-23 03:35:23 -07:00
|
|
|
startNodes,
|
|
|
|
workflowData,
|
|
|
|
},
|
|
|
|
} as IRunExecutionData,
|
|
|
|
workflowData: {
|
2022-11-04 06:04:31 -07:00
|
|
|
id: this.workflowsStore.workflowId,
|
2019-06-23 03:35:23 -07:00
|
|
|
name: workflowData.name!,
|
|
|
|
active: workflowData.active!,
|
|
|
|
createdAt: 0,
|
|
|
|
updatedAt: 0,
|
|
|
|
...workflowData,
|
|
|
|
},
|
|
|
|
};
|
2022-11-04 06:04:31 -07:00
|
|
|
this.workflowsStore.setWorkflowExecutionData(executionData);
|
2021-11-19 01:17:13 -08:00
|
|
|
this.updateNodesExecutionIssues();
|
2020-08-25 11:38:09 -07:00
|
|
|
|
2022-10-26 01:02:56 -07:00
|
|
|
const runWorkflowApiResponse = await this.runWorkflowApi(startRunData);
|
2021-05-05 17:46:33 -07:00
|
|
|
|
2022-10-26 01:02:56 -07:00
|
|
|
this.$externalHooks().run('workflowRun.runWorkflow', { nodeName, source });
|
2021-05-05 17:46:33 -07:00
|
|
|
|
2022-12-14 01:04:10 -08:00
|
|
|
return runWorkflowApiResponse;
|
2019-06-23 03:35:23 -07:00
|
|
|
} catch (error) {
|
2023-04-21 06:48:07 -07:00
|
|
|
this.titleSet(workflow.name as string, 'ERROR');
|
2022-12-14 01:04:10 -08:00
|
|
|
this.$showError(error, this.$locale.baseText('workflowRun.showError.title'));
|
2019-06-23 03:35:23 -07:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|