diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index fa9e0cb069..5e2cc7e8ca 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -158,8 +158,9 @@ const getTriggerNodeTooltip = computed(() => { }); const isPollingTypeNode = computed(() => !!nodeType.value?.polling); + const isExecuting = computed(() => { - if (!node.value) return false; + if (!node.value || !workflowRunning.value) return false; return workflowsStore.isNodeExecuting(node.value.name); }); diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue index 0efaa15cd1..bfd6e0e38d 100644 --- a/packages/editor-ui/src/components/NodeExecuteButton.vue +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -56,7 +56,7 @@ defineOptions({ const lastPopupCountUpdate = ref(0); const router = useRouter(); -const { runWorkflowResolvePending, stopCurrentExecution } = useRunWorkflow({ router }); +const { runWorkflow, runWorkflowResolvePending, stopCurrentExecution } = useRunWorkflow({ router }); const workflowsStore = useWorkflowsStore(); const externalHooks = useExternalHooks(); @@ -76,10 +76,10 @@ const nodeType = computed((): INodeTypeDescription | null => { }); const isNodeRunning = computed(() => { + if (!uiStore.isActionActive['workflowRunning']) return false; const triggeredNode = workflowsStore.executedNode; return ( - uiStore.isActionActive.workflowRunning && - (workflowsStore.isNodeExecuting(node.value?.name ?? '') || triggeredNode === node.value?.name) + workflowsStore.isNodeExecuting(node.value?.name ?? '') || triggeredNode === node.value?.name ); }); @@ -264,10 +264,18 @@ async function onClick() { telemetry.track('User clicked execute node button', telemetryPayload); await externalHooks.run('nodeExecuteButton.onClick', telemetryPayload); - await runWorkflowResolvePending({ - destinationNode: props.nodeName, - source: 'RunData.ExecuteNodeButton', - }); + if (workflowsStore.isWaitingExecution) { + await runWorkflowResolvePending({ + destinationNode: props.nodeName, + source: 'RunData.ExecuteNodeButton', + }); + } else { + await runWorkflow({ + destinationNode: props.nodeName, + source: 'RunData.ExecuteNodeButton', + }); + } + emit('execute'); } } diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index f4e1681e17..bf3cbbe6eb 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -123,7 +123,7 @@ const defaultOutputMode = computed(() => { }); const isNodeRunning = computed(() => { - return !!node.value && workflowsStore.isNodeExecuting(node.value.name); + return workflowRunning.value && !!node.value && workflowsStore.isNodeExecuting(node.value.name); }); const workflowRunning = computed(() => { diff --git a/packages/editor-ui/src/composables/useRunWorkflow.ts b/packages/editor-ui/src/composables/useRunWorkflow.ts index 32d2b86553..8369921f8a 100644 --- a/packages/editor-ui/src/composables/useRunWorkflow.ts +++ b/packages/editor-ui/src/composables/useRunWorkflow.ts @@ -387,6 +387,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType ({ + useNDVStore: vi.fn(() => ({ + activeNode: null, + })), +})); vi.mock('@/api/workflows', () => ({ getWorkflows: vi.fn(), @@ -49,6 +64,67 @@ describe('useWorkflowsStore', () => { expect(workflowsStore.workflow.id).toBe(PLACEHOLDER_EMPTY_WORKFLOW_ID); }); + describe('isWaitingExecution', () => { + it('should return false if no activeNode and no waiting nodes in workflow', () => { + workflowsStore.workflow.nodes = [ + { type: 'type1' }, + { type: 'type2' }, + ] as unknown as IWorkflowDb['nodes']; + + const isWaiting = workflowsStore.isWaitingExecution; + expect(isWaiting).toEqual(false); + }); + + it('should return false if no activeNode and waiting node in workflow and waiting node is disabled', () => { + workflowsStore.workflow.nodes = [ + { type: FORM_NODE_TYPE, disabled: true }, + { type: 'type2' }, + ] as unknown as IWorkflowDb['nodes']; + + const isWaiting = workflowsStore.isWaitingExecution; + expect(isWaiting).toEqual(false); + }); + + it('should return true if no activeNode and wait node in workflow', () => { + workflowsStore.workflow.nodes = [ + { type: WAIT_NODE_TYPE }, + { type: 'type2' }, + ] as unknown as IWorkflowDb['nodes']; + + const isWaiting = workflowsStore.isWaitingExecution; + expect(isWaiting).toEqual(true); + }); + + it('should return true if no activeNode and form node in workflow', () => { + workflowsStore.workflow.nodes = [ + { type: FORM_NODE_TYPE }, + { type: 'type2' }, + ] as unknown as IWorkflowDb['nodes']; + + const isWaiting = workflowsStore.isWaitingExecution; + expect(isWaiting).toEqual(true); + }); + + it('should return true if no activeNode and sendAndWait node in workflow', () => { + workflowsStore.workflow.nodes = [ + { type: 'type1', parameters: { operation: SEND_AND_WAIT_OPERATION } }, + { type: 'type2' }, + ] as unknown as IWorkflowDb['nodes']; + + const isWaiting = workflowsStore.isWaitingExecution; + expect(isWaiting).toEqual(true); + }); + + it('should return true if activeNode is waiting node', () => { + vi.mocked(useNDVStore).mockReturnValue({ + activeNode: { type: WAIT_NODE_TYPE } as unknown as INodeUi, + } as unknown as ReturnType); + + const isWaiting = workflowsStore.isWaitingExecution; + expect(isWaiting).toEqual(true); + }); + }); + describe('allWorkflows', () => { it('should return sorted workflows by name', () => { workflowsStore.setWorkflows([ diff --git a/packages/editor-ui/src/stores/workflows.store.ts b/packages/editor-ui/src/stores/workflows.store.ts index 3a8778d2cc..b93b00106a 100644 --- a/packages/editor-ui/src/stores/workflows.store.ts +++ b/packages/editor-ui/src/stores/workflows.store.ts @@ -183,14 +183,46 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { const allNodes = computed(() => workflow.value.nodes); - const isWaitingExecution = computed(() => { - return allNodes.value.some( - (node) => - (node.type === WAIT_NODE_TYPE || - node.type === FORM_NODE_TYPE || - node.parameters.operation === SEND_AND_WAIT_OPERATION) && - node.disabled !== true, + const willNodeWait = (node: INodeUi): boolean => { + return ( + (node.type === WAIT_NODE_TYPE || + node.type === FORM_NODE_TYPE || + node.parameters?.operation === SEND_AND_WAIT_OPERATION) && + node.disabled !== true ); + }; + + const isWaitingExecution = computed(() => { + const activeNode = useNDVStore().activeNode; + + if (activeNode) { + if (willNodeWait(activeNode)) return true; + + const workflow = getCurrentWorkflow(); + const parentNodes = workflow.getParentNodes(activeNode.name); + + for (const parentNode of parentNodes) { + if (willNodeWait(workflow.nodes[parentNode])) { + return true; + } + } + + return false; + } + return allNodes.value.some((node) => willNodeWait(node)); + }); + + const isWorkflowRunning = computed(() => { + if (uiStore.isActionActive.workflowRunning) return true; + + if (activeExecutionId.value) { + const execution = getWorkflowExecution; + if (execution.value && execution.value.status === 'waiting' && !execution.value.finished) { + return true; + } + } + + return false; }); // Names of all nodes currently on canvas. @@ -1617,6 +1649,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { allConnections, allNodes, isWaitingExecution, + isWorkflowRunning, canvasNames, nodesByName, nodesIssuesExist, diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue index fc29ae2f7a..60f4f8983b 100644 --- a/packages/editor-ui/src/views/NodeView.v2.vue +++ b/packages/editor-ui/src/views/NodeView.v2.vue @@ -961,14 +961,7 @@ const projectPermissions = computed(() => { const isStoppingExecution = ref(false); -const isWorkflowRunning = computed(() => { - if (uiStore.isActionActive.workflowRunning) return true; - if (workflowsStore.activeExecutionId) { - const execution = workflowsStore.getWorkflowExecution; - if (execution && execution.status === 'waiting' && !execution.finished) return true; - } - return false; -}); +const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning); const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook); const isExecutionDisabled = computed(() => { diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 9428e0a056..3cb0acc227 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -426,12 +426,7 @@ export default defineComponent({ return this.workflowsStore.getWorkflowExecution; }, workflowRunning(): boolean { - if (this.uiStore.isActionActive.workflowRunning) return true; - if (this.workflowsStore.activeExecutionId) { - const execution = this.workflowsStore.getWorkflowExecution; - if (execution && execution.status === 'waiting' && !execution.finished) return true; - } - return false; + return this.workflowsStore.isWorkflowRunning; }, currentWorkflow(): string { return this.$route.params.name?.toString() || this.workflowsStore.workflowId;