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 isExecuting = computed(() => {
if (!node.value) return false;
if (!node.value || !workflowRunning.value) return false;
return workflowsStore.isNodeExecuting(node.value.name);
});

View file

@ -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);
if (workflowsStore.isWaitingExecution) {
await runWorkflowResolvePending({
destinationNode: props.nodeName,
source: 'RunData.ExecuteNodeButton',
});
} else {
await runWorkflow({
destinationNode: props.nodeName,
source: 'RunData.ExecuteNodeButton',
});
}
emit('execute');
}
}

View file

@ -123,7 +123,7 @@ const defaultOutputMode = computed<OutputType>(() => {
});
const isNodeRunning = computed(() => {
return !!node.value && workflowsStore.isNodeExecuting(node.value.name);
return workflowRunning.value && !!node.value && workflowsStore.isNodeExecuting(node.value.name);
});
const workflowRunning = computed(() => {

View file

@ -378,6 +378,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
['error', 'canceled', 'crashed', 'success'].includes(execution.status)
) {
workflowsStore.setWorkflowExecutionData(execution);
uiStore.removeActiveAction('workflowRunning');
workflowsStore.activeExecutionId = null;
if (timeoutId) clearTimeout(timeoutId);
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_TRIGGER_NODE_TYPE = 'n8n-nodes-base.simulateTrigger';
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_HTTP_NODE_VERSION = 4.1;

View file

@ -2,18 +2,33 @@ import { setActivePinia, createPinia } from 'pinia';
import * as workflowsApi from '@/api/workflows';
import {
DUPLICATE_POSTFFIX,
FORM_NODE_TYPE,
MAX_WORKFLOW_NAME_LENGTH,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
WAIT_NODE_TYPE,
} from '@/constants';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { IExecutionResponse, INodeUi, IWorkflowDb, IWorkflowSettings } from '@/Interface';
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 { dataPinningEventBus } from '@/event-bus';
import { useUIStore } from '@/stores/ui.store';
import type { PushPayload } from '@n8n/api-types';
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', () => ({
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<typeof useNDVStore>);
const isWaiting = workflowsStore.isWaitingExecution;
expect(isWaiting).toEqual(true);
});
});
describe('allWorkflows', () => {
it('should return sorted workflows by name', () => {
workflowsStore.setWorkflows([

View file

@ -3,6 +3,7 @@ import {
DEFAULT_NEW_WORKFLOW_NAME,
DUPLICATE_POSTFFIX,
ERROR_TRIGGER_NODE_TYPE,
FORM_NODE_TYPE,
MAX_WORKFLOW_NAME_LENGTH,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
START_NODE_TYPE,
@ -182,12 +183,46 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
const allNodes = computed<INodeUi[]>(() => workflow.value.nodes);
const isWaitingExecution = computed(() => {
return allNodes.value.some(
(node) =>
(node.type === WAIT_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.
@ -1614,6 +1649,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
allConnections,
allNodes,
isWaitingExecution,
isWorkflowRunning,
canvasNames,
nodesByName,
nodesIssuesExist,

View file

@ -951,14 +951,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(() => {

View file

@ -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;