mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 00:24:07 -08:00
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:
parent
f52a771735
commit
1ae9b29758
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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([
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue