fix: Check if execution will wait on Test step click (no-changelog) (#11311)

# Conflicts:
#	packages/editor-ui/src/stores/workflows.store.ts
This commit is contained in:
Michael Kret 2024-10-23 15:53:01 +03:00 committed by Csaba Tuncsik
parent f52a771735
commit 1ae9b29758
No known key found for this signature in database
9 changed files with 140 additions and 29 deletions

View file

@ -157,8 +157,9 @@ const getTriggerNodeTooltip = computed(() => {
}); });
const isPollingTypeNode = computed(() => !!nodeType.value?.polling); const isPollingTypeNode = computed(() => !!nodeType.value?.polling);
const isExecuting = computed(() => { const isExecuting = computed(() => {
if (!node.value) return false; if (!node.value || !workflowRunning.value) return false;
return workflowsStore.isNodeExecuting(node.value.name); return workflowsStore.isNodeExecuting(node.value.name);
}); });

View file

@ -56,7 +56,7 @@ defineOptions({
const lastPopupCountUpdate = ref(0); const lastPopupCountUpdate = ref(0);
const router = useRouter(); const router = useRouter();
const { runWorkflowResolvePending, stopCurrentExecution } = useRunWorkflow({ router }); const { runWorkflow, runWorkflowResolvePending, stopCurrentExecution } = useRunWorkflow({ router });
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const externalHooks = useExternalHooks(); const externalHooks = useExternalHooks();
@ -76,10 +76,10 @@ const nodeType = computed((): INodeTypeDescription | null => {
}); });
const isNodeRunning = computed(() => { const isNodeRunning = computed(() => {
if (!uiStore.isActionActive['workflowRunning']) return false;
const triggeredNode = workflowsStore.executedNode; const triggeredNode = workflowsStore.executedNode;
return ( 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); telemetry.track('User clicked execute node button', telemetryPayload);
await externalHooks.run('nodeExecuteButton.onClick', telemetryPayload); await externalHooks.run('nodeExecuteButton.onClick', telemetryPayload);
await runWorkflowResolvePending({ if (workflowsStore.isWaitingExecution) {
destinationNode: props.nodeName, await runWorkflowResolvePending({
source: 'RunData.ExecuteNodeButton', destinationNode: props.nodeName,
}); source: 'RunData.ExecuteNodeButton',
});
} else {
await runWorkflow({
destinationNode: props.nodeName,
source: 'RunData.ExecuteNodeButton',
});
}
emit('execute'); emit('execute');
} }
} }

View file

@ -123,7 +123,7 @@ const defaultOutputMode = computed<OutputType>(() => {
}); });
const isNodeRunning = 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(() => { const workflowRunning = computed(() => {

View file

@ -378,6 +378,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
['error', 'canceled', 'crashed', 'success'].includes(execution.status) ['error', 'canceled', 'crashed', 'success'].includes(execution.status)
) { ) {
workflowsStore.setWorkflowExecutionData(execution); workflowsStore.setWorkflowExecutionData(execution);
uiStore.removeActiveAction('workflowRunning');
workflowsStore.activeExecutionId = null; workflowsStore.activeExecutionId = null;
if (timeoutId) clearTimeout(timeoutId); if (timeoutId) clearTimeout(timeoutId);
resolve(); resolve();

View file

@ -195,6 +195,7 @@ export const CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE =
export const SIMULATE_NODE_TYPE = 'n8n-nodes-base.simulate'; export const SIMULATE_NODE_TYPE = 'n8n-nodes-base.simulate';
export const SIMULATE_TRIGGER_NODE_TYPE = 'n8n-nodes-base.simulateTrigger'; export const SIMULATE_TRIGGER_NODE_TYPE = 'n8n-nodes-base.simulateTrigger';
export const AI_TRANSFORM_NODE_TYPE = 'n8n-nodes-base.aiTransform'; export const AI_TRANSFORM_NODE_TYPE = 'n8n-nodes-base.aiTransform';
export const FORM_NODE_TYPE = 'n8n-nodes-base.form';
export const CREDENTIAL_ONLY_NODE_PREFIX = 'n8n-creds-base'; export const CREDENTIAL_ONLY_NODE_PREFIX = 'n8n-creds-base';
export const CREDENTIAL_ONLY_HTTP_NODE_VERSION = 4.1; export const CREDENTIAL_ONLY_HTTP_NODE_VERSION = 4.1;

View file

@ -2,18 +2,33 @@ import { setActivePinia, createPinia } from 'pinia';
import * as workflowsApi from '@/api/workflows'; import * as workflowsApi from '@/api/workflows';
import { import {
DUPLICATE_POSTFFIX, DUPLICATE_POSTFFIX,
FORM_NODE_TYPE,
MAX_WORKFLOW_NAME_LENGTH, MAX_WORKFLOW_NAME_LENGTH,
PLACEHOLDER_EMPTY_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID,
WAIT_NODE_TYPE,
} from '@/constants'; } from '@/constants';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import type { IExecutionResponse, INodeUi, IWorkflowDb, IWorkflowSettings } from '@/Interface'; import type { IExecutionResponse, INodeUi, IWorkflowDb, IWorkflowSettings } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { type ExecutionSummary, type IConnection, type INodeExecutionData } from 'n8n-workflow';
import {
SEND_AND_WAIT_OPERATION,
type ExecutionSummary,
type IConnection,
type INodeExecutionData,
} from 'n8n-workflow';
import { stringSizeInBytes } from '@/utils/typesUtils'; import { stringSizeInBytes } from '@/utils/typesUtils';
import { dataPinningEventBus } from '@/event-bus'; import { dataPinningEventBus } from '@/event-bus';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import type { PushPayload } from '@n8n/api-types'; import type { PushPayload } from '@n8n/api-types';
import { flushPromises } from '@vue/test-utils'; import { flushPromises } from '@vue/test-utils';
import { useNDVStore } from '@/stores/ndv.store';
vi.mock('@/stores/ndv.store', () => ({
useNDVStore: vi.fn(() => ({
activeNode: null,
})),
}));
vi.mock('@/api/workflows', () => ({ vi.mock('@/api/workflows', () => ({
getWorkflows: vi.fn(), getWorkflows: vi.fn(),
@ -49,6 +64,67 @@ describe('useWorkflowsStore', () => {
expect(workflowsStore.workflow.id).toBe(PLACEHOLDER_EMPTY_WORKFLOW_ID); 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<typeof useNDVStore>);
const isWaiting = workflowsStore.isWaitingExecution;
expect(isWaiting).toEqual(true);
});
});
describe('allWorkflows', () => { describe('allWorkflows', () => {
it('should return sorted workflows by name', () => { it('should return sorted workflows by name', () => {
workflowsStore.setWorkflows([ workflowsStore.setWorkflows([

View file

@ -3,6 +3,7 @@ import {
DEFAULT_NEW_WORKFLOW_NAME, DEFAULT_NEW_WORKFLOW_NAME,
DUPLICATE_POSTFFIX, DUPLICATE_POSTFFIX,
ERROR_TRIGGER_NODE_TYPE, ERROR_TRIGGER_NODE_TYPE,
FORM_NODE_TYPE,
MAX_WORKFLOW_NAME_LENGTH, MAX_WORKFLOW_NAME_LENGTH,
PLACEHOLDER_EMPTY_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID,
START_NODE_TYPE, START_NODE_TYPE,
@ -182,12 +183,46 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
const allNodes = computed<INodeUi[]>(() => workflow.value.nodes); const allNodes = computed<INodeUi[]>(() => workflow.value.nodes);
const isWaitingExecution = computed(() => { const willNodeWait = (node: INodeUi): boolean => {
return allNodes.value.some( return (
(node) => (node.type === WAIT_NODE_TYPE ||
(node.type === WAIT_NODE_TYPE || node.parameters.operation === SEND_AND_WAIT_OPERATION) && node.type === FORM_NODE_TYPE ||
node.disabled !== true, 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. // Names of all nodes currently on canvas.
@ -1614,6 +1649,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
allConnections, allConnections,
allNodes, allNodes,
isWaitingExecution, isWaitingExecution,
isWorkflowRunning,
canvasNames, canvasNames,
nodesByName, nodesByName,
nodesIssuesExist, nodesIssuesExist,

View file

@ -951,14 +951,7 @@ const projectPermissions = computed(() => {
const isStoppingExecution = ref(false); const isStoppingExecution = ref(false);
const isWorkflowRunning = computed(() => { const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning);
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 isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook); const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
const isExecutionDisabled = computed(() => { const isExecutionDisabled = computed(() => {

View file

@ -426,12 +426,7 @@ export default defineComponent({
return this.workflowsStore.getWorkflowExecution; return this.workflowsStore.getWorkflowExecution;
}, },
workflowRunning(): boolean { workflowRunning(): boolean {
if (this.uiStore.isActionActive.workflowRunning) return true; return this.workflowsStore.isWorkflowRunning;
if (this.workflowsStore.activeExecutionId) {
const execution = this.workflowsStore.getWorkflowExecution;
if (execution && execution.status === 'waiting' && !execution.finished) return true;
}
return false;
}, },
currentWorkflow(): string { currentWorkflow(): string {
return this.$route.params.name?.toString() || this.workflowsStore.workflowId; return this.$route.params.name?.toString() || this.workflowsStore.workflowId;