mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-30 08:17:06 +02:00
refactor(editor): Move session state off workflows-store bridge (no-changelog) (#31138)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e104c7f299
commit
1eee149ab8
|
|
@ -21,6 +21,7 @@ const { mockWorkflowDocumentStore } = vi.hoisted(() => ({
|
|||
allNodes: [] as Array<{ id: string; name: string; type: string }>,
|
||||
workflowTriggerNodes: [] as Array<{ id: string; name: string; type: string }>,
|
||||
name: '',
|
||||
documentId: 'test-id',
|
||||
workflowId: 'test-workflow',
|
||||
settings: {},
|
||||
getPinDataSnapshot: () => ({}),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '@/app/constants';
|
||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
injectWorkflowDocumentStore,
|
||||
useWorkflowDocumentStore,
|
||||
|
|
@ -223,7 +224,11 @@ describe('NodeExecuteButton', () => {
|
|||
it('displays "Stop Listening" when node is listening for events', () => {
|
||||
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
|
||||
vi.spyOn(workflowDocumentStore, 'getNodeByName').mockReturnValue(node);
|
||||
workflowsStore.executionWaitingForWebhook = true;
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('abc123')),
|
||||
'executionWaitingForWebhook',
|
||||
'get',
|
||||
).mockReturnValue(true);
|
||||
nodeTypesStore.isTriggerNode = () => true;
|
||||
|
||||
const { getByRole } = renderComponent();
|
||||
|
|
@ -235,7 +240,11 @@ describe('NodeExecuteButton', () => {
|
|||
vi.spyOn(workflowDocumentStore, 'getNodeByName').mockReturnValue(node);
|
||||
workflowState.executingNode.isNodeExecuting = vi.fn().mockReturnValue(true);
|
||||
nodeTypesStore.isTriggerNode = () => true;
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('abc123')),
|
||||
'isWorkflowRunning',
|
||||
'get',
|
||||
).mockReturnValue(true);
|
||||
|
||||
const { getByRole } = renderComponent();
|
||||
expect(getByRole('button').textContent).toBe('Stop Listening');
|
||||
|
|
@ -245,7 +254,11 @@ describe('NodeExecuteButton', () => {
|
|||
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
|
||||
vi.spyOn(workflowDocumentStore, 'getNodeByName').mockReturnValue(node);
|
||||
workflowState.executingNode.isNodeExecuting = vi.fn().mockReturnValue(true);
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('abc123')),
|
||||
'isWorkflowRunning',
|
||||
'get',
|
||||
).mockReturnValue(true);
|
||||
|
||||
const { getByRole } = renderComponent();
|
||||
expect(getByRole('button')).toHaveAttribute('aria-busy', 'true');
|
||||
|
|
@ -270,7 +283,11 @@ describe('NodeExecuteButton', () => {
|
|||
});
|
||||
|
||||
it('should be disabled when workflow is running but node is not executing', async () => {
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('abc123')),
|
||||
'isWorkflowRunning',
|
||||
'get',
|
||||
).mockReturnValue(true);
|
||||
workflowState.executingNode.isNodeExecuting = vi.fn().mockReturnValue(false);
|
||||
vi.spyOn(workflowDocumentStore, 'getNodeByName').mockReturnValue(
|
||||
mockNode({ name: 'test-node', type: SET_NODE_TYPE }),
|
||||
|
|
@ -307,7 +324,11 @@ describe('NodeExecuteButton', () => {
|
|||
});
|
||||
|
||||
it('stops webhook when clicking button while listening for events', async () => {
|
||||
workflowsStore.executionWaitingForWebhook = true;
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('abc123')),
|
||||
'executionWaitingForWebhook',
|
||||
'get',
|
||||
).mockReturnValue(true);
|
||||
nodeTypesStore.isTriggerNode = () => true;
|
||||
vi.spyOn(workflowDocumentStore, 'getNodeByName').mockReturnValue(
|
||||
mockNode({ name: 'test-node', type: SET_NODE_TYPE }),
|
||||
|
|
@ -321,7 +342,11 @@ describe('NodeExecuteButton', () => {
|
|||
});
|
||||
|
||||
it('stops execution when clicking button while workflow is running', async () => {
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('abc123')),
|
||||
'isWorkflowRunning',
|
||||
'get',
|
||||
).mockReturnValue(true);
|
||||
nodeTypesStore.isTriggerNode = () => true;
|
||||
useWorkflowState().setActiveExecutionId('test-execution-id');
|
||||
workflowState.executingNode.isNodeExecuting = vi.fn().mockReturnValue(true);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import type { ICredentialsResponse } from '@/features/credentials/credentials.ty
|
|||
import type { IWorkflowTemplate, IWorkflowTemplateNode } from '@n8n/rest-api-client/api/templates';
|
||||
import { RemoveNodeCommand, ReplaceNodeParametersCommand } from '@/app/models/history';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useHistoryStore } from '@/app/stores/history.store';
|
||||
import { getNDVStoreId, useNDVStore } from '@/features/ndv/shared/ndv.store';
|
||||
|
|
@ -4051,8 +4052,13 @@ describe('useCanvasOperations', () => {
|
|||
uiStore.resetLastInteractedWith = vi.fn();
|
||||
executionsStore.activeExecution = null;
|
||||
|
||||
workflowsStore.executionWaitingForWebhook = true;
|
||||
workflowsStore.workflowId = 'workflow-id';
|
||||
const executionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('workflow-id'),
|
||||
);
|
||||
// Spy on the getter — readonly wrapping prevents direct assignment, and
|
||||
// createTestingPinia stubs setExecutionWaitingForWebhook so the action is a no-op.
|
||||
vi.spyOn(executionStateStore, 'executionWaitingForWebhook', 'get').mockReturnValue(true);
|
||||
workflowsStore.lastSuccessfulExecution = {} as IExecutionResponse;
|
||||
workflowsStore.currentWorkflowExecutions = [
|
||||
{
|
||||
|
|
@ -4104,8 +4110,8 @@ describe('useCanvasOperations', () => {
|
|||
|
||||
nodeCreatorStore.setNodeCreatorState = vi.fn();
|
||||
workflowsStore.removeTestWebhook = vi.fn();
|
||||
|
||||
workflowsStore.executionWaitingForWebhook = false;
|
||||
workflowsStore.workflowId = 'workflow-id';
|
||||
// Default state-store value is false; no override needed.
|
||||
|
||||
const { resetWorkspace } = useCanvasOperations();
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import { useSettingsStore } from '@/app/stores/settings.store';
|
|||
import { useTagsStore } from '@/features/shared/tags/tags.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import type {
|
||||
CanvasConnection,
|
||||
CanvasConnectionCreateData,
|
||||
|
|
@ -139,6 +140,7 @@ import { useSetupPanelStore } from '@/features/setupPanel/setupPanel.store';
|
|||
import { clearAllNodeResourceLocatorValues } from '@/features/workflows/templates/utils/templateTransforms';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import {
|
||||
createWorkflowDocumentId,
|
||||
pinDataToExecutionData,
|
||||
injectWorkflowDocumentStore,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
|
|
@ -2311,7 +2313,10 @@ export function useCanvasOperations() {
|
|||
});
|
||||
|
||||
// Make sure that if there is a waiting test-webhook, it gets removed
|
||||
if (workflowsStore.executionWaitingForWebhook) {
|
||||
const executionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
if (executionStateStore.executionWaitingForWebhook) {
|
||||
try {
|
||||
void workflowsStore.removeTestWebhook(workflowsStore.workflowId);
|
||||
} catch (error) {}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
|
||||
const {
|
||||
mockWorkflowsStore,
|
||||
mockWorkflowExecutionStateStore,
|
||||
mockNodeTypesStore,
|
||||
mockNdvStore,
|
||||
mockRunWorkflow,
|
||||
|
|
@ -39,14 +40,16 @@ const {
|
|||
mockNodeHelpers,
|
||||
} = vi.hoisted(() => ({
|
||||
mockWorkflowsStore: {
|
||||
isWorkflowRunning: false,
|
||||
executedNode: undefined as string | undefined,
|
||||
executionWaitingForWebhook: false,
|
||||
workflowId: '123',
|
||||
chatPartialExecutionDestinationNode: undefined as string | undefined,
|
||||
getNodeByName: vi.fn(),
|
||||
removeTestWebhook: vi.fn(),
|
||||
},
|
||||
mockWorkflowExecutionStateStore: {
|
||||
isWorkflowRunning: false,
|
||||
executionWaitingForWebhook: false,
|
||||
chatPartialExecutionDestinationNode: undefined as string | undefined,
|
||||
},
|
||||
mockNodeTypesStore: {
|
||||
getNodeType: vi.fn(),
|
||||
isTriggerNode: vi.fn(),
|
||||
|
|
@ -99,6 +102,10 @@ vi.mock('@/app/stores/workflows.store', () => ({
|
|||
useWorkflowsStore: vi.fn().mockReturnValue(mockWorkflowsStore),
|
||||
}));
|
||||
|
||||
vi.mock('@/app/stores/workflowExecutionState.store', () => ({
|
||||
useWorkflowExecutionStateStore: vi.fn().mockReturnValue(mockWorkflowExecutionStateStore),
|
||||
}));
|
||||
|
||||
vi.mock('@/app/stores/nodeTypes.store', () => ({
|
||||
useNodeTypesStore: vi.fn().mockReturnValue(mockNodeTypesStore),
|
||||
}));
|
||||
|
|
@ -199,10 +206,10 @@ describe('useNodeExecution', () => {
|
|||
uiStore = useUIStore();
|
||||
|
||||
// Reset store properties to defaults
|
||||
mockWorkflowsStore.isWorkflowRunning = false;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = false;
|
||||
mockWorkflowsStore.executedNode = undefined;
|
||||
mockWorkflowsStore.executionWaitingForWebhook = false;
|
||||
mockWorkflowsStore.chatPartialExecutionDestinationNode = undefined;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = false;
|
||||
mockWorkflowExecutionStateStore.chatPartialExecutionDestinationNode = undefined;
|
||||
mockWorkflowDocumentStore.checkIfNodeHasChatParent.mockReturnValue(false);
|
||||
mockWorkflowsStore.removeTestWebhook.mockReset();
|
||||
mockWorkflowsStore.getNodeByName.mockReset();
|
||||
|
|
@ -295,7 +302,7 @@ describe('useNodeExecution', () => {
|
|||
describe('isListening', () => {
|
||||
it('should return true when trigger node is waiting for webhook', () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: false }));
|
||||
|
||||
const { isListening } = useNodeExecution(node);
|
||||
|
|
@ -305,7 +312,7 @@ describe('useNodeExecution', () => {
|
|||
|
||||
it('should return false when node is disabled', () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: true }));
|
||||
|
||||
const { isListening } = useNodeExecution(node);
|
||||
|
|
@ -314,7 +321,7 @@ describe('useNodeExecution', () => {
|
|||
});
|
||||
|
||||
it('should return false when not a trigger node', () => {
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode());
|
||||
|
||||
const { isListening } = useNodeExecution(node);
|
||||
|
|
@ -333,7 +340,7 @@ describe('useNodeExecution', () => {
|
|||
|
||||
it('should return false when executed node is a different node', () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowsStore.executedNode = 'Other Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -350,7 +357,7 @@ describe('useNodeExecution', () => {
|
|||
name: WEBHOOK_NODE_TYPE,
|
||||
group: ['trigger'],
|
||||
} as INodeTypeDescription);
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Test Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -365,7 +372,7 @@ describe('useNodeExecution', () => {
|
|||
name: 'n8n-nodes-base.scheduleTrigger',
|
||||
group: ['schedule'],
|
||||
} as INodeTypeDescription);
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Test Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -380,7 +387,7 @@ describe('useNodeExecution', () => {
|
|||
name: MANUAL_TRIGGER_NODE_TYPE,
|
||||
group: ['trigger'],
|
||||
} as INodeTypeDescription);
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Test Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -392,7 +399,7 @@ describe('useNodeExecution', () => {
|
|||
|
||||
describe('isExecuting', () => {
|
||||
it('should return true when node is running and not listening', () => {
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Test Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -413,7 +420,7 @@ describe('useNodeExecution', () => {
|
|||
describe('disabledReason', () => {
|
||||
it('should return empty string when listening', () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: false }));
|
||||
|
||||
const { disabledReason } = useNodeExecution(node);
|
||||
|
|
@ -443,7 +450,7 @@ describe('useNodeExecution', () => {
|
|||
});
|
||||
|
||||
it('should return workflow running message when another node is executing', () => {
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Other Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -464,7 +471,7 @@ describe('useNodeExecution', () => {
|
|||
describe('buttonLabel', () => {
|
||||
it('should return stopListening when isListening', () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: false }));
|
||||
|
||||
const { buttonLabel } = useNodeExecution(node);
|
||||
|
|
@ -558,7 +565,7 @@ describe('useNodeExecution', () => {
|
|||
|
||||
it('should return undefined when listening', () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: false }));
|
||||
|
||||
const { buttonIcon } = useNodeExecution(node);
|
||||
|
|
@ -694,7 +701,7 @@ describe('useNodeExecution', () => {
|
|||
|
||||
it('should stop webhook when listening', async () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: false }));
|
||||
|
||||
const { execute } = useNodeExecution(node);
|
||||
|
|
@ -710,7 +717,7 @@ describe('useNodeExecution', () => {
|
|||
name: WEBHOOK_NODE_TYPE,
|
||||
group: ['trigger'],
|
||||
} as INodeTypeDescription);
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Test Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
@ -847,7 +854,7 @@ describe('useNodeExecution', () => {
|
|||
describe('stopExecution', () => {
|
||||
it('should stop webhook when listening', async () => {
|
||||
mockNodeTypesStore.isTriggerNode.mockReturnValue(true);
|
||||
mockWorkflowsStore.executionWaitingForWebhook = true;
|
||||
mockWorkflowExecutionStateStore.executionWaitingForWebhook = true;
|
||||
const node = ref(createTestNode({ disabled: false }));
|
||||
|
||||
const { stopExecution } = useNodeExecution(node);
|
||||
|
|
@ -862,7 +869,7 @@ describe('useNodeExecution', () => {
|
|||
name: WEBHOOK_NODE_TYPE,
|
||||
group: ['trigger'],
|
||||
} as INodeTypeDescription);
|
||||
mockWorkflowsStore.isWorkflowRunning = true;
|
||||
mockWorkflowExecutionStateStore.isWorkflowRunning = true;
|
||||
mockWorkflowsStore.executedNode = 'Test Node';
|
||||
const node = ref(createTestNode({ name: 'Test Node' }));
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
||||
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { injectNDVStore } from '@/features/ndv/shared/ndv.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
|
|
@ -101,6 +102,9 @@ export function useNodeExecution(
|
|||
const workflowState = injectWorkflowState();
|
||||
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
|
||||
const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
|
|
@ -140,7 +144,8 @@ export function useNodeExecution(
|
|||
const isWebhookNode = computed(() => nodeType.value?.name === WEBHOOK_NODE_TYPE);
|
||||
|
||||
const isNodeRunning = computed(() => {
|
||||
if (!workflowsStore.isWorkflowRunning || codeGenerationInProgress.value) return false;
|
||||
if (!workflowExecutionStateStore.value.isWorkflowRunning || codeGenerationInProgress.value)
|
||||
return false;
|
||||
const triggeredNode = workflowsStore.executedNode;
|
||||
return (
|
||||
workflowState.executingNode.isNodeExecuting(nodeRef.value?.name ?? '') ||
|
||||
|
|
@ -149,7 +154,7 @@ export function useNodeExecution(
|
|||
});
|
||||
|
||||
const isListening = computed(() => {
|
||||
const waitingOnWebhook = workflowsStore.executionWaitingForWebhook;
|
||||
const waitingOnWebhook = workflowExecutionStateStore.value.executionWaitingForWebhook;
|
||||
const executedNode = workflowsStore.executedNode;
|
||||
|
||||
return (
|
||||
|
|
@ -199,7 +204,7 @@ export function useNodeExecution(
|
|||
return i18n.baseText('ndv.execute.requiredFieldsMissing');
|
||||
}
|
||||
|
||||
if (workflowsStore.isWorkflowRunning && !isNodeRunning.value) {
|
||||
if (workflowExecutionStateStore.value.isWorkflowRunning && !isNodeRunning.value) {
|
||||
return i18n.baseText('ndv.execute.workflowAlreadyRunning');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
|||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
|
|
@ -281,7 +282,12 @@ describe('executionFinished', () => {
|
|||
const workflowsListStore = useWorkflowsListStore();
|
||||
const readyToRunStore = useReadyToRunStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'activeExecutionId', 'get').mockReturnValue('123');
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
|
|
@ -325,7 +331,12 @@ describe('executionFinished', () => {
|
|||
const workflowsListStore = useWorkflowsListStore();
|
||||
const readyToRunStore = useReadyToRunStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'activeExecutionId', 'get').mockReturnValue('123');
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
|
|
@ -366,7 +377,12 @@ describe('executionFinished', () => {
|
|||
const workflowsListStore = useWorkflowsListStore();
|
||||
const readyToRunStore = useReadyToRunStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'activeExecutionId', 'get').mockReturnValue('123');
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
|
|
@ -410,7 +426,12 @@ describe('executionFinished', () => {
|
|||
const workflowsListStore = useWorkflowsListStore();
|
||||
const readyToRunStore = useReadyToRunStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'activeExecutionId', 'get').mockReturnValue('123');
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
|
|
@ -451,7 +472,12 @@ describe('executionFinished', () => {
|
|||
const workflowsListStore = useWorkflowsListStore();
|
||||
const readyToRunStore = useReadyToRunStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'activeExecutionId', 'get').mockReturnValue('123');
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
|
|
@ -505,8 +531,13 @@ describe('executionFinished', () => {
|
|||
const workflowsListStore = mockedStore(useWorkflowsListStore);
|
||||
const uiStore = mockedStore(useUIStore);
|
||||
|
||||
// Set activeExecutionId directly on the store
|
||||
workflowsStore.activeExecutionId = '123';
|
||||
// Set workflowId + activeExecutionId via the state store
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
|
||||
// Mock getWorkflowById to return a workflow
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
|
|
@ -567,7 +598,12 @@ describe('executionFinished', () => {
|
|||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const workflowsListStore = mockedStore(useWorkflowsListStore);
|
||||
|
||||
workflowsStore.activeExecutionId = '123';
|
||||
workflowsStore.workflowId = '1';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')),
|
||||
'activeExecutionId',
|
||||
'get',
|
||||
).mockReturnValue('123');
|
||||
|
||||
vi.spyOn(workflowsListStore, 'getWorkflowById').mockReturnValue({
|
||||
id: '1',
|
||||
|
|
@ -615,8 +651,8 @@ describe('executionFinished', () => {
|
|||
setActivePinia(pinia);
|
||||
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
// In iframe preview after resetWorkspace, activeExecutionId can be undefined
|
||||
workflowsStore.activeExecutionId = undefined;
|
||||
workflowsStore.workflowId = '1';
|
||||
// In iframe preview after resetWorkspace, activeExecutionId is undefined by default.
|
||||
|
||||
const clearNodeExecutionQueue = vi.fn();
|
||||
const workflowState = mock<WorkflowState>({
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ import {
|
|||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import { useWorkflowsListStore } from '@/app/stores/workflowsList.store';
|
||||
import { useBuilderStore } from '@/features/ai/assistant/builder.store';
|
||||
|
|
@ -79,8 +76,12 @@ export async function executionFinished(
|
|||
options.workflowState.executingNode.lastAddedExecutingNode = null;
|
||||
options.workflowState.executingNode.clearNodeExecutionQueue();
|
||||
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
|
||||
// No workflow is actively running, therefore we ignore this event
|
||||
if (typeof workflowsStore.activeExecutionId === 'undefined') {
|
||||
if (typeof workflowExecutionStateStore.activeExecutionId === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -259,9 +260,12 @@ export function getRunDataExecutedErrorMessage(execution: SimplifiedExecution) {
|
|||
return i18n.baseText('pushConnection.executionFailed.message');
|
||||
} else if (execution.status === 'canceled') {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
|
||||
return i18n.baseText('executionsList.showMessage.stopExecution.message', {
|
||||
interpolate: { activeExecutionId: workflowsStore.activeExecutionId ?? '' },
|
||||
interpolate: { activeExecutionId: workflowExecutionStateStore.activeExecutionId ?? '' },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -480,8 +484,8 @@ export function setRunExecutionData(
|
|||
workflowState: WorkflowState,
|
||||
) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowsStore.workflowId),
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const runDataExecutedErrorMessage = getRunDataExecutedErrorMessage(execution);
|
||||
|
|
@ -502,7 +506,7 @@ export function setRunExecutionData(
|
|||
stoppedAt: execution.stoppedAt,
|
||||
});
|
||||
executionDataStore.setExecutionRunData(runExecutionData);
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
|
||||
// Set the node execution issues on all the nodes which produced an error so that
|
||||
// it can be displayed in the node-view
|
||||
|
|
@ -519,7 +523,7 @@ export function setRunExecutionData(
|
|||
runExecutionData.resultData.runData[lastNodeExecuted][0].data?.main[0]?.length ?? 0;
|
||||
}
|
||||
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
|
||||
void useExternalHooks().run('pushConnection.executionFinished', {
|
||||
itemsCount,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@ import {
|
|||
setRunExecutionData,
|
||||
} from './executionFinished';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import type { useRouter } from 'vue-router';
|
||||
import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
|
||||
|
|
@ -21,13 +19,13 @@ export async function executionRecovered(
|
|||
options: { router: ReturnType<typeof useRouter>; workflowState: WorkflowState },
|
||||
) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowsStore.workflowId),
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
const uiStore = useUIStore();
|
||||
|
||||
// No workflow is actively running, therefore we ignore this event
|
||||
if (typeof stateStore.activeExecutionId === 'undefined') {
|
||||
if (typeof workflowExecutionStateStore.activeExecutionId === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,12 @@ import {
|
|||
useWorkflowDocumentStore,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import type { ExecutionStarted } from '@n8n/api-types/push/execution';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
|
||||
describe('executionStarted', () => {
|
||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||
let stateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let workflowExecutionStateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
|
||||
function makeEvent(executionId = 'exec-1'): ExecutionStarted {
|
||||
return {
|
||||
|
|
@ -32,15 +29,17 @@ describe('executionStarted', () => {
|
|||
const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId('wf-123'));
|
||||
workflowDocumentStore.setName('My Workflow');
|
||||
|
||||
stateStore = useWorkflowExecutionStateStore(createWorkflowExecutionStateId('wf-123'));
|
||||
workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('wf-123'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip when activeExecutionId is undefined', async () => {
|
||||
// activeExecutionId defaults to undefined, no need to set it
|
||||
await executionStarted(makeEvent());
|
||||
|
||||
// stateStore.activeExecutionId should remain undefined
|
||||
expect(stateStore.activeExecutionId).toBeUndefined();
|
||||
// workflowExecutionStateStore.activeExecutionId should remain undefined
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBeUndefined();
|
||||
|
||||
// No execution data store should have been created for exec-1
|
||||
const executionDataStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
|
|
@ -48,11 +47,11 @@ describe('executionStarted', () => {
|
|||
});
|
||||
|
||||
it('should accept execution when activeExecutionId is null and populate workflowData from store', async () => {
|
||||
stateStore.setActiveExecutionId(null);
|
||||
workflowExecutionStateStore.setActiveExecutionId(null);
|
||||
|
||||
await executionStarted(makeEvent('exec-1'));
|
||||
|
||||
expect(stateStore.activeExecutionId).toBe('exec-1');
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBe('exec-1');
|
||||
|
||||
const executionDataStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
expect(executionDataStore.execution).toMatchObject({
|
||||
|
|
@ -64,7 +63,7 @@ describe('executionStarted', () => {
|
|||
|
||||
it('should not reinitialize when same execution ID arrives', async () => {
|
||||
// Set up an active execution with existing data
|
||||
stateStore.promotePendingExecution('exec-1');
|
||||
workflowExecutionStateStore.promotePendingExecution('exec-1');
|
||||
const executionDataStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
executionDataStore.setExecution({
|
||||
id: 'exec-1',
|
||||
|
|
@ -81,8 +80,8 @@ describe('executionStarted', () => {
|
|||
|
||||
await executionStarted(makeEvent('exec-1'));
|
||||
|
||||
// stateStore.activeExecutionId should remain 'exec-1' without change
|
||||
expect(stateStore.activeExecutionId).toBe('exec-1');
|
||||
// workflowExecutionStateStore.activeExecutionId should remain 'exec-1' without change
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBe('exec-1');
|
||||
|
||||
// execution data should not have been overwritten (same reference or same id)
|
||||
expect(executionDataStore.execution?.id).toBe('exec-1');
|
||||
|
|
@ -114,7 +113,7 @@ describe('executionStarted', () => {
|
|||
// activeExecutionId defaults to undefined; in iframe context this should still accept
|
||||
await executionStarted(makeEvent('exec-2'));
|
||||
|
||||
expect(stateStore.activeExecutionId).toBe('exec-2');
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBe('exec-2');
|
||||
|
||||
const executionDataStore = useExecutionDataStore(createExecutionDataId('exec-2'));
|
||||
expect(executionDataStore.execution).toMatchObject({
|
||||
|
|
@ -125,7 +124,7 @@ describe('executionStarted', () => {
|
|||
|
||||
it('should accept new execution and reset state when re-executing in iframe', async () => {
|
||||
// Set up an existing active execution
|
||||
stateStore.promotePendingExecution('exec-1');
|
||||
workflowExecutionStateStore.promotePendingExecution('exec-1');
|
||||
const oldExecStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
oldExecStore.setExecution({
|
||||
id: 'exec-1',
|
||||
|
|
@ -142,7 +141,7 @@ describe('executionStarted', () => {
|
|||
|
||||
await executionStarted(makeEvent('exec-2'));
|
||||
|
||||
expect(stateStore.activeExecutionId).toBe('exec-2');
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBe('exec-2');
|
||||
|
||||
const newExecStore = useExecutionDataStore(createExecutionDataId('exec-2'));
|
||||
expect(newExecStore.execution).toMatchObject({
|
||||
|
|
@ -153,7 +152,7 @@ describe('executionStarted', () => {
|
|||
|
||||
it('should not reset when same execution ID arrives in iframe', async () => {
|
||||
// Set up an existing active execution with data
|
||||
stateStore.promotePendingExecution('exec-1');
|
||||
workflowExecutionStateStore.promotePendingExecution('exec-1');
|
||||
const executionDataStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
executionDataStore.setExecution({
|
||||
id: 'exec-1',
|
||||
|
|
@ -169,7 +168,7 @@ describe('executionStarted', () => {
|
|||
await executionStarted(makeEvent('exec-1'));
|
||||
|
||||
// Should remain exec-1 without reinitializing
|
||||
expect(stateStore.activeExecutionId).toBe('exec-1');
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBe('exec-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ import {
|
|||
createWorkflowDocumentId,
|
||||
useWorkflowDocumentStore,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import { parse } from 'flatted';
|
||||
import { createRunExecutionData } from 'n8n-workflow';
|
||||
|
|
@ -18,26 +15,29 @@ import type { IRunExecutionData } from 'n8n-workflow';
|
|||
*/
|
||||
export async function executionStarted({ data }: ExecutionStarted) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowsStore.workflowId),
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
workflowDocumentStore.documentId,
|
||||
);
|
||||
const isIframe = window !== window.parent;
|
||||
|
||||
// In non-iframe context, undefined means "not tracking executions" → skip.
|
||||
// In iframe context, executionFinished resets activeExecutionId to undefined,
|
||||
// but we still want to accept new executions (re-execution scenario).
|
||||
if (typeof stateStore.activeExecutionId === 'undefined' && !isIframe) {
|
||||
if (typeof workflowExecutionStateStore.activeExecutionId === 'undefined' && !isIframe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if we need to (re)initialize execution tracking state
|
||||
const needsInit =
|
||||
stateStore.activeExecutionId === null ||
|
||||
typeof stateStore.activeExecutionId === 'undefined' ||
|
||||
(isIframe && stateStore.activeExecutionId !== data.executionId);
|
||||
workflowExecutionStateStore.activeExecutionId === null ||
|
||||
typeof workflowExecutionStateStore.activeExecutionId === 'undefined' ||
|
||||
(isIframe && workflowExecutionStateStore.activeExecutionId !== data.executionId);
|
||||
|
||||
if (needsInit) {
|
||||
stateStore.promotePendingExecution(data.executionId);
|
||||
workflowExecutionStateStore.promotePendingExecution(data.executionId);
|
||||
}
|
||||
|
||||
const executionDataStore = useExecutionDataStore(createExecutionDataId(data.executionId));
|
||||
|
|
@ -45,10 +45,6 @@ export async function executionStarted({ data }: ExecutionStarted) {
|
|||
// Initialize or reinitialize execution data to clear previous execution's
|
||||
// node status (e.g. DemoLayout iframe receiving push events for a new execution).
|
||||
if (!executionDataStore.execution?.data || needsInit) {
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
|
||||
executionDataStore.setExecution({
|
||||
id: data.executionId,
|
||||
finished: false,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import { TRIMMED_TASK_DATA_CONNECTIONS_KEY } from 'n8n-workflow';
|
|||
import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import type { Mocked } from 'vitest';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import { createTestWorkflow, createTestWorkflowExecutionResponse } from '@/__tests__/mocks';
|
||||
|
||||
|
|
@ -31,7 +29,7 @@ import { openFormPopupWindow } from '@/features/execution/executions/executions.
|
|||
describe('nodeExecuteAfter', () => {
|
||||
let mockOptions: { workflowState: Mocked<WorkflowState> };
|
||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||
let stateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let workflowExecutionStateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let executionDataStore: ReturnType<typeof useExecutionDataStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -41,7 +39,9 @@ describe('nodeExecuteAfter', () => {
|
|||
workflowsStore = useWorkflowsStore();
|
||||
workflowsStore.setWorkflowId('test-wf');
|
||||
|
||||
stateStore = useWorkflowExecutionStateStore(createWorkflowExecutionStateId('test-wf'));
|
||||
workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('test-wf'),
|
||||
);
|
||||
|
||||
executionDataStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
executionDataStore.setExecution(
|
||||
|
|
@ -53,7 +53,7 @@ describe('nodeExecuteAfter', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
stateStore.setActiveExecutionId('exec-1');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-1');
|
||||
|
||||
mockOptions = {
|
||||
workflowState: mock<WorkflowState>({
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import type { NodeExecuteAfter } from '@n8n/api-types/push/execution';
|
||||
import { useAssistantStore } from '@/features/ai/assistant/assistant.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import type { INodeExecutionData, ITaskData } from 'n8n-workflow';
|
||||
import { TRIMMED_TASK_DATA_CONNECTIONS_KEY } from 'n8n-workflow';
|
||||
|
|
@ -22,8 +20,8 @@ export async function nodeExecuteAfter(
|
|||
{ workflowState }: { workflowState: WorkflowState },
|
||||
) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowsStore.workflowId),
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
const assistantStore = useAssistantStore();
|
||||
|
||||
|
|
@ -60,7 +58,7 @@ export async function nodeExecuteAfter(
|
|||
},
|
||||
};
|
||||
|
||||
const activeExecutionId = stateStore.activeExecutionId;
|
||||
const activeExecutionId = workflowExecutionStateStore.activeExecutionId;
|
||||
if (typeof activeExecutionId === 'string') {
|
||||
useExecutionDataStore(createExecutionDataId(activeExecutionId)).updateNodeExecutionStatus(
|
||||
pushDataWithPlaceholderOutputData,
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
|||
import type { NodeExecuteAfterData } from '@n8n/api-types/push/execution';
|
||||
import { createRunExecutionData } from 'n8n-workflow';
|
||||
import { createTestWorkflowExecutionResponse } from '@/__tests__/mocks';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
|
||||
describe('nodeExecuteAfterData', () => {
|
||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||
let stateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let workflowExecutionStateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let executionDataStore: ReturnType<typeof useExecutionDataStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -21,7 +19,9 @@ describe('nodeExecuteAfterData', () => {
|
|||
workflowsStore = useWorkflowsStore();
|
||||
workflowsStore.setWorkflowId('test-wf');
|
||||
|
||||
stateStore = useWorkflowExecutionStateStore(createWorkflowExecutionStateId('test-wf'));
|
||||
workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('test-wf'),
|
||||
);
|
||||
|
||||
executionDataStore = useExecutionDataStore(createExecutionDataId('exec-1'));
|
||||
executionDataStore.setExecution(
|
||||
|
|
@ -49,7 +49,7 @@ describe('nodeExecuteAfterData', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
stateStore.setActiveExecutionId('exec-1');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-1');
|
||||
});
|
||||
|
||||
it('should update node execution data with incoming payload', async () => {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ import type { NodeExecuteAfterData } from '@n8n/api-types/push/execution';
|
|||
import { useSchemaPreviewStore } from '@/features/ndv/runData/schemaPreview.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import {
|
||||
createWorkflowDocumentId,
|
||||
|
|
@ -17,15 +14,15 @@ import {
|
|||
*/
|
||||
export async function nodeExecuteAfterData({ data: pushData }: NodeExecuteAfterData) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowsStore.workflowId),
|
||||
);
|
||||
const workflowDocumentStore = computed(() =>
|
||||
useWorkflowDocumentStore(createWorkflowDocumentId(workflowsStore.workflowId)),
|
||||
);
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
workflowDocumentStore.value.documentId,
|
||||
);
|
||||
const schemaPreviewStore = useSchemaPreviewStore();
|
||||
|
||||
const activeExecutionId = stateStore.activeExecutionId;
|
||||
const activeExecutionId = workflowExecutionStateStore.activeExecutionId;
|
||||
if (typeof activeExecutionId === 'string') {
|
||||
useExecutionDataStore(createExecutionDataId(activeExecutionId)).updateNodeExecutionRunData(
|
||||
pushData,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import type { NodeExecuteBefore } from '@n8n/api-types/push/execution';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
|
||||
|
|
@ -15,13 +13,13 @@ export async function nodeExecuteBefore(
|
|||
{ workflowState }: { workflowState: WorkflowState },
|
||||
) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowsStore.workflowId),
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
|
||||
workflowState.executingNode.addExecutingNode(data.nodeName);
|
||||
|
||||
const activeExecutionId = stateStore.activeExecutionId;
|
||||
const activeExecutionId = workflowExecutionStateStore.activeExecutionId;
|
||||
if (typeof activeExecutionId === 'string') {
|
||||
useExecutionDataStore(createExecutionDataId(activeExecutionId)).addNodeExecutionStartedData(
|
||||
data,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import type { TestWebhookDeleted } from '@n8n/api-types/push/webhook';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
|
||||
/**
|
||||
|
|
@ -12,7 +14,9 @@ export async function testWebhookDeleted(
|
|||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
if (data.workflowId === workflowsStore.workflowId) {
|
||||
workflowsStore.setExecutionWaitingForWebhook(false);
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
).setExecutionWaitingForWebhook(false);
|
||||
options.workflowState.setActiveExecutionId(undefined);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import type { TestWebhookReceived } from '@n8n/api-types/push/webhook';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import type { WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
|
||||
/**
|
||||
|
|
@ -12,7 +14,9 @@ export async function testWebhookReceived(
|
|||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
if (data.workflowId === workflowsStore.workflowId) {
|
||||
workflowsStore.setExecutionWaitingForWebhook(false);
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
).setExecutionWaitingForWebhook(false);
|
||||
options.workflowState.setActiveExecutionId(data.executionId ?? null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import type { INodeUi, IStartRunData } from '@/Interface';
|
|||
import type { IExecutionResponse } from '@/features/execution/executions/executions.types';
|
||||
import type { WorkflowData } from '@n8n/rest-api-client/api/workflows';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useToast } from '@/app/composables/useToast';
|
||||
import { useWorkflowHelpers } from '@/app/composables/useWorkflowHelpers';
|
||||
|
|
@ -53,6 +55,7 @@ type Writable<T> = { -readonly [K in keyof T]: T[K] };
|
|||
|
||||
const { mockDocumentStore } = vi.hoisted(() => {
|
||||
const store = {
|
||||
documentId: '123@latest',
|
||||
workflowId: '123',
|
||||
name: 'Test Workflow',
|
||||
allNodes: [],
|
||||
|
|
@ -95,12 +98,12 @@ vi.mock('@/app/stores/workflowDocument.store', () => ({
|
|||
}));
|
||||
|
||||
vi.mock('@/app/stores/workflows.store', async () => {
|
||||
const { createWorkflowExecutionStateId, useWorkflowExecutionStateStore } = await vi.importActual<
|
||||
const { useWorkflowExecutionStateStore } = await vi.importActual<
|
||||
typeof import('@/app/stores/workflowExecutionState.store')
|
||||
>('@/app/stores/workflowExecutionState.store');
|
||||
|
||||
function getStateStore() {
|
||||
return useWorkflowExecutionStateStore(createWorkflowExecutionStateId('123'));
|
||||
return useWorkflowExecutionStateStore(createWorkflowDocumentId('123'));
|
||||
}
|
||||
|
||||
const storeState: Record<string, unknown> = {
|
||||
|
|
@ -356,7 +359,9 @@ describe('useRunWorkflow({ router })', () => {
|
|||
expect(response).toEqual(mockResponse);
|
||||
expect(setActiveExecutionId).toHaveBeenNthCalledWith(1, null);
|
||||
expect(setActiveExecutionId).toHaveBeenNthCalledWith(2, '123');
|
||||
expect(workflowsStore.executionWaitingForWebhook).toBe(false);
|
||||
expect(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('123')).executionWaitingForWebhook,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should not prevent running a webhook-based workflow that has issues', async () => {
|
||||
|
|
@ -393,7 +398,9 @@ describe('useRunWorkflow({ router })', () => {
|
|||
const response = await runWorkflowApi({} as IStartRunData);
|
||||
|
||||
expect(response).toEqual(mockResponse);
|
||||
expect(workflowsStore.executionWaitingForWebhook).toBe(true);
|
||||
expect(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('123')).executionWaitingForWebhook,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1259,7 +1266,9 @@ describe('useRunWorkflow({ router })', () => {
|
|||
const runWorkflowComposable = useRunWorkflow({ router });
|
||||
|
||||
mockDocumentStore.allNodes = [chatTrigger];
|
||||
vi.mocked(workflowsStore).selectedTriggerNodeName = undefined;
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('123')).setSelectedTriggerNodeName(
|
||||
undefined,
|
||||
);
|
||||
mockDocumentStore.serialize.mockReturnValue({
|
||||
id: 'workflowId',
|
||||
nodes: [],
|
||||
|
|
@ -1289,7 +1298,9 @@ describe('useRunWorkflow({ router })', () => {
|
|||
const runWorkflowComposable = useRunWorkflow({ router });
|
||||
|
||||
mockDocumentStore.allNodes = [chatTrigger, manualTrigger];
|
||||
vi.mocked(workflowsStore).selectedTriggerNodeName = undefined;
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('123')).setSelectedTriggerNodeName(
|
||||
undefined,
|
||||
);
|
||||
mockDocumentStore.serialize.mockReturnValue({
|
||||
id: 'workflowId',
|
||||
nodes: [],
|
||||
|
|
@ -1390,7 +1401,9 @@ describe('useRunWorkflow({ router })', () => {
|
|||
'test-wf-id',
|
||||
);
|
||||
workflowState.setActiveExecutionId('test-exec-id');
|
||||
workflowsStore.setExecutionWaitingForWebhook(false);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('123')).setExecutionWaitingForWebhook(
|
||||
false,
|
||||
);
|
||||
|
||||
getExecutionSpy.mockResolvedValue(executionData);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
BINARY_MODE_COMBINED,
|
||||
} from 'n8n-workflow';
|
||||
import { retry } from '@n8n/utils/retry';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useToast } from '@/app/composables/useToast';
|
||||
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
|
||||
|
|
@ -37,6 +38,7 @@ import {
|
|||
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { displayForm } from '@/features/execution/executions/executions.utils';
|
||||
import { useExternalHooks } from '@/app/composables/useExternalHooks';
|
||||
|
|
@ -76,12 +78,14 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
|||
const rootStore = useRootStore();
|
||||
const pushConnectionStore = usePushConnectionStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionState = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
// `inject()` only resolves inside a setup context; callers from async event
|
||||
// handlers must pass `workflowState` in.
|
||||
const workflowState = useRunWorkflowOpts.workflowState ?? injectWorkflowState();
|
||||
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const workflowSaving = useWorkflowSaving({
|
||||
router: useRunWorkflowOpts.router,
|
||||
|
|
@ -124,14 +128,15 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const workflowExecutionIdIsNew = workflowsStore.previousExecutionId !== response.executionId;
|
||||
const workflowExecutionIdIsPending = workflowsStore.activeExecutionId === null;
|
||||
const workflowExecutionIdIsNew =
|
||||
workflowExecutionState.value.previousExecutionId !== response.executionId;
|
||||
const workflowExecutionIdIsPending = workflowExecutionState.value.activeExecutionId === null;
|
||||
if (response.executionId && workflowExecutionIdIsNew && workflowExecutionIdIsPending) {
|
||||
workflowState.setActiveExecutionId(response.executionId);
|
||||
}
|
||||
|
||||
if (response.waitingForWebhook === true) {
|
||||
workflowsStore.setExecutionWaitingForWebhook(true);
|
||||
workflowExecutionState.value.setExecutionWaitingForWebhook(true);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
|
@ -145,7 +150,7 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
|||
source?: string;
|
||||
sessionId?: string;
|
||||
}): Promise<IExecutionPushResponse | undefined> {
|
||||
if (workflowsStore.activeExecutionId) {
|
||||
if (workflowExecutionState.value.activeExecutionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +253,9 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
|||
// If the chat node has no input data or pin data, open the chat modal
|
||||
// and halt the execution
|
||||
if (!chatHasInputData && !chatHasPinData) {
|
||||
workflowsStore.setChatPartialExecutionDestinationNode(options.destinationNode.nodeName);
|
||||
workflowExecutionState.value.setChatPartialExecutionDestinationNode(
|
||||
options.destinationNode.nodeName,
|
||||
);
|
||||
startChat();
|
||||
return;
|
||||
}
|
||||
|
|
@ -513,7 +520,7 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
|||
}
|
||||
|
||||
async function stopCurrentExecution() {
|
||||
const executionId = workflowsStore.activeExecutionId;
|
||||
const executionId = workflowExecutionState.value.activeExecutionId;
|
||||
let stopData: IExecutionsStopData | undefined;
|
||||
|
||||
if (!executionId) {
|
||||
|
|
@ -603,7 +610,7 @@ export function useRunWorkflow(useRunWorkflowOpts: {
|
|||
telemetry.track('User clicked execute workflow button', telemetryPayload);
|
||||
void externalHooks.run('nodeView.onRunWorkflow', telemetryPayload);
|
||||
|
||||
let resolvedTriggerNode = triggerNode ?? workflowsStore.selectedTriggerNodeName;
|
||||
let resolvedTriggerNode = triggerNode ?? workflowExecutionState.value.selectedTriggerNodeName;
|
||||
|
||||
// When no trigger is explicitly selected (e.g. chat trigger is the only trigger
|
||||
// and the Run button doesn't offer it for selection), resolve it from the workflow.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useExternalHooks } from '@/app/composables/useExternalHooks';
|
|||
import { useCanvasOperations } from '@/app/composables/useCanvasOperations';
|
||||
import { useParentFolder } from '@/features/core/folders/composables/useParentFolder';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowsListStore } from '@/app/stores/workflowsList.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
|
|
@ -196,11 +197,14 @@ export function useWorkflowInitialization(workflowState: WorkflowState) {
|
|||
|
||||
documentTitle.setDocumentTitle(currentWorkflowDocumentStore.value?.name ?? '', 'DEBUG');
|
||||
|
||||
if (!workflowsStore.isInDebugMode) {
|
||||
const executionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
if (!executionStateStore.isInDebugMode) {
|
||||
const executionId = route.params.executionId;
|
||||
if (typeof executionId === 'string') {
|
||||
await applyExecutionData(executionId);
|
||||
workflowsStore.setIsInDebugMode(true);
|
||||
executionStateStore.setIsInDebugMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,15 @@ import { useWorkflowState, type WorkflowState } from './useWorkflowState';
|
|||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { createTestTaskData, createTestWorkflowExecutionResponse } from '@/__tests__/mocks';
|
||||
import { createRunExecutionData } from 'n8n-workflow';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import { IN_PROGRESS_EXECUTION_ID } from '@/app/constants/placeholders';
|
||||
|
||||
describe('useWorkflowState', () => {
|
||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||
let workflowState: WorkflowState;
|
||||
let stateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let workflowExecutionStateStore: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
let executionDataStore: ReturnType<typeof useExecutionDataStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -23,13 +21,15 @@ describe('useWorkflowState', () => {
|
|||
workflowsStore.setWorkflowId('test-wf');
|
||||
workflowState = useWorkflowState();
|
||||
|
||||
stateStore = useWorkflowExecutionStateStore(createWorkflowExecutionStateId('test-wf'));
|
||||
workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('test-wf'),
|
||||
);
|
||||
});
|
||||
|
||||
describe('markExecutionAsStopped', () => {
|
||||
beforeEach(() => {
|
||||
// Set up active execution in the facade stores
|
||||
stateStore.setActiveExecutionId('test-exec-id');
|
||||
workflowExecutionStateStore.setActiveExecutionId('test-exec-id');
|
||||
|
||||
executionDataStore = useExecutionDataStore(createExecutionDataId('test-exec-id'));
|
||||
executionDataStore.setExecution(
|
||||
|
|
@ -91,15 +91,15 @@ describe('useWorkflowState', () => {
|
|||
describe('when activeExecutionId is null (pending scaffold)', () => {
|
||||
beforeEach(() => {
|
||||
// Reset to pending state instead of the string-id default from outer beforeEach.
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
stateStore.setPendingExecution(
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
workflowExecutionStateStore.setPendingExecution(
|
||||
createTestWorkflowExecutionResponse({
|
||||
id: IN_PROGRESS_EXECUTION_ID,
|
||||
status: 'running',
|
||||
}),
|
||||
);
|
||||
// Re-set since promotePendingExecution would have moved it; emulate raw scaffold state.
|
||||
stateStore.setActiveExecutionId(null);
|
||||
workflowExecutionStateStore.setActiveExecutionId(null);
|
||||
|
||||
useExecutionDataStore(createExecutionDataId(IN_PROGRESS_EXECUTION_ID)).setExecution(
|
||||
createTestWorkflowExecutionResponse({
|
||||
|
|
@ -143,9 +143,13 @@ describe('useWorkflowState', () => {
|
|||
mode: 'manual',
|
||||
});
|
||||
|
||||
expect(stateStore.pendingExecution?.status).toBe('canceled');
|
||||
expect(stateStore.pendingExecution?.startedAt).toEqual(new Date('2023-01-01T10:00:00Z'));
|
||||
expect(stateStore.pendingExecution?.stoppedAt).toEqual(new Date('2023-01-01T10:05:00Z'));
|
||||
expect(workflowExecutionStateStore.pendingExecution?.status).toBe('canceled');
|
||||
expect(workflowExecutionStateStore.pendingExecution?.startedAt).toEqual(
|
||||
new Date('2023-01-01T10:00:00Z'),
|
||||
);
|
||||
expect(workflowExecutionStateStore.pendingExecution?.stoppedAt).toEqual(
|
||||
new Date('2023-01-01T10:05:00Z'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -153,10 +157,10 @@ describe('useWorkflowState', () => {
|
|||
beforeEach(() => {
|
||||
// Simulate post-stop-race: active was just cleared, but displayed still points
|
||||
// at the freshly-fetched finished execution.
|
||||
stateStore.setActiveExecutionId('display-exec');
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
expect(stateStore.activeExecutionId).toBeUndefined();
|
||||
expect(stateStore.displayedExecutionId).toBe('display-exec');
|
||||
workflowExecutionStateStore.setActiveExecutionId('display-exec');
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
expect(workflowExecutionStateStore.activeExecutionId).toBeUndefined();
|
||||
expect(workflowExecutionStateStore.displayedExecutionId).toBe('display-exec');
|
||||
|
||||
useExecutionDataStore(createExecutionDataId('display-exec')).setExecution(
|
||||
createTestWorkflowExecutionResponse({
|
||||
|
|
@ -195,15 +199,15 @@ describe('useWorkflowState', () => {
|
|||
describe('resetState', () => {
|
||||
it('disposes every executionData store this workflow ever bound, including rolled-out ids', () => {
|
||||
// Three sequential runs — exec-1 rolls out of previousExecutionId after run 3.
|
||||
stateStore.setActiveExecutionId('exec-1');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-1');
|
||||
useExecutionDataStore(createExecutionDataId('exec-1')).setExecution(
|
||||
createTestWorkflowExecutionResponse({ id: 'exec-1' }),
|
||||
);
|
||||
stateStore.setActiveExecutionId('exec-2');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-2');
|
||||
useExecutionDataStore(createExecutionDataId('exec-2')).setExecution(
|
||||
createTestWorkflowExecutionResponse({ id: 'exec-2' }),
|
||||
);
|
||||
stateStore.setActiveExecutionId('exec-3');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-3');
|
||||
useExecutionDataStore(createExecutionDataId('exec-3')).setExecution(
|
||||
createTestWorkflowExecutionResponse({ id: 'exec-3' }),
|
||||
);
|
||||
|
|
@ -216,18 +220,18 @@ describe('useWorkflowState', () => {
|
|||
});
|
||||
|
||||
it('clears displayedExecutionId so workflowExecutionData reads as null', () => {
|
||||
stateStore.setActiveExecutionId('exec-A');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-A');
|
||||
useExecutionDataStore(createExecutionDataId('exec-A')).setExecution(
|
||||
createTestWorkflowExecutionResponse({ id: 'exec-A', finished: true, status: 'success' }),
|
||||
);
|
||||
// Simulate post-finish: active cleared, displayed preserved (the deliberate UX behavior).
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
expect(stateStore.displayedExecutionId).toBe('exec-A');
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
expect(workflowExecutionStateStore.displayedExecutionId).toBe('exec-A');
|
||||
expect(workflowsStore.workflowExecutionData?.id).toBe('exec-A');
|
||||
|
||||
workflowState.resetState();
|
||||
|
||||
const fresh = useWorkflowExecutionStateStore(createWorkflowExecutionStateId('test-wf'));
|
||||
const fresh = useWorkflowExecutionStateStore(createWorkflowDocumentId('test-wf'));
|
||||
expect(fresh.displayedExecutionId).toBeUndefined();
|
||||
expect(fresh.activeExecutionId).toBeUndefined();
|
||||
expect(fresh.pendingExecution).toBeNull();
|
||||
|
|
@ -235,7 +239,7 @@ describe('useWorkflowState', () => {
|
|||
});
|
||||
|
||||
it('disposes the IN_PROGRESS placeholder store along with the pending scaffold', () => {
|
||||
stateStore.setPendingExecution(
|
||||
workflowExecutionStateStore.setPendingExecution(
|
||||
createTestWorkflowExecutionResponse({
|
||||
id: IN_PROGRESS_EXECUTION_ID,
|
||||
status: 'running',
|
||||
|
|
@ -260,11 +264,11 @@ describe('useWorkflowState', () => {
|
|||
|
||||
it('reopening the same workflow id after resetWorkspace order surfaces no stale state', () => {
|
||||
// Stage execution on test-wf
|
||||
stateStore.setActiveExecutionId('exec-A');
|
||||
workflowExecutionStateStore.setActiveExecutionId('exec-A');
|
||||
useExecutionDataStore(createExecutionDataId('exec-A')).setExecution(
|
||||
createTestWorkflowExecutionResponse({ id: 'exec-A', finished: true, status: 'success' }),
|
||||
);
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
expect(workflowsStore.workflowExecutionData?.id).toBe('exec-A');
|
||||
|
||||
// Mirror resetWorkspace ordering: resetState first (while workflowId is still set),
|
||||
|
|
@ -277,7 +281,7 @@ describe('useWorkflowState', () => {
|
|||
workflowsStore.setWorkflowId('test-wf');
|
||||
|
||||
// Fresh state — no leakage.
|
||||
const fresh = useWorkflowExecutionStateStore(createWorkflowExecutionStateId('test-wf'));
|
||||
const fresh = useWorkflowExecutionStateStore(createWorkflowDocumentId('test-wf'));
|
||||
expect(fresh.activeExecutionId).toBeUndefined();
|
||||
expect(fresh.displayedExecutionId).toBeUndefined();
|
||||
expect(fresh.pendingExecution).toBeNull();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { DEFAULT_SETTINGS } from '@/app/stores/workflowDocument/useWorkflowDocum
|
|||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowStateStore } from '@/app/stores/workflowState.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
disposeWorkflowExecutionStateStore,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
|
|
@ -37,35 +36,35 @@ export function useWorkflowState() {
|
|||
////
|
||||
|
||||
function setWorkflowExecutionData(workflowResultData: IExecutionResponse | null) {
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(ws.workflowId),
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(ws.workflowId),
|
||||
);
|
||||
if (workflowResultData === null) {
|
||||
stateStore.setPendingExecution(null);
|
||||
stateStore.clearDisplayedExecution();
|
||||
workflowExecutionStateStore.setPendingExecution(null);
|
||||
workflowExecutionStateStore.clearDisplayedExecution();
|
||||
} else if (workflowResultData.id === IN_PROGRESS_EXECUTION_ID) {
|
||||
stateStore.setPendingExecution(workflowResultData);
|
||||
stateStore.setActiveExecutionId(null);
|
||||
workflowExecutionStateStore.setPendingExecution(workflowResultData);
|
||||
workflowExecutionStateStore.setActiveExecutionId(null);
|
||||
useExecutionDataStore(createExecutionDataId(IN_PROGRESS_EXECUTION_ID)).setExecution(
|
||||
workflowResultData,
|
||||
);
|
||||
} else {
|
||||
stateStore.trackExecutionId(workflowResultData.id);
|
||||
workflowExecutionStateStore.trackExecutionId(workflowResultData.id);
|
||||
useExecutionDataStore(createExecutionDataId(workflowResultData.id)).setExecution(
|
||||
workflowResultData,
|
||||
);
|
||||
if (typeof stateStore.activeExecutionId !== 'string') {
|
||||
stateStore.setPendingExecution(null);
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
stateStore.setDisplayedExecutionId(workflowResultData.id);
|
||||
if (typeof workflowExecutionStateStore.activeExecutionId !== 'string') {
|
||||
workflowExecutionStateStore.setPendingExecution(null);
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
workflowExecutionStateStore.setDisplayedExecutionId(workflowResultData.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveExecutionId(id: string | null | undefined) {
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(ws.workflowId),
|
||||
).setActiveExecutionId(id);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId(ws.workflowId)).setActiveExecutionId(
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
async function getNewWorkflowData(
|
||||
|
|
@ -103,16 +102,16 @@ export function useWorkflowState() {
|
|||
const documentTitle = useDocumentTitle();
|
||||
|
||||
function markExecutionAsStopped(stopData?: IExecutionsStopData) {
|
||||
const stateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(ws.workflowId),
|
||||
);
|
||||
const activeExecutionId = stateStore.activeExecutionId;
|
||||
|
||||
stateStore.setActiveExecutionId(undefined);
|
||||
workflowStateStore.executingNode.clearNodeExecutionQueue();
|
||||
stateStore.setExecutionWaitingForWebhook(false);
|
||||
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(ws.workflowId));
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
workflowDocumentStore.documentId,
|
||||
);
|
||||
const activeExecutionId = workflowExecutionStateStore.activeExecutionId;
|
||||
|
||||
workflowExecutionStateStore.setActiveExecutionId(undefined);
|
||||
workflowStateStore.executingNode.clearNodeExecutionQueue();
|
||||
workflowExecutionStateStore.setExecutionWaitingForWebhook(false);
|
||||
|
||||
documentTitle.setDocumentTitle(workflowDocumentStore.name, 'IDLE');
|
||||
|
||||
if (typeof activeExecutionId === 'string') {
|
||||
|
|
@ -128,12 +127,12 @@ export function useWorkflowState() {
|
|||
executionDataStore.clearExecutionStartedData();
|
||||
executionDataStore.markAsStopped(stopData);
|
||||
if (stopData) {
|
||||
stateStore.applyStopDataToPendingExecution(stopData);
|
||||
workflowExecutionStateStore.applyStopDataToPendingExecution(stopData);
|
||||
}
|
||||
} else {
|
||||
// activeExecutionId === undefined: fall back to displayedExecutionId for the
|
||||
// stop-race-with-finished case where active was just cleared.
|
||||
const displayedExecutionId = stateStore.displayedExecutionId;
|
||||
const displayedExecutionId = workflowExecutionStateStore.displayedExecutionId;
|
||||
if (typeof displayedExecutionId === 'string') {
|
||||
const executionDataStore = useExecutionDataStore(
|
||||
createExecutionDataId(displayedExecutionId),
|
||||
|
|
@ -153,13 +152,15 @@ export function useWorkflowState() {
|
|||
useBuilderStore().resetManualExecutionStats();
|
||||
return;
|
||||
}
|
||||
const stateStore = useWorkflowExecutionStateStore(createWorkflowExecutionStateId(wid));
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(wid),
|
||||
);
|
||||
// Disposes every tracked executionData store + IN_PROGRESS placeholder, then clears all
|
||||
// session-level fields.
|
||||
stateStore.resetExecutionState();
|
||||
workflowExecutionStateStore.resetExecutionState();
|
||||
// Then dispose the per-workflow state store so pinia state doesn't accumulate one entry
|
||||
// per workflow ever opened in this session.
|
||||
disposeWorkflowExecutionStateStore(stateStore);
|
||||
disposeWorkflowExecutionStateStore(workflowExecutionStateStore);
|
||||
|
||||
workflowStateStore.executingNode.executingNode.length = 0;
|
||||
useBuilderStore().resetManualExecutionStats();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowDocumentRenderData } from './useWorkflowDocumentRenderData';
|
||||
|
||||
const nodeInputsByNodeId = shallowReactive(new Map<string, ComputedRef<CanvasConnectionPort[]>>());
|
||||
|
|
@ -31,13 +32,13 @@ vi.mock('@/app/stores/workflowExecutionState.store', () => ({
|
|||
useWorkflowExecutionStateStore: vi.fn(() => ({
|
||||
activeExecutionIssuesByNodeName: executionIssuesByNodeName,
|
||||
})),
|
||||
createWorkflowExecutionStateId: (id: string) => id,
|
||||
}));
|
||||
|
||||
describe('useWorkflowDocumentRenderData', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
vi.mocked(useWorkflowDocumentStore).mockClear();
|
||||
vi.mocked(useWorkflowExecutionStateStore).mockClear();
|
||||
});
|
||||
|
||||
it('passes through nodeInputsByNodeId and nodeOutputsByNodeId by reference', () => {
|
||||
|
|
@ -58,4 +59,12 @@ describe('useWorkflowDocumentRenderData', () => {
|
|||
|
||||
expect(renderData.executionIssuesByNodeName).toBe(executionIssuesByNodeName);
|
||||
});
|
||||
|
||||
it('uses the exact workflow document id when resolving execution state', () => {
|
||||
const documentId = createWorkflowDocumentId('wf-1', 'ver-123');
|
||||
|
||||
useWorkflowDocumentRenderData(documentId);
|
||||
|
||||
expect(useWorkflowExecutionStateStore).toHaveBeenCalledWith(documentId);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ import {
|
|||
useWorkflowDocumentStore,
|
||||
type WorkflowDocumentId,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import {
|
||||
useWorkflowExecutionStateStore,
|
||||
createWorkflowExecutionStateId,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
|
||||
/**
|
||||
* Canvas render data accessor for a workflow document.
|
||||
|
|
@ -25,9 +22,7 @@ import {
|
|||
*/
|
||||
export function useWorkflowDocumentRenderData(workflowDocumentId: WorkflowDocumentId) {
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(workflowDocumentId);
|
||||
const executionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(workflowDocumentStore.workflowId),
|
||||
);
|
||||
const executionStateStore = useWorkflowExecutionStateStore(workflowDocumentId);
|
||||
|
||||
return {
|
||||
nodeInputsByNodeId: workflowDocumentStore.nodeInputsByNodeId,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -13,16 +13,14 @@ import {
|
|||
disposeExecutionDataStore,
|
||||
useExecutionDataStore,
|
||||
} from './executionData.store';
|
||||
import { createWorkflowDocumentId, useWorkflowDocumentStore } from './workflowDocument.store';
|
||||
import { useWorkflowDocumentStore, type WorkflowDocumentId } from './workflowDocument.store';
|
||||
import { CHANGE_ACTION } from './workflowDocument/types';
|
||||
import type { ChangeAction, ChangeEvent } from './workflowDocument/types';
|
||||
|
||||
const EMPTY_EXECUTION_ISSUES_BY_NODE_NAME = new Map<string, ComputedRef<string[]>>();
|
||||
|
||||
export type WorkflowExecutionStateId = string;
|
||||
|
||||
export type WorkflowExecutionStateChangePayload = {
|
||||
workflowId: WorkflowExecutionStateId;
|
||||
documentId: WorkflowDocumentId;
|
||||
field: WorkflowExecutionStateField;
|
||||
};
|
||||
|
||||
|
|
@ -42,19 +40,18 @@ export type WorkflowExecutionStateField =
|
|||
|
||||
export type WorkflowExecutionStateChangeEvent = ChangeEvent<WorkflowExecutionStateChangePayload>;
|
||||
|
||||
export function createWorkflowExecutionStateId(workflowId: string): WorkflowExecutionStateId {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Pinia store id for a workflow-execution-state store.
|
||||
*/
|
||||
export function getWorkflowExecutionStateStoreId(id: WorkflowExecutionStateId) {
|
||||
export function getWorkflowExecutionStateStoreId(id: WorkflowDocumentId) {
|
||||
return `${STORES.WORKFLOW_EXECUTION_STATES}/${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a workflow-execution-state store keyed by workflow id.
|
||||
* Creates a workflow-execution-state store keyed by the workflow document id.
|
||||
* One execution-state store exists per workflow-document store, so the two
|
||||
* share an identity — pass the same `WorkflowDocumentId` (constructed via
|
||||
* `createWorkflowDocumentId`) to both factories.
|
||||
*
|
||||
* Owns per-workflow execution UI state — active/displayed/previous
|
||||
* execution ids, the pending-execution scaffold, chat, debug, webhook wait,
|
||||
|
|
@ -62,9 +59,10 @@ export function getWorkflowExecutionStateStoreId(id: WorkflowExecutionStateId) {
|
|||
* reference. Reads route through `useExecutionDataStore` for execution payloads
|
||||
* (or fall back to `pendingExecution` while `activeExecutionId === null`).
|
||||
*/
|
||||
export function useWorkflowExecutionStateStore(id: WorkflowExecutionStateId) {
|
||||
export function useWorkflowExecutionStateStore(id: WorkflowDocumentId) {
|
||||
return defineStore(getWorkflowExecutionStateStoreId(id), () => {
|
||||
const workflowId = id;
|
||||
const documentId = id;
|
||||
const [workflowId] = id.split('@');
|
||||
|
||||
// --- State ---
|
||||
|
||||
|
|
@ -104,7 +102,7 @@ export function useWorkflowExecutionStateStore(id: WorkflowExecutionStateId) {
|
|||
function fireChange(action: ChangeAction, field: WorkflowExecutionStateField) {
|
||||
void onWorkflowExecutionStateChange.trigger({
|
||||
action,
|
||||
payload: { workflowId, field },
|
||||
payload: { documentId, field },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -561,9 +559,7 @@ export function useWorkflowExecutionStateStore(id: WorkflowExecutionStateId) {
|
|||
}
|
||||
|
||||
if (workflowId) {
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(
|
||||
createWorkflowDocumentId(workflowId),
|
||||
);
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(documentId);
|
||||
workflowDocumentStore.renameNodeMetadata(nameData.old, nameData.new);
|
||||
workflowDocumentStore.renamePinDataNode(nameData.old, nameData.new);
|
||||
}
|
||||
|
|
@ -595,6 +591,7 @@ export function useWorkflowExecutionStateStore(id: WorkflowExecutionStateId) {
|
|||
}
|
||||
|
||||
return {
|
||||
documentId,
|
||||
workflowId,
|
||||
// Read API
|
||||
activeExecutionId: readonly(activeExecutionId),
|
||||
|
|
|
|||
|
|
@ -1261,30 +1261,6 @@ describe('useWorkflowsStore', () => {
|
|||
});
|
||||
|
||||
describe('execution session setters', () => {
|
||||
it('setExecutionWaitingForWebhook updates the value', () => {
|
||||
expect(workflowsStore.executionWaitingForWebhook).toBe(false);
|
||||
workflowsStore.setExecutionWaitingForWebhook(true);
|
||||
expect(workflowsStore.executionWaitingForWebhook).toBe(true);
|
||||
workflowsStore.setExecutionWaitingForWebhook(false);
|
||||
expect(workflowsStore.executionWaitingForWebhook).toBe(false);
|
||||
});
|
||||
|
||||
it('setIsInDebugMode updates the value', () => {
|
||||
expect(workflowsStore.isInDebugMode).toBe(false);
|
||||
workflowsStore.setIsInDebugMode(true);
|
||||
expect(workflowsStore.isInDebugMode).toBe(true);
|
||||
workflowsStore.setIsInDebugMode(false);
|
||||
expect(workflowsStore.isInDebugMode).toBe(false);
|
||||
});
|
||||
|
||||
it('setChatPartialExecutionDestinationNode updates the value', () => {
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBeNull();
|
||||
workflowsStore.setChatPartialExecutionDestinationNode('Some Node');
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe('Some Node');
|
||||
workflowsStore.setChatPartialExecutionDestinationNode(null);
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBeNull();
|
||||
});
|
||||
|
||||
it('setLastSuccessfulExecution updates the value independently of active execution', () => {
|
||||
const execution = { id: 'last-success' } as IExecutionResponse;
|
||||
workflowsStore.setWorkflowExecutionData({
|
||||
|
|
@ -1377,34 +1353,4 @@ describe('useWorkflowsStore', () => {
|
|||
expect(workflowsStore.currentWorkflowExecutions).toEqual([exec2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeExecutionId tri-state', () => {
|
||||
it('starts undefined (not tracking)', () => {
|
||||
expect(workflowsStore.activeExecutionId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('null indicates execution started but id pending', () => {
|
||||
workflowsStore.private.setActiveExecutionId(null);
|
||||
expect(workflowsStore.activeExecutionId).toBeNull();
|
||||
});
|
||||
|
||||
it('string indicates known execution id', () => {
|
||||
workflowsStore.private.setActiveExecutionId('exec-1');
|
||||
expect(workflowsStore.activeExecutionId).toBe('exec-1');
|
||||
});
|
||||
|
||||
it('rolls activeExecutionId into previousExecutionId on transition to a new id', () => {
|
||||
workflowsStore.private.setActiveExecutionId('exec-1');
|
||||
workflowsStore.private.setActiveExecutionId('exec-2');
|
||||
expect(workflowsStore.previousExecutionId).toBe('exec-1');
|
||||
expect(workflowsStore.activeExecutionId).toBe('exec-2');
|
||||
});
|
||||
|
||||
it('does not update previousExecutionId when clearing to undefined', () => {
|
||||
workflowsStore.private.setActiveExecutionId('exec-1');
|
||||
workflowsStore.private.setActiveExecutionId(undefined);
|
||||
expect(workflowsStore.previousExecutionId).toBeUndefined();
|
||||
expect(workflowsStore.activeExecutionId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,10 +48,7 @@ import {
|
|||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
|
||||
export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||
const uiStore = useUIStore();
|
||||
|
|
@ -83,7 +80,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
// done together to preserve test contracts.
|
||||
|
||||
const currentExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(createWorkflowExecutionStateId(workflowId.value)),
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId(workflowId.value)),
|
||||
);
|
||||
|
||||
const currentWorkflowExecutions = computed<ExecutionSummary[]>({
|
||||
|
|
@ -118,30 +115,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
>,
|
||||
);
|
||||
|
||||
const executionWaitingForWebhook = computed<boolean>({
|
||||
get: () => currentExecutionStateStore.value.executionWaitingForWebhook,
|
||||
set: (value) => currentExecutionStateStore.value.setExecutionWaitingForWebhook(value),
|
||||
});
|
||||
|
||||
const isInDebugMode = computed<boolean>({
|
||||
get: () => currentExecutionStateStore.value.isInDebugMode,
|
||||
set: (value) => currentExecutionStateStore.value.setIsInDebugMode(value),
|
||||
});
|
||||
|
||||
const chatMessages = computed<string[]>(
|
||||
() => currentExecutionStateStore.value.chatMessages as string[],
|
||||
);
|
||||
|
||||
const chatPartialExecutionDestinationNode = computed<string | null>({
|
||||
get: () => currentExecutionStateStore.value.chatPartialExecutionDestinationNode,
|
||||
set: (value) => currentExecutionStateStore.value.setChatPartialExecutionDestinationNode(value),
|
||||
});
|
||||
|
||||
const selectedTriggerNodeName = computed<string | undefined>({
|
||||
get: () => currentExecutionStateStore.value.selectedTriggerNodeName,
|
||||
set: (value) => currentExecutionStateStore.value.setSelectedTriggerNodeName(value),
|
||||
});
|
||||
|
||||
// A workflow is new if it hasn't been saved to the backend yet.
|
||||
// TODO: move to workflowDocumentStore after `workflow` ref is removed from this store.
|
||||
// When moved, preserve the `workflowsListStore.getWorkflowById` coupling — pure
|
||||
|
|
@ -174,21 +147,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
return workflowExecutionData.value.data.resultData.runData;
|
||||
});
|
||||
|
||||
const isWorkflowRunning = computed(() => {
|
||||
if (activeExecutionId.value === null) {
|
||||
return true;
|
||||
} else if (activeExecutionId.value && workflowExecutionData.value) {
|
||||
if (
|
||||
['waiting', 'running'].includes(workflowExecutionData.value.status) &&
|
||||
!workflowExecutionData.value.finished
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const executedNode = computed(() => workflowExecutionData.value?.executedNode);
|
||||
|
||||
const getAllLoadedFinishedExecutions = computed(() => {
|
||||
|
|
@ -199,37 +157,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
|
||||
const getWorkflowExecution = computed(() => workflowExecutionData.value);
|
||||
|
||||
const getPastChatMessages = computed(() => chatMessages.value);
|
||||
|
||||
const canViewWorkflows = computed(
|
||||
() => !settingsStore.isChatFeatureEnabled || !hasRole(['global:chatUser']),
|
||||
);
|
||||
|
||||
/**
|
||||
* Active execution id (tri-state):
|
||||
* - undefined → no active execution being tracked
|
||||
* - null → execution started but backend id not yet known
|
||||
* - string → active backend execution id
|
||||
*
|
||||
* Routes through the per-workflow session store. Exposed as a writable
|
||||
* computed so existing test setups that mutate via `createTestingPinia`
|
||||
* continue to work.
|
||||
*/
|
||||
const activeExecutionId = computed<string | null | undefined>({
|
||||
get: () => currentExecutionStateStore.value.activeExecutionId,
|
||||
set: (value) => currentExecutionStateStore.value.setActiveExecutionId(value),
|
||||
});
|
||||
const readonlyActiveExecutionId = computed(
|
||||
() => currentExecutionStateStore.value.activeExecutionId,
|
||||
);
|
||||
const readonlyPreviousExecutionId = computed(
|
||||
() => currentExecutionStateStore.value.previousExecutionId,
|
||||
);
|
||||
|
||||
function setActiveExecutionId(id: string | null | undefined) {
|
||||
currentExecutionStateStore.value.setActiveExecutionId(id);
|
||||
}
|
||||
|
||||
function getWorkflowResultDataByNodeName(nodeName: string): ITaskData[] | null {
|
||||
if (getWorkflowRunData.value === null) {
|
||||
return null;
|
||||
|
|
@ -420,18 +351,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
currentExecutionStateStore.value.clearActiveExecutionStartedData();
|
||||
}
|
||||
|
||||
function setExecutionWaitingForWebhook(value: boolean): void {
|
||||
currentExecutionStateStore.value.setExecutionWaitingForWebhook(value);
|
||||
}
|
||||
|
||||
function setIsInDebugMode(value: boolean): void {
|
||||
currentExecutionStateStore.value.setIsInDebugMode(value);
|
||||
}
|
||||
|
||||
function setChatPartialExecutionDestinationNode(value: string | null): void {
|
||||
currentExecutionStateStore.value.setChatPartialExecutionDestinationNode(value);
|
||||
}
|
||||
|
||||
function setLastSuccessfulExecution(execution: IExecutionResponse | null): void {
|
||||
currentExecutionStateStore.value.setLastSuccessfulExecution(execution);
|
||||
}
|
||||
|
|
@ -731,41 +650,20 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
return url.toString();
|
||||
}
|
||||
|
||||
function resetChatMessages(): void {
|
||||
currentExecutionStateStore.value.resetChatMessages();
|
||||
}
|
||||
|
||||
function appendChatMessage(message: string): void {
|
||||
currentExecutionStateStore.value.appendChatMessage(message);
|
||||
}
|
||||
|
||||
function setSelectedTriggerNodeName(value: string | undefined) {
|
||||
currentExecutionStateStore.value.setSelectedTriggerNodeName(value);
|
||||
}
|
||||
|
||||
return {
|
||||
currentWorkflowExecutions,
|
||||
workflowExecutionData,
|
||||
workflowExecutionPairedItemMappings,
|
||||
workflowExecutionResultDataLastUpdate,
|
||||
workflowExecutionStartedData,
|
||||
activeExecutionId: readonlyActiveExecutionId,
|
||||
previousExecutionId: readonlyPreviousExecutionId,
|
||||
executionWaitingForWebhook,
|
||||
isInDebugMode,
|
||||
chatMessages,
|
||||
chatPartialExecutionDestinationNode,
|
||||
workflowId,
|
||||
isNewWorkflow,
|
||||
isWorkflowSaved,
|
||||
getWorkflowRunData,
|
||||
getWorkflowResultDataByNodeName,
|
||||
isWorkflowRunning,
|
||||
executedNode,
|
||||
getAllLoadedFinishedExecutions,
|
||||
getWorkflowExecution,
|
||||
getPastChatMessages,
|
||||
selectedTriggerNodeName,
|
||||
getExecutionDataById,
|
||||
convertTemplateNodeToNodeUi,
|
||||
getWorkflowFromUrl,
|
||||
|
|
@ -782,9 +680,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
setWorkflowExecutionRunData,
|
||||
setWorkflowExecutionData,
|
||||
clearExecutionStartedData,
|
||||
setExecutionWaitingForWebhook,
|
||||
setIsInDebugMode,
|
||||
setChatPartialExecutionDestinationNode,
|
||||
setLastSuccessfulExecution,
|
||||
clearCurrentWorkflowExecutions,
|
||||
setCurrentWorkflowExecutions,
|
||||
|
|
@ -803,17 +698,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
deleteExecution,
|
||||
addToCurrentExecutions,
|
||||
getBinaryUrl,
|
||||
resetChatMessages,
|
||||
appendChatMessage,
|
||||
getPartialIdForNode,
|
||||
setSelectedTriggerNodeName,
|
||||
fetchLastSuccessfulExecution,
|
||||
lastSuccessfulExecution,
|
||||
canViewWorkflows,
|
||||
// This is exposed to ease the refactoring to the injected workflowState composable
|
||||
// Please do not use outside this context
|
||||
private: {
|
||||
setActiveExecutionId,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from '../stores/workflowDocument.store';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { useWorkflowsStore } from '../stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '../stores/workflowExecutionState.store';
|
||||
import { useNodeTypesStore } from '../stores/nodeTypes.store';
|
||||
import { renderComponent } from '@/__tests__/render';
|
||||
import NodeView from './NodeView.vue';
|
||||
|
|
@ -31,11 +32,14 @@ vi.mock('vue-router', () => ({
|
|||
describe('NodeView', () => {
|
||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||
let workflowDocumentStore: ReturnType<typeof useWorkflowDocumentStore>;
|
||||
let workflowExecutionState: ReturnType<typeof useWorkflowExecutionStateStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
workflowsStore = useWorkflowsStore();
|
||||
workflowsStore.setWorkflowId('w0');
|
||||
workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId('w0'));
|
||||
workflowExecutionState = useWorkflowExecutionStateStore(createWorkflowDocumentId('w0'));
|
||||
});
|
||||
|
||||
describe('Trigger node selection', () => {
|
||||
|
|
@ -75,18 +79,18 @@ describe('NodeView', () => {
|
|||
|
||||
it('should select newly added trigger node automatically', async () => {
|
||||
renderNodeView();
|
||||
await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n0'));
|
||||
await waitFor(() => expect(workflowExecutionState.selectedTriggerNodeName).toBe('n0'));
|
||||
workflowDocumentStore.addNode(n2);
|
||||
await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n2'));
|
||||
await waitFor(() => expect(workflowExecutionState.selectedTriggerNodeName).toBe('n2'));
|
||||
});
|
||||
|
||||
it('should re-select a trigger when selected trigger gets disabled or removed', async () => {
|
||||
renderNodeView();
|
||||
await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n0'));
|
||||
await waitFor(() => expect(workflowExecutionState.selectedTriggerNodeName).toBe('n0'));
|
||||
useWorkflowDocumentStore(
|
||||
createWorkflowDocumentId(workflowDocumentStore.workflowId),
|
||||
).removeNode(n0);
|
||||
await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n1'));
|
||||
await waitFor(() => expect(workflowExecutionState.selectedTriggerNodeName).toBe('n1'));
|
||||
useWorkflowDocumentStore(
|
||||
createWorkflowDocumentId(workflowDocumentStore.workflowId),
|
||||
).setNodeValue({
|
||||
|
|
@ -94,7 +98,7 @@ describe('NodeView', () => {
|
|||
key: 'disabled',
|
||||
value: true,
|
||||
});
|
||||
await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe(undefined));
|
||||
await waitFor(() => expect(workflowExecutionState.selectedTriggerNodeName).toBe(undefined));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { useUIStore } from '@/app/stores/ui.store';
|
|||
import CanvasRunWorkflowButton from '@/features/workflows/canvas/components/elements/buttons/CanvasRunWorkflowButton.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useWorkflowsListStore } from '@/app/stores/workflowsList.store';
|
||||
import { useRunWorkflow } from '@/app/composables/useRunWorkflow';
|
||||
import { useGlobalLinkActions } from '@/app/composables/useGlobalLinkActions';
|
||||
|
|
@ -183,6 +184,10 @@ const clipboard = useClipboard({ onPaste: onClipboardPaste });
|
|||
const nodeTypesStore = useNodeTypesStore();
|
||||
const uiStore = useUIStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionState = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const workflowsListStore = useWorkflowsListStore();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
|
|
@ -266,7 +271,6 @@ const readOnlyNotification = ref<null | { visible: boolean }>(null);
|
|||
const fallbackNodes = ref<INodeUi[]>([]);
|
||||
|
||||
const workflowId = useInjectWorkflowId();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const routeNodeId = computed(() => {
|
||||
const nodeId = route.params.nodeId;
|
||||
return Array.isArray(nodeId) ? nodeId[0] : nodeId;
|
||||
|
|
@ -426,7 +430,8 @@ const selectableTriggerNodes = computed(() =>
|
|||
);
|
||||
const isRunButtonSplit = computed(() => {
|
||||
return (
|
||||
selectableTriggerNodes.value.length > 1 && workflowsStore.selectedTriggerNodeName !== undefined
|
||||
selectableTriggerNodes.value.length > 1 &&
|
||||
workflowExecutionState.value.selectedTriggerNodeName !== undefined
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1060,8 +1065,10 @@ const projectPermissions = computed(() => {
|
|||
|
||||
const isStoppingExecution = ref(false);
|
||||
|
||||
const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning);
|
||||
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
||||
const isWorkflowRunning = computed(() => workflowExecutionState.value.isWorkflowRunning);
|
||||
const isExecutionWaitingForWebhook = computed(
|
||||
() => workflowExecutionState.value.executionWaitingForWebhook,
|
||||
);
|
||||
|
||||
const isExecutionDisabled = computed(() => {
|
||||
if (
|
||||
|
|
@ -1734,15 +1741,17 @@ watch(
|
|||
[selectableTriggerNodes, workflowExecutionTriggerNodeName],
|
||||
([newSelectable, currentTrigger], [oldSelectable]) => {
|
||||
if (currentTrigger !== undefined) {
|
||||
workflowsStore.setSelectedTriggerNodeName(currentTrigger);
|
||||
workflowExecutionState.value.setSelectedTriggerNodeName(currentTrigger);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
workflowsStore.selectedTriggerNodeName === undefined ||
|
||||
newSelectable.every((node) => node.name !== workflowsStore.selectedTriggerNodeName)
|
||||
workflowExecutionState.value.selectedTriggerNodeName === undefined ||
|
||||
newSelectable.every(
|
||||
(node) => node.name !== workflowExecutionState.value.selectedTriggerNodeName,
|
||||
)
|
||||
) {
|
||||
workflowsStore.setSelectedTriggerNodeName(
|
||||
workflowExecutionState.value.setSelectedTriggerNodeName(
|
||||
findTriggerNodeToAutoSelect(selectableTriggerNodes.value, nodeTypesStore.getNodeType)?.name,
|
||||
);
|
||||
return;
|
||||
|
|
@ -1754,7 +1763,7 @@ watch(
|
|||
|
||||
if (newTrigger !== undefined) {
|
||||
// Select newly added node
|
||||
workflowsStore.setSelectedTriggerNodeName(newTrigger.name);
|
||||
workflowExecutionState.value.setSelectedTriggerNodeName(newTrigger.name);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
|
@ -1946,12 +1955,12 @@ onBeforeUnmount(() => {
|
|||
:executing="isWorkflowRunning"
|
||||
:trigger-nodes="triggerNodes"
|
||||
:get-node-type="nodeTypesStore.getNodeType"
|
||||
:selected-trigger-node-name="workflowsStore.selectedTriggerNodeName"
|
||||
:selected-trigger-node-name="workflowExecutionState.selectedTriggerNodeName"
|
||||
:embedded="isDemoRoute"
|
||||
@mouseenter="onRunWorkflowButtonMouseEnter"
|
||||
@mouseleave="onRunWorkflowButtonMouseLeave"
|
||||
@execute="runEntireWorkflow('main')"
|
||||
@select-trigger-node="workflowsStore.setSelectedTriggerNodeName"
|
||||
@select-trigger-node="workflowExecutionState.setSelectedTriggerNodeName"
|
||||
/>
|
||||
<template v-if="containsChatTriggerNodes">
|
||||
<CanvasChatButton
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { useRoute } from 'vue-router';
|
|||
import { useSettingsStore } from '@/app/stores/settings.store';
|
||||
import { assert } from '@n8n/utils/assert';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import type { ICredentialType, NodeError, INode } from 'n8n-workflow';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/app/composables/useTelemetry';
|
||||
|
|
@ -197,7 +198,8 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
|
||||
return (
|
||||
chatSessionTask.value === 'error' &&
|
||||
workflowsStore.activeExecutionId === currentSessionActiveExecutionId.value &&
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId(workflowsStore.workflowId))
|
||||
.activeExecutionId === currentSessionActiveExecutionId.value &&
|
||||
targetNode === chatSessionError.value?.node.name
|
||||
);
|
||||
}
|
||||
|
|
@ -492,8 +494,11 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
chatSessionError.value = context;
|
||||
currentSessionWorkflowId.value = workflowId;
|
||||
|
||||
if (workflowsStore.activeExecutionId) {
|
||||
currentSessionActiveExecutionId.value = workflowsStore.activeExecutionId;
|
||||
const activeExecutionId = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
).activeExecutionId;
|
||||
if (activeExecutionId) {
|
||||
currentSessionActiveExecutionId.value = activeExecutionId;
|
||||
}
|
||||
|
||||
const { authType, nodeInputData, schemas } = assistantHelpers.getNodeInfoForAssistant(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { useInjectWorkflowId } from '@/app/composables/useInjectWorkflowId';
|
|||
import { useTelemetry } from '@/app/composables/useTelemetry';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
|
|
@ -533,7 +534,9 @@ async function onExecuteWithMockData() {
|
|||
});
|
||||
|
||||
await runWorkflow({
|
||||
triggerNode: workflowsStore.selectedTriggerNodeName ?? triggerNode?.name,
|
||||
triggerNode:
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId)
|
||||
.selectedTriggerNodeName ?? triggerNode?.name,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import type { INodeUi } from '@/Interface';
|
|||
import ExecuteMessage from './ExecuteMessage.vue';
|
||||
import { CHAT_TRIGGER_NODE_TYPE, SETUP_CREDENTIALS_MODAL_KEY } from '@/app/constants';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
|
|
@ -136,16 +137,19 @@ describe('ExecuteMessage', () => {
|
|||
Object.defineProperty(workflowsStore, 'workflowExecutionData', {
|
||||
get: () => workflowExecutionDataRef,
|
||||
});
|
||||
Object.defineProperty(workflowsStore, 'executionWaitingForWebhook', {
|
||||
const workflowExecutionState = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('test-workflow'),
|
||||
);
|
||||
Object.defineProperty(workflowExecutionState, 'executionWaitingForWebhook', {
|
||||
get: () => executionWaitingForWebhookRef.value,
|
||||
set: (value: boolean) => {
|
||||
executionWaitingForWebhookRef.value = value;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(workflowsStore, 'selectedTriggerNodeName', {
|
||||
Object.defineProperty(workflowExecutionState, 'selectedTriggerNodeName', {
|
||||
get: () => selectedTriggerNodeNameRef.value,
|
||||
});
|
||||
workflowsStore.setSelectedTriggerNodeName = vi.fn((name: string | undefined) => {
|
||||
workflowExecutionState.setSelectedTriggerNodeName = vi.fn((name: string | undefined) => {
|
||||
selectedTriggerNodeNameRef.value = name;
|
||||
});
|
||||
logsStore.toggleOpen = vi.fn();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- eslint-disable import-x/extensions -->
|
||||
<script setup lang="ts">
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
|
|
@ -33,11 +33,13 @@ interface Emits {
|
|||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowId = useInjectWorkflowId();
|
||||
const workflowDocumentStore = computed(() =>
|
||||
useWorkflowDocumentStore(createWorkflowDocumentId(workflowId.value)),
|
||||
);
|
||||
const workflowExecutionState = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const uiStore = useUIStore();
|
||||
const i18n = useI18n();
|
||||
|
|
@ -304,9 +306,9 @@ watch(hasValidationIssues, (hasIssues, hadIssues) => {
|
|||
size="medium"
|
||||
:trigger-nodes="availableTriggerNodes"
|
||||
:get-node-type="nodeTypesStore.getNodeType"
|
||||
:selected-trigger-node-name="workflowsStore.selectedTriggerNodeName"
|
||||
:selected-trigger-node-name="workflowExecutionState.selectedTriggerNodeName"
|
||||
@execute="onExecute"
|
||||
@select-trigger-node="workflowsStore.setSelectedTriggerNodeName"
|
||||
@select-trigger-node="workflowExecutionState.setSelectedTriggerNodeName"
|
||||
/>
|
||||
</N8nTooltip>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useRouter } from 'vue-router';
|
|||
import { useI18n } from '@n8n/i18n';
|
||||
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { useLogsStore } from '@/app/stores/logs.store';
|
||||
import { useRunWorkflow } from '@/app/composables/useRunWorkflow';
|
||||
|
|
@ -23,6 +24,9 @@ export function useBuilderExecution(isReady: ComputedRef<boolean>) {
|
|||
const i18n = useI18n();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionState = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const logsStore = useLogsStore();
|
||||
const toast = useToast();
|
||||
|
|
@ -40,8 +44,10 @@ export function useBuilderExecution(isReady: ComputedRef<boolean>) {
|
|||
!isReady.value ? i18n.baseText('aiAssistant.builder.executeMessage.validationTooltip') : '',
|
||||
);
|
||||
|
||||
const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning);
|
||||
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
||||
const isWorkflowRunning = computed(() => workflowExecutionState.value.isWorkflowRunning);
|
||||
const isExecutionWaitingForWebhook = computed(
|
||||
() => workflowExecutionState.value.executionWaitingForWebhook,
|
||||
);
|
||||
|
||||
// --- Execution watcher ---
|
||||
let executionWatcherStop: (() => void) | undefined;
|
||||
|
|
@ -78,7 +84,7 @@ export function useBuilderExecution(isReady: ComputedRef<boolean>) {
|
|||
if (!isReady.value) return false;
|
||||
|
||||
const selectedTriggerNode =
|
||||
workflowsStore.selectedTriggerNodeName ?? availableTriggerNodes.value[0]?.name;
|
||||
workflowExecutionState.value.selectedTriggerNodeName ?? availableTriggerNodes.value[0]?.name;
|
||||
const selectedTriggerNodeType = selectedTriggerNode
|
||||
? workflowDocumentStore.value?.getNodeByName(selectedTriggerNode)
|
||||
: null;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
|
|||
import { useSettingsStore } from '@/app/stores/settings.store';
|
||||
import { useCredentialsStore } from '@/features/credentials/credentials.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
|
|
@ -583,7 +584,9 @@ export const useChatStore = defineStore(STORES.CHAT_HUB, () => {
|
|||
});
|
||||
|
||||
// Signal canvas that an execution is pending (null = waiting for execution ID)
|
||||
workflowsStore.private.setActiveExecutionId(null);
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
).setActiveExecutionId(null);
|
||||
}
|
||||
|
||||
async function sendMessage(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ import type { InstanceAiAgentNode } from '@n8n/api-types';
|
|||
import WorkflowCanvasHost from '@/app/components/WorkflowCanvasHost.vue';
|
||||
import { EditorExternalReadOnlyKey } from '@/app/constants/injectionKeys';
|
||||
import { usePushConnectionStore } from '@/app/stores/pushConnection.store';
|
||||
import {
|
||||
createWorkflowExecutionStateId,
|
||||
useWorkflowExecutionStateStore,
|
||||
} from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createExecutionDataId, useExecutionDataStore } from '@/app/stores/executionData.store';
|
||||
import type { FixWithAiError } from '../fixWithAi';
|
||||
import { useThread } from '../instanceAi.store';
|
||||
|
|
@ -59,9 +57,9 @@ const pushStore = usePushConnectionStore();
|
|||
const removeExecutionStartedListener = pushStore.addEventListener((event) => {
|
||||
if (event.type !== 'executionStarted') return;
|
||||
if (event.data.workflowId !== props.workflowId) return;
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowExecutionStateId(props.workflowId),
|
||||
).setActiveExecutionId(null);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId(props.workflowId)).setActiveExecutionId(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
// On executionFinished with errors, surface a structured failures report so
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import { EnterpriseEditionFeature, MODAL_CONFIRM, VIEWS } from '@/app/constants'
|
|||
import { DEBUG_PAYWALL_MODAL_KEY } from '../executions.constants';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
createWorkflowDocumentId,
|
||||
injectWorkflowDocumentStore,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import { useSettingsStore } from '@/app/stores/settings.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useTelemetry } from '@/app/composables/useTelemetry';
|
||||
|
|
@ -175,7 +179,9 @@ export const useExecutionDebugging = (providedWorkflowState?: WorkflowState) =>
|
|||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
workflowsStore.setIsInDebugMode(false);
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
).setIsInDebugMode(false);
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ vi.mock('@n8n/stores/useRootStore', () => ({
|
|||
|
||||
vi.mock('@/app/stores/workflows.store', () => ({
|
||||
useWorkflowsStore: () => ({
|
||||
workflowId: 'test-workflow',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/app/stores/workflowExecutionState.store', () => ({
|
||||
useWorkflowExecutionStateStore: () => ({
|
||||
activeExecutionId: '123',
|
||||
}),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import {
|
|||
WORKFLOW_TRIGGER_NODE_TYPE,
|
||||
} from '@/app/constants';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { i18n } from '@n8n/i18n';
|
||||
import { h } from 'vue';
|
||||
|
|
@ -196,7 +198,9 @@ export const waitingNodeTooltip = (
|
|||
node.type,
|
||||
)?.waitingNodeTooltip;
|
||||
if (waitingNodeTooltipFromNodeType) {
|
||||
const activeExecutionId = useWorkflowsStore().activeExecutionId as string;
|
||||
const activeExecutionId = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(useWorkflowsStore().workflowId),
|
||||
).activeExecutionId as string;
|
||||
// Use signed URLs from metadata if available
|
||||
// otherwise fall back to constructing URLs without token
|
||||
const additionalData: IWorkflowDataProxyAdditionalKeys = {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { setActivePinia } from 'pinia';
|
|||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { useChatState } from './useChatState';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { useLogsStore } from '@/app/stores/logs.store';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
|
|
@ -384,8 +385,11 @@ describe('useChatState', () => {
|
|||
expect(chatState.webhookRegistered.value).toBe(true);
|
||||
});
|
||||
|
||||
it('should include destinationNode when set in workflowsStore', async () => {
|
||||
workflowsStore.setChatPartialExecutionDestinationNode('DestinationNode');
|
||||
it('should include destinationNode when set in workflowExecutionState', async () => {
|
||||
const executionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('workflow-123'),
|
||||
);
|
||||
executionStateStore.setChatPartialExecutionDestinationNode('DestinationNode');
|
||||
|
||||
const chatState = useChatState(false);
|
||||
await chatState.registerChatWebhook();
|
||||
|
|
@ -399,7 +403,7 @@ describe('useChatState', () => {
|
|||
mode: 'inclusive',
|
||||
},
|
||||
});
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBeNull();
|
||||
expect(executionStateStore.chatPartialExecutionDestinationNode).toBeNull();
|
||||
});
|
||||
|
||||
it('should not register if already registering', async () => {
|
||||
|
|
@ -508,12 +512,15 @@ describe('useChatState', () => {
|
|||
});
|
||||
|
||||
it('should clear partial execution destination node', () => {
|
||||
workflowsStore.setChatPartialExecutionDestinationNode('SomeNode');
|
||||
const executionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId('workflow-123'),
|
||||
);
|
||||
executionStateStore.setChatPartialExecutionDestinationNode('SomeNode');
|
||||
|
||||
const chatState = useChatState(false);
|
||||
chatState.refreshSession();
|
||||
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBeNull();
|
||||
expect(executionStateStore.chatPartialExecutionDestinationNode).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
|
|||
import { useRunWorkflow } from '@/app/composables/useRunWorkflow';
|
||||
import { VIEWS } from '@/app/constants';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import MessageWithButtons from '@n8n/chat/components/MessageWithButtons.vue';
|
||||
|
|
@ -45,6 +46,9 @@ export function useChatState(
|
|||
const locale = useI18n();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionState = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const workflowState = injectWorkflowState();
|
||||
const rootStore = useRootStore();
|
||||
const logsStore = useLogsStore();
|
||||
|
|
@ -62,7 +66,7 @@ export function useChatState(
|
|||
// Use provided sessionId or fall back to logsStore sessionId
|
||||
const effectiveSessionId = computed(() => toValue(sessionId) ?? currentSessionId.value);
|
||||
|
||||
const previousChatMessages = computed(() => workflowsStore.getPastChatMessages);
|
||||
const previousChatMessages = computed(() => workflowExecutionState.value.getPastChatMessages);
|
||||
const chatTriggerNode = computed(
|
||||
() => workflowDocumentStore.value.allNodes.find(isChatNode) ?? null,
|
||||
);
|
||||
|
|
@ -206,13 +210,13 @@ export function useChatState(
|
|||
sessionId: effectiveSessionId.value,
|
||||
};
|
||||
|
||||
if (workflowsStore.chatPartialExecutionDestinationNode) {
|
||||
if (workflowExecutionState.value.chatPartialExecutionDestinationNode) {
|
||||
runWorkflowOptions.destinationNode = {
|
||||
nodeName: workflowsStore.chatPartialExecutionDestinationNode,
|
||||
nodeName: workflowExecutionState.value.chatPartialExecutionDestinationNode,
|
||||
mode: 'inclusive',
|
||||
};
|
||||
// Clear after use so subsequent messages run full workflow
|
||||
workflowsStore.setChatPartialExecutionDestinationNode(null);
|
||||
workflowExecutionState.value.setChatPartialExecutionDestinationNode(null);
|
||||
}
|
||||
|
||||
const response = await runWorkflow(runWorkflowOptions);
|
||||
|
|
@ -332,7 +336,7 @@ export function useChatState(
|
|||
logsStore.resetChatSessionId();
|
||||
logsStore.resetMessages();
|
||||
// Clear partial execution destination to allow full workflow execution
|
||||
workflowsStore.setChatPartialExecutionDestinationNode(null);
|
||||
workflowExecutionState.value.setChatPartialExecutionDestinationNode(null);
|
||||
|
||||
if (logsStore.isOpen) {
|
||||
chatEventBus.emit('focusInput');
|
||||
|
|
|
|||
|
|
@ -4,14 +4,18 @@ import { computed } from 'vue';
|
|||
import { useRoute } from 'vue-router';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
|
||||
export function useClearExecutionButtonVisible() {
|
||||
const route = useRoute();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const workflowExecutionData = computed(() => workflowsStore.workflowExecutionData);
|
||||
const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning);
|
||||
const isWorkflowRunning = computed(() => workflowExecutionStateStore.value.isWorkflowRunning);
|
||||
const isReadOnlyRoute = computed(() => !!route?.meta?.readOnlyCanvas);
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const isReadOnlyEnvironment = computed(() => sourceControlStore.preferences.branchReadOnly);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import { watch, computed, ref, type ComputedRef } from 'vue';
|
|||
import type { IExecutionResponse } from '@/features/execution/executions/executions.types';
|
||||
import { Workflow, type IRunExecutionData, type ITaskStartedData } from 'n8n-workflow';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import {
|
||||
createWorkflowDocumentId,
|
||||
injectWorkflowDocumentStore,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
|
||||
import {
|
||||
copyExecutionData,
|
||||
|
|
@ -108,7 +112,9 @@ export function useLogsExecutionData({ isEnabled, filter }: UseLogsExecutionData
|
|||
workflowState.setWorkflowExecutionData(null);
|
||||
nodeHelpers.updateNodesExecutionIssues();
|
||||
// Clear partial execution destination to allow full workflow execution
|
||||
workflowsStore.setChatPartialExecutionDestinationNode(null);
|
||||
useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
).setChatPartialExecutionDestinationNode(null);
|
||||
void workflowsStore.fetchLastSuccessfulExecution();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { useUIStore } from '@/app/stores/ui.store';
|
|||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { waitingNodeTooltip } from '@/features/execution/executions/executions.utils';
|
||||
import { useExecutionRedaction } from '@/features/execution/executions/composables/useExecutionRedaction';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
|
|
@ -108,6 +109,9 @@ const workflowId = useInjectWorkflowId();
|
|||
const nodeTypesStore = useNodeTypesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const workflowState = injectWorkflowState();
|
||||
const router = useRouter();
|
||||
const { runWorkflow } = useRunWorkflow({ router });
|
||||
|
|
@ -194,7 +198,7 @@ const isMappingEnabled = computed(() => {
|
|||
return true;
|
||||
});
|
||||
const isExecutingPrevious = computed(() => {
|
||||
if (!workflowsStore.isWorkflowRunning) {
|
||||
if (!workflowExecutionStateStore.value.isWorkflowRunning) {
|
||||
return false;
|
||||
}
|
||||
const triggeredNode = workflowsStore.executedNode;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { injectWorkflowState } from '@/app/composables/useWorkflowState';
|
|||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/app/constants';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
// Types
|
||||
|
||||
type RunDataRef = InstanceType<typeof RunData>;
|
||||
|
|
@ -82,6 +83,9 @@ const nodeTypesStore = useNodeTypesStore();
|
|||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowState = injectWorkflowState();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const activeNode = computed(() => ndvStore.value.activeNode);
|
||||
|
|
@ -162,7 +166,7 @@ const isNodeRunning = computed(() => {
|
|||
);
|
||||
});
|
||||
|
||||
const workflowRunning = computed(() => workflowsStore.isWorkflowRunning);
|
||||
const workflowRunning = computed(() => workflowExecutionStateStore.value.isWorkflowRunning);
|
||||
|
||||
const runTaskData = computed(() => {
|
||||
if (!node.value || workflowExecution.value === null) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { mockedStore, type MockedStore } from '@/__tests__/utils';
|
|||
import TriggerPanel from './TriggerPanel.vue';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createTestNode, mockNodeTypeDescription } from '@/__tests__/mocks';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { setActivePinia } from 'pinia';
|
||||
|
|
@ -65,7 +66,9 @@ describe('TriggerPanel.vue', () => {
|
|||
});
|
||||
|
||||
it('renders listening state for webhook node', () => {
|
||||
workflowsStore.setExecutionWaitingForWebhook(true);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')).setExecutionWaitingForWebhook(
|
||||
true,
|
||||
);
|
||||
workflowsStore.executedNode = 'Webhook';
|
||||
const { getByTestId } = renderComponent(TriggerPanel, {
|
||||
props: { nodeName: 'Webhook' },
|
||||
|
|
@ -79,7 +82,9 @@ describe('TriggerPanel.vue', () => {
|
|||
});
|
||||
|
||||
it('does not render listening state for other nodes', () => {
|
||||
workflowsStore.setExecutionWaitingForWebhook(true);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')).setExecutionWaitingForWebhook(
|
||||
true,
|
||||
);
|
||||
workflowsStore.executedNode = 'OtherNode';
|
||||
const { queryByTestId } = renderComponent(TriggerPanel, {
|
||||
props: { nodeName: 'Webhook' },
|
||||
|
|
@ -93,7 +98,9 @@ describe('TriggerPanel.vue', () => {
|
|||
});
|
||||
|
||||
it('renders listening state when executedNode is a child of the current node', () => {
|
||||
workflowsStore.setExecutionWaitingForWebhook(true);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')).setExecutionWaitingForWebhook(
|
||||
true,
|
||||
);
|
||||
workflowsStore.executedNode = 'ChildNode';
|
||||
vi.spyOn(workflowDocStore, 'getParentNodes').mockReturnValue(['Webhook']);
|
||||
const { getByTestId } = renderComponent(TriggerPanel, {
|
||||
|
|
@ -108,7 +115,9 @@ describe('TriggerPanel.vue', () => {
|
|||
});
|
||||
|
||||
it('does not render listening state when executedNode is not a child or current node', () => {
|
||||
workflowsStore.setExecutionWaitingForWebhook(true);
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('1')).setExecutionWaitingForWebhook(
|
||||
true,
|
||||
);
|
||||
workflowsStore.executedNode = 'UnrelatedNode';
|
||||
const { queryByTestId } = renderComponent(TriggerPanel, {
|
||||
props: { nodeName: 'Webhook' },
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import CopyInput from '@/app/components/CopyInput.vue';
|
|||
import NodeIcon from '@/app/components/NodeIcon.vue';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectNDVStore } from '@/features/ndv/shared/ndv.store';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
|
|
@ -56,6 +57,9 @@ const nodesTypeStore = useNodeTypesStore();
|
|||
const uiStore = useUIStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const ndvStore = injectNDVStore();
|
||||
|
||||
const router = useRouter();
|
||||
|
|
@ -171,7 +175,7 @@ const isListeningForEvents = computed(() => {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!workflowsStore.executionWaitingForWebhook) {
|
||||
if (!workflowExecutionStateStore.value.executionWaitingForWebhook) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +188,7 @@ const isListeningForEvents = computed(() => {
|
|||
return !executedNode || isCurrentNodeExecuted || isChildNodeExecuted;
|
||||
});
|
||||
|
||||
const workflowRunning = computed(() => workflowsStore.isWorkflowRunning);
|
||||
const workflowRunning = computed(() => workflowExecutionStateStore.value.isWorkflowRunning);
|
||||
|
||||
const isActivelyPolling = computed(() => {
|
||||
const triggeredNode = workflowsStore.executedNode;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import type { DataPinningDiscoveryEvent } from '@/app/event-bus';
|
|||
import { dataPinningEventBus } from '@/app/event-bus';
|
||||
import { ndvEventBus } from '@/features/ndv/shared/ndv.eventBus';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { injectNDVStore } from '@/features/ndv/shared/ndv.store';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
|
|
@ -70,6 +71,9 @@ const pinnedData = usePinnedData(activeNode);
|
|||
const nodeTypesStore = useNodeTypesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const deviceSupport = useDeviceSupport();
|
||||
const workflowId = useInjectWorkflowId();
|
||||
const telemetry = useTelemetry();
|
||||
|
|
@ -113,8 +117,8 @@ const showTriggerWaitingWarning = computed(
|
|||
triggerWaitingWarningEnabled.value &&
|
||||
!!activeNodeType.value &&
|
||||
!activeNodeType.value.group.includes('trigger') &&
|
||||
workflowsStore.isWorkflowRunning &&
|
||||
workflowsStore.executionWaitingForWebhook,
|
||||
workflowExecutionStateStore.value.isWorkflowRunning &&
|
||||
workflowExecutionStateStore.value.executionWaitingForWebhook,
|
||||
);
|
||||
|
||||
const workflowRunData = computed(() => {
|
||||
|
|
@ -314,10 +318,12 @@ const featureRequestUrl = computed(() => {
|
|||
|
||||
const outputPanelEditMode = computed(() => ndvStore.value.outputPanelEditMode);
|
||||
|
||||
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
||||
const isExecutionWaitingForWebhook = computed(
|
||||
() => workflowExecutionStateStore.value.executionWaitingForWebhook,
|
||||
);
|
||||
|
||||
const blockUi = computed(
|
||||
() => workflowsStore.isWorkflowRunning || isExecutionWaitingForWebhook.value,
|
||||
() => workflowExecutionStateStore.value.isWorkflowRunning || isExecutionWaitingForWebhook.value,
|
||||
);
|
||||
|
||||
const foreignCredentials = computed(() =>
|
||||
|
|
@ -430,7 +436,7 @@ const onUnlinkRun = (pane: string) => {
|
|||
|
||||
const onNodeExecute = () => {
|
||||
setTimeout(() => {
|
||||
if (!activeNode.value || !workflowsStore.isWorkflowRunning) {
|
||||
if (!activeNode.value || !workflowExecutionStateStore.value.isWorkflowRunning) {
|
||||
return;
|
||||
}
|
||||
triggerWaitingWarningEnabled.value = true;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { injectNDVStore } from '../ndv.store';
|
|||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
|
|
@ -302,7 +303,11 @@ const outputPanelEditMode = computed(() => ndvStore.value.outputPanelEditMode);
|
|||
|
||||
const isWorkflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
||||
|
||||
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
||||
const isExecutionWaitingForWebhook = computed(
|
||||
() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId)
|
||||
.executionWaitingForWebhook,
|
||||
);
|
||||
|
||||
const blockUi = computed(() => isWorkflowRunning.value || isExecutionWaitingForWebhook.value);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { createTestNode, mockNodeTypeDescription } from '@/__tests__/mocks';
|
|||
import { mockedStore } from '@/__tests__/utils';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { createWorkflowDocumentId } from '@/app/stores/workflowDocument.store';
|
||||
import { useLogsStore } from '@/app/stores/logs.store';
|
||||
import { CHAT_TRIGGER_NODE_TYPE } from '@/app/constants/nodeTypes';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
|
|
@ -53,6 +55,7 @@ describe('useTriggerExecution', () => {
|
|||
createTestingPinia();
|
||||
nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||
workflowsStore = mockedStore(useWorkflowsStore);
|
||||
workflowsStore.workflowId = 'test-workflow-id';
|
||||
logsStore = mockedStore(useLogsStore);
|
||||
nodeTypesStore.getNodeType = vi.fn().mockReturnValue(null);
|
||||
|
||||
|
|
@ -127,7 +130,11 @@ describe('useTriggerExecution', () => {
|
|||
}),
|
||||
);
|
||||
logsStore.isOpen = true;
|
||||
workflowsStore.chatPartialExecutionDestinationNode = 'When chat message received';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('test-workflow-id')),
|
||||
'chatPartialExecutionDestinationNode',
|
||||
'get',
|
||||
).mockReturnValue('When chat message received');
|
||||
|
||||
const node = ref<INodeUi | null>(chatNode);
|
||||
const { isInListeningState } = useTriggerExecution(node);
|
||||
|
|
@ -147,7 +154,11 @@ describe('useTriggerExecution', () => {
|
|||
}),
|
||||
);
|
||||
logsStore.isOpen = false;
|
||||
workflowsStore.chatPartialExecutionDestinationNode = 'When chat message received';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('test-workflow-id')),
|
||||
'chatPartialExecutionDestinationNode',
|
||||
'get',
|
||||
).mockReturnValue('When chat message received');
|
||||
|
||||
const node = ref<INodeUi | null>(chatNode);
|
||||
const { isInListeningState } = useTriggerExecution(node);
|
||||
|
|
@ -167,7 +178,11 @@ describe('useTriggerExecution', () => {
|
|||
}),
|
||||
);
|
||||
logsStore.isOpen = true;
|
||||
workflowsStore.chatPartialExecutionDestinationNode = 'Some Other Node';
|
||||
vi.spyOn(
|
||||
useWorkflowExecutionStateStore(createWorkflowDocumentId('test-workflow-id')),
|
||||
'chatPartialExecutionDestinationNode',
|
||||
'get',
|
||||
).mockReturnValue('Some Other Node');
|
||||
|
||||
const node = ref<INodeUi | null>(chatNode);
|
||||
const { isInListeningState } = useTriggerExecution(node);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useI18n } from '@n8n/i18n';
|
|||
import type { INodeUi } from '@/Interface';
|
||||
import { useNodeExecution, type UseNodeExecutionOptions } from '@/app/composables/useNodeExecution';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { getTriggerNodeServiceName } from '@/app/utils/nodeTypesUtils';
|
||||
import { CHAT_TRIGGER_NODE_TYPE } from '@/app/constants/nodeTypes';
|
||||
import { useLogsStore } from '@/app/stores/logs.store';
|
||||
|
|
@ -21,7 +21,6 @@ export function useTriggerExecution(
|
|||
) {
|
||||
const i18n = useI18n();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const logsStore = useLogsStore();
|
||||
|
||||
|
|
@ -50,7 +49,8 @@ export function useTriggerExecution(
|
|||
return (
|
||||
nodeType.value?.name === CHAT_TRIGGER_NODE_TYPE &&
|
||||
logsStore.isOpen &&
|
||||
workflowsStore.chatPartialExecutionDestinationNode === nodeValue.value?.name
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId)
|
||||
.chatPartialExecutionDestinationNode === nodeValue.value?.name
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useI18n } from '@n8n/i18n';
|
|||
import { useRunWorkflow } from '@/app/composables/useRunWorkflow';
|
||||
import { CHAT_TRIGGER_NODE_TYPE } from '@/app/constants';
|
||||
import { useLogsStore } from '@/app/stores/logs.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useChatHubPanelStore } from '@/features/ai/chatHub/chatHubPanel.store';
|
||||
import { computed, useCssModule } from 'vue';
|
||||
|
|
@ -41,8 +41,10 @@ const containerClass = computed(() => ({
|
|||
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const logsStore = useLogsStore();
|
||||
const chatHubPanelStore = useChatHubPanelStore();
|
||||
const { runEntireWorkflow } = useRunWorkflow({ router });
|
||||
|
|
@ -62,7 +64,7 @@ const isChatHubOpen = computed(() => chatHubPanelStore.isOpen);
|
|||
const isChatOpen = computed(() =>
|
||||
isChatHubAvailable.value ? isChatHubOpen.value : logsStore.isOpen,
|
||||
);
|
||||
const isExecuting = computed(() => workflowsStore.isWorkflowRunning);
|
||||
const isExecuting = computed(() => workflowExecutionStateStore.value.isWorkflowRunning);
|
||||
const testId = computed(() => `execute-workflow-button-${name}`);
|
||||
|
||||
function openChat() {
|
||||
|
|
@ -82,7 +84,7 @@ function closeChat() {
|
|||
}
|
||||
|
||||
async function handleClickExecute() {
|
||||
workflowsStore.setSelectedTriggerNodeName(name);
|
||||
workflowExecutionStateStore.value.setSelectedTriggerNodeName(name);
|
||||
await runEntireWorkflow('node', name);
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
useWorkflowDocumentStore,
|
||||
createWorkflowDocumentId,
|
||||
} from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import type { IPinData } from 'n8n-workflow';
|
||||
import {
|
||||
CanvasConnectionMode,
|
||||
|
|
@ -165,6 +166,14 @@ function setPinData(pinData: IPinData) {
|
|||
workflowDocumentStore.setPinData(pinData);
|
||||
}
|
||||
|
||||
function setWorkflowRunning(running: boolean) {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowExecutionStateStore = useWorkflowExecutionStateStore(
|
||||
createWorkflowDocumentId(workflowsStore.workflowId),
|
||||
);
|
||||
vi.spyOn(workflowExecutionStateStore, 'isWorkflowRunning', 'get').mockReturnValue(running);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the render data maps directly for tests
|
||||
* that rely on per-node inputs/outputs from the render data.
|
||||
|
|
@ -1766,7 +1775,6 @@ describe('useCanvasMapping', () => {
|
|||
|
||||
describe('nodeExecutionWaitingForNextById', () => {
|
||||
it('should be true when already executed node is waiting for next', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const node1 = createTestNode({
|
||||
name: 'Node 1',
|
||||
});
|
||||
|
|
@ -1783,7 +1791,7 @@ describe('useCanvasMapping', () => {
|
|||
|
||||
workflowState.executingNode.executingNode = [];
|
||||
workflowState.executingNode.lastAddedExecutingNode = node1.name;
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
setWorkflowRunning(true);
|
||||
|
||||
const { nodeExecutionWaitingForNextById } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
|
|
@ -1797,7 +1805,6 @@ describe('useCanvasMapping', () => {
|
|||
});
|
||||
|
||||
it('should be false when workflow is not executing', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const node1 = createTestNode({
|
||||
name: 'Node 1',
|
||||
});
|
||||
|
|
@ -1814,7 +1821,7 @@ describe('useCanvasMapping', () => {
|
|||
|
||||
workflowState.executingNode.executingNode = [];
|
||||
workflowState.executingNode.lastAddedExecutingNode = node1.name;
|
||||
workflowsStore.isWorkflowRunning = false;
|
||||
setWorkflowRunning(false);
|
||||
|
||||
const { nodeExecutionWaitingForNextById } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
|
|
@ -1828,7 +1835,6 @@ describe('useCanvasMapping', () => {
|
|||
});
|
||||
|
||||
it('should be false when there are nodes that are executing', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const node1 = createTestNode({
|
||||
name: 'Node 1',
|
||||
});
|
||||
|
|
@ -1845,7 +1851,7 @@ describe('useCanvasMapping', () => {
|
|||
|
||||
workflowState.executingNode.executingNode = [node2.name];
|
||||
workflowState.executingNode.lastAddedExecutingNode = node1.name;
|
||||
workflowsStore.isWorkflowRunning = false;
|
||||
setWorkflowRunning(false);
|
||||
|
||||
const { nodeExecutionWaitingForNextById } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
|
|
@ -1874,7 +1880,7 @@ describe('useCanvasMapping', () => {
|
|||
connections,
|
||||
});
|
||||
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
setWorkflowRunning(true);
|
||||
workflowsStore.getWorkflowRunData = {};
|
||||
setPinData({});
|
||||
|
||||
|
|
@ -1904,7 +1910,7 @@ describe('useCanvasMapping', () => {
|
|||
connections,
|
||||
});
|
||||
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
setWorkflowRunning(true);
|
||||
workflowsStore.getWorkflowRunData = {};
|
||||
setPinData({});
|
||||
|
||||
|
|
@ -1934,7 +1940,7 @@ describe('useCanvasMapping', () => {
|
|||
connections,
|
||||
});
|
||||
|
||||
workflowsStore.isWorkflowRunning = true;
|
||||
setWorkflowRunning(true);
|
||||
workflowsStore.getWorkflowRunData = {};
|
||||
setPinData({ 'Manual Trigger': [{ json: {} }] }); // Node has pinned data
|
||||
|
||||
|
|
@ -1963,7 +1969,7 @@ describe('useCanvasMapping', () => {
|
|||
connections,
|
||||
});
|
||||
|
||||
workflowsStore.isWorkflowRunning = false;
|
||||
setWorkflowRunning(false);
|
||||
workflowsStore.getWorkflowRunData = {};
|
||||
setPinData({});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useI18n } from '@n8n/i18n';
|
|||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import type { CanvasRenderData } from '../canvas.utils';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
|
@ -71,6 +72,9 @@ export function useCanvasMapping({
|
|||
const i18n = useI18n();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const workflowExecutionStateStore = computed(() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId),
|
||||
);
|
||||
const workflowState = injectWorkflowState();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
|
|
@ -197,7 +201,7 @@ export function useCanvasMapping({
|
|||
);
|
||||
|
||||
const nodeTooltipById = computed(() => {
|
||||
if (!workflowsStore.isWorkflowRunning) {
|
||||
if (!workflowExecutionStateStore.value.isWorkflowRunning) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +259,7 @@ export function useCanvasMapping({
|
|||
acc[node.id] =
|
||||
node.name === workflowState.executingNode.lastAddedExecutingNode &&
|
||||
workflowState.executingNode.executingNode.length === 0 &&
|
||||
workflowsStore.isWorkflowRunning;
|
||||
workflowExecutionStateStore.value.isWorkflowRunning;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
|
|||
import { type IUpdateInformation } from '@/Interface';
|
||||
import { injectNDVStore } from '@/features/ndv/shared/ndv.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
|
||||
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
|
||||
import { computed } from 'vue';
|
||||
|
||||
|
|
@ -22,7 +22,6 @@ const emit = defineEmits<{
|
|||
dblclickHeader: [MouseEvent];
|
||||
}>();
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowDocumentStore = injectWorkflowDocumentStore();
|
||||
const uiStore = useUIStore();
|
||||
const { renameNode } = useCanvasOperations();
|
||||
|
|
@ -34,7 +33,11 @@ const foreignCredentials = computed(() =>
|
|||
nodeHelpers.getForeignCredentialsIfSharingEnabled(activeNode.value?.credentials),
|
||||
);
|
||||
const isWorkflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
||||
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
||||
const isExecutionWaitingForWebhook = computed(
|
||||
() =>
|
||||
useWorkflowExecutionStateStore(workflowDocumentStore.value.documentId)
|
||||
.executionWaitingForWebhook,
|
||||
);
|
||||
const blockUi = computed(() => isWorkflowRunning.value || isExecutionWaitingForWebhook.value);
|
||||
|
||||
function handleValueChanged(parameterData: IUpdateInformation) {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ vi.mock('@/app/stores/workflowExecutionState.store', () => ({
|
|||
useWorkflowExecutionStateStore: () => ({
|
||||
activeExecutionIssuesByNodeName: new Map(),
|
||||
}),
|
||||
createWorkflowExecutionStateId: (id: string) => id,
|
||||
}));
|
||||
|
||||
vi.mock('@/app/stores/nodeTypes.store', () => ({
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
diff --git a/es/components/table/src/table/style-helper.mjs b/es/components/table/src/table/style-helper.mjs
|
||||
index b07e3e8184b60d293fa9755e00dbcab0a3843e70..be0ebe232c5e15e10cfbdeb5edc29a97eb339c38 100644
|
||||
--- a/es/components/table/src/table/style-helper.mjs
|
||||
+++ b/es/components/table/src/table/style-helper.mjs
|
||||
@@ -70,6 +70,11 @@ function useStyle(props, layout, store, table) {
|
||||
};
|
||||
});
|
||||
const doLayout = () => {
|
||||
+ // Guard against post-teardown firing: lodash debounce schedules doLayout
|
||||
+ // via setTimeout, which can fire after vitest tears down jsdom and deletes
|
||||
+ // requestAnimationFrame from globalThis. The bare reference below would
|
||||
+ // then throw a ReferenceError that vitest 4 promotes to a test failure.
|
||||
+ if (typeof requestAnimationFrame === 'undefined') return;
|
||||
if (shouldUpdateHeight.value) {
|
||||
layout.updateElsHeight();
|
||||
}
|
||||
diff --git a/es/hooks/use-lockscreen/index.mjs b/es/hooks/use-lockscreen/index.mjs
|
||||
index 482516a6c59f8dcf0caba62b7482f63f126c2280..82a37f344bd650e9d514397b4531c0ff36487c70 100644
|
||||
--- a/es/hooks/use-lockscreen/index.mjs
|
||||
|
|
@ -11,6 +27,22 @@ index 482516a6c59f8dcf0caba62b7482f63f126c2280..82a37f344bd650e9d514397b4531c0ff
|
|||
removeClass(document == null ? void 0 : document.body, hiddenCls.value);
|
||||
if (withoutHiddenClass && document) {
|
||||
document.body.style.width = bodyWidth;
|
||||
diff --git a/lib/components/table/src/table/style-helper.js b/lib/components/table/src/table/style-helper.js
|
||||
index dbf8c403dc7b89f9af550c2ea5047f02bc9f44c6..b80704a3eb614bcfa79da267a5910900f204e002 100644
|
||||
--- a/lib/components/table/src/table/style-helper.js
|
||||
+++ b/lib/components/table/src/table/style-helper.js
|
||||
@@ -74,6 +74,11 @@ function useStyle(props, layout, store, table) {
|
||||
};
|
||||
});
|
||||
const doLayout = () => {
|
||||
+ // Guard against post-teardown firing: lodash debounce schedules doLayout
|
||||
+ // via setTimeout, which can fire after vitest tears down jsdom and deletes
|
||||
+ // requestAnimationFrame from globalThis. The bare reference below would
|
||||
+ // then throw a ReferenceError that vitest 4 promotes to a test failure.
|
||||
+ if (typeof requestAnimationFrame === 'undefined') return;
|
||||
if (shouldUpdateHeight.value) {
|
||||
layout.updateElsHeight();
|
||||
}
|
||||
diff --git a/lib/hooks/use-lockscreen/index.js b/lib/hooks/use-lockscreen/index.js
|
||||
index ce7bd581a57cd0d7e834c42a954b48d148578ef5..496e4dc07bae546bea037cedb23ea0ee7b3a7955 100644
|
||||
--- a/lib/hooks/use-lockscreen/index.js
|
||||
|
|
|
|||
|
|
@ -523,7 +523,7 @@ patchedDependencies:
|
|||
hash: a4b6d56db16fe5870646929938466d6a5c668435fd1551bed6a93fffb597ba42
|
||||
path: patches/bull@4.16.4.patch
|
||||
element-plus@2.4.3:
|
||||
hash: 3bc4ea0a42ad52c6bbc3d06c12c2963d55b57d6b5b8d436e46e7fd8ff8c10661
|
||||
hash: fbab57fe3750e430abd5d5e7c04cbf1b6a8f9f1c9676b14c73b77d3e06ba9eee
|
||||
path: patches/element-plus@2.4.3.patch
|
||||
ics:
|
||||
hash: 163587ad2fa9bc787ed09cd5e958eace08b4aa8aaca651869e9434ba674e158d
|
||||
|
|
@ -3550,7 +3550,7 @@ importers:
|
|||
version: 2.1.1
|
||||
element-plus:
|
||||
specifier: catalog:frontend
|
||||
version: 2.4.3(patch_hash=3bc4ea0a42ad52c6bbc3d06c12c2963d55b57d6b5b8d436e46e7fd8ff8c10661)(vue@3.5.26(typescript@6.0.2))
|
||||
version: 2.4.3(patch_hash=fbab57fe3750e430abd5d5e7c04cbf1b6a8f9f1c9676b14c73b77d3e06ba9eee)(vue@3.5.26(typescript@6.0.2))
|
||||
emojibase-data:
|
||||
specifier: ^17.0.0
|
||||
version: 17.0.0(emojibase@17.0.0)
|
||||
|
|
@ -4107,7 +4107,7 @@ importers:
|
|||
version: 3.0.3
|
||||
element-plus:
|
||||
specifier: catalog:frontend
|
||||
version: 2.4.3(patch_hash=3bc4ea0a42ad52c6bbc3d06c12c2963d55b57d6b5b8d436e46e7fd8ff8c10661)(vue@3.5.26(typescript@6.0.2))
|
||||
version: 2.4.3(patch_hash=fbab57fe3750e430abd5d5e7c04cbf1b6a8f9f1c9676b14c73b77d3e06ba9eee)(vue@3.5.26(typescript@6.0.2))
|
||||
email-providers:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
|
|
@ -24209,7 +24209,7 @@ snapshots:
|
|||
'@currents/commit-info': 1.0.1-beta.0
|
||||
async-retry: 1.3.3
|
||||
axios: 1.16.1(debug@4.4.3)
|
||||
axios-retry: 4.5.0(axios@1.16.1(debug@4.4.3))
|
||||
axios-retry: 4.5.0(axios@1.16.1)
|
||||
chalk: 4.1.2
|
||||
commander: 13.1.0
|
||||
date-fns: 2.30.0
|
||||
|
|
@ -27441,7 +27441,7 @@ snapshots:
|
|||
'@rudderstack/rudder-sdk-node@3.0.5':
|
||||
dependencies:
|
||||
axios: 1.16.1(debug@4.4.3)
|
||||
axios-retry: 4.5.0(axios@1.16.1(debug@4.4.3))
|
||||
axios-retry: 4.5.0(axios@1.16.1)
|
||||
component-type: 2.0.0
|
||||
join-component: 1.1.0
|
||||
lodash.clonedeep: 4.5.0
|
||||
|
|
@ -30321,7 +30321,7 @@ snapshots:
|
|||
|
||||
axe-core@4.7.2: {}
|
||||
|
||||
axios-retry@4.5.0(axios@1.16.1(debug@4.4.3)):
|
||||
axios-retry@4.5.0(axios@1.16.1):
|
||||
dependencies:
|
||||
axios: 1.16.1(debug@4.4.3)
|
||||
is-retry-allowed: 2.2.0
|
||||
|
|
@ -32164,7 +32164,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
element-plus@2.4.3(patch_hash=3bc4ea0a42ad52c6bbc3d06c12c2963d55b57d6b5b8d436e46e7fd8ff8c10661)(vue@3.5.26(typescript@6.0.2)):
|
||||
element-plus@2.4.3(patch_hash=fbab57fe3750e430abd5d5e7c04cbf1b6a8f9f1c9676b14c73b77d3e06ba9eee)(vue@3.5.26(typescript@6.0.2)):
|
||||
dependencies:
|
||||
'@ctrl/tinycolor': 3.6.0
|
||||
'@element-plus/icons-vue': 2.3.1(vue@3.5.26(typescript@6.0.2))
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user