refactor(editor): Refactor usePushConnection and introduce new queueing system (#14529)

This commit is contained in:
Alex Grozav
2025-04-30 15:36:43 +03:00
committed by GitHub
parent 442cd094ee
commit 833d8e3c18
49 changed files with 1525 additions and 1262 deletions

View File

@@ -221,7 +221,7 @@ describe('LogsPanel', () => {
expect(rendered.getByText('Running')).toBeInTheDocument();
expect(rendered.queryByText('AI Agent')).not.toBeInTheDocument();
workflowsStore.setNodeExecuting({
workflowsStore.addNodeExecutionData({
nodeName: 'AI Agent',
executionId: '567',
data: { executionIndex: 0, startTime: Date.parse('2025-04-20T12:34:51.000Z'), source: [] },
@@ -243,7 +243,6 @@ describe('LogsPanel', () => {
executionStatus: 'success',
},
});
expect(await treeItem.findByText('AI Agent')).toBeInTheDocument();
expect(treeItem.getByText('Success in 33ms')).toBeInTheDocument();

View File

@@ -8,8 +8,8 @@ import {
START_NODE_TYPE,
} from '@/constants';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { waitingNodeTooltip } from '@/utils/executionUtils';
import { uniqBy } from 'lodash-es';
import { N8nIcon, N8nRadioButtons, N8nText, N8nTooltip } from '@n8n/design-system';
@@ -23,7 +23,6 @@ import {
} from 'n8n-workflow';
import { storeToRefs } from 'pinia';
import { computed, ref, watch } from 'vue';
import { useNDVStore } from '../stores/ndv.store';
import InputNodeSelect from './InputNodeSelect.vue';
import NodeExecuteButton from './NodeExecuteButton.vue';
import RunData from './RunData.vue';
@@ -90,7 +89,6 @@ const inputModes = [
const nodeTypesStore = useNodeTypesStore();
const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
const {
activeNode,
@@ -166,7 +164,7 @@ const isMappingEnabled = computed(() => {
return true;
});
const isExecutingPrevious = computed(() => {
if (!workflowRunning.value) {
if (!workflowsStore.isWorkflowRunning) {
return false;
}
const triggeredNode = workflowsStore.executedNode;
@@ -187,7 +185,6 @@ const isExecutingPrevious = computed(() => {
}
return false;
});
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
const rootNodesParents = computed(() => {
if (!rootNode.value) return [];

View File

@@ -31,7 +31,6 @@ import { dataPinningEventBus, ndvEventBus } from '@/event-bus';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
@@ -72,7 +71,6 @@ const { activeNode } = storeToRefs(ndvStore);
const pinnedData = usePinnedData(activeNode);
const workflowActivate = useWorkflowActivate();
const nodeTypesStore = useNodeTypesStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const settingsStore = useSettingsStore();
const deviceSupport = useDeviceSupport();
@@ -108,14 +106,12 @@ const activeNodeType = computed(() => {
return null;
});
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
const showTriggerWaitingWarning = computed(
() =>
triggerWaitingWarningEnabled.value &&
!!activeNodeType.value &&
!activeNodeType.value.group.includes('trigger') &&
workflowRunning.value &&
workflowsStore.isWorkflowRunning &&
workflowsStore.executionWaitingForWebhook,
);
@@ -327,11 +323,11 @@ const featureRequestUrl = computed(() => {
const outputPanelEditMode = computed(() => ndvStore.outputPanelEditMode);
const isWorkflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
const blockUi = computed(() => isWorkflowRunning.value || isExecutionWaitingForWebhook.value);
const blockUi = computed(
() => workflowsStore.isWorkflowRunning || isExecutionWaitingForWebhook.value,
);
const foreignCredentials = computed(() => {
const credentials = activeNode.value?.credentials;
@@ -470,7 +466,7 @@ const onUnlinkRun = (pane: string) => {
const onNodeExecute = () => {
setTimeout(() => {
if (!activeNode.value || !workflowRunning.value) {
if (!activeNode.value || !workflowsStore.isWorkflowRunning) {
return;
}
triggerWaitingWarningEnabled.value = true;

View File

@@ -15,7 +15,6 @@ import {
} from '@/constants';
import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useUIStore } from '@/stores/ui.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
@@ -84,7 +83,6 @@ vi.mock('@/composables/useMessage', () => {
let renderComponent: ReturnType<typeof createComponentRenderer>;
let workflowsStore: MockedStore<typeof useWorkflowsStore>;
let uiStore: MockedStore<typeof useUIStore>;
let nodeTypesStore: MockedStore<typeof useNodeTypesStore>;
let ndvStore: MockedStore<typeof useNDVStore>;
@@ -109,7 +107,6 @@ describe('NodeExecuteButton', () => {
});
workflowsStore = mockedStore(useWorkflowsStore);
uiStore = mockedStore(useUIStore);
nodeTypesStore = mockedStore(useNodeTypesStore);
ndvStore = mockedStore(useNDVStore);
@@ -193,7 +190,7 @@ describe('NodeExecuteButton', () => {
workflowsStore.getNodeByName.mockReturnValue(node);
workflowsStore.isNodeExecuting = vi.fn(() => true);
nodeTypesStore.isTriggerNode = () => true;
uiStore.isActionActive.workflowRunning = true;
workflowsStore.isWorkflowRunning = true;
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Stop Listening');
@@ -203,7 +200,7 @@ describe('NodeExecuteButton', () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
workflowsStore.isNodeExecuting = vi.fn(() => true);
uiStore.isActionActive.workflowRunning = true;
workflowsStore.isWorkflowRunning = true;
const { getByRole } = renderComponent();
expect(getByRole('button').querySelector('.n8n-spinner')).toBeVisible();
@@ -227,7 +224,7 @@ describe('NodeExecuteButton', () => {
});
it('should be disabled when workflow is running but node is not executing', async () => {
uiStore.isActionActive.workflowRunning = true;
workflowsStore.isWorkflowRunning = true;
workflowsStore.isNodeExecuting.mockReturnValue(false);
workflowsStore.getNodeByName.mockReturnValue(
mockNode({ name: 'test-node', type: SET_NODE_TYPE }),
@@ -277,7 +274,7 @@ describe('NodeExecuteButton', () => {
});
it('stops execution when clicking button while workflow is running', async () => {
uiStore.isActionActive.workflowRunning = true;
workflowsStore.isWorkflowRunning = true;
nodeTypesStore.isTriggerNode = () => true;
workflowsStore.setActiveExecutionId('test-execution-id');
workflowsStore.isNodeExecuting.mockReturnValue(true);

View File

@@ -22,7 +22,6 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
import { nodeViewEventBus } from '@/event-bus';
import { usePinnedData } from '@/composables/usePinnedData';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { useUIStore } from '@/stores/ui.store';
import { useRouter } from 'vue-router';
import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
@@ -72,7 +71,6 @@ const externalHooks = useExternalHooks();
const toast = useToast();
const ndvStore = useNDVStore();
const nodeTypesStore = useNodeTypesStore();
const uiStore = useUIStore();
const i18n = useI18n();
const message = useMessage();
const telemetry = useTelemetry();
@@ -85,7 +83,7 @@ const nodeType = computed((): INodeTypeDescription | null => {
});
const isNodeRunning = computed(() => {
if (!uiStore.isActionActive.workflowRunning || codeGenerationInProgress.value) return false;
if (!workflowsStore.isWorkflowRunning || codeGenerationInProgress.value) return false;
const triggeredNode = workflowsStore.executedNode;
return (
workflowsStore.isNodeExecuting(node.value?.name ?? '') || triggeredNode === node.value?.name
@@ -96,8 +94,6 @@ const isTriggerNode = computed(() => {
return node.value ? nodeTypesStore.isTriggerNode(node.value.type) : false;
});
const isWorkflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
const isManualTriggerNode = computed(() =>
nodeType.value ? nodeType.value.name === MANUAL_TRIGGER_NODE_TYPE : false,
);
@@ -168,7 +164,7 @@ const disabledHint = computed(() => {
return i18n.baseText('ndv.execute.requiredFieldsMissing');
}
if (isWorkflowRunning.value && !isNodeRunning.value) {
if (workflowsStore.isWorkflowRunning && !isNodeRunning.value) {
return i18n.baseText('ndv.execute.workflowAlreadyRunning');
}

View File

@@ -9,7 +9,6 @@ import {
import RunData from './RunData.vue';
import RunInfo from './RunInfo.vue';
import { storeToRefs } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
@@ -77,7 +76,6 @@ const emit = defineEmits<{
const ndvStore = useNDVStore();
const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
const telemetry = useTelemetry();
const i18n = useI18n();
const { activeNode } = storeToRefs(ndvStore);
@@ -144,7 +142,7 @@ const isNodeRunning = computed(() => {
return workflowRunning.value && !!node.value && workflowsStore.isNodeExecuting(node.value.name);
});
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
const workflowRunning = computed(() => workflowsStore.isWorkflowRunning);
const workflowExecution = computed(() => {
return workflowsStore.getWorkflowExecution;

View File

@@ -162,9 +162,7 @@ const isListeningForEvents = computed(() => {
);
});
const workflowRunning = computed(() => {
return uiStore.isActionActive.workflowRunning;
});
const workflowRunning = computed(() => workflowsStore.isWorkflowRunning);
const isActivelyPolling = computed(() => {
const triggeredNode = workflowsStore.executedNode;

View File

@@ -16,7 +16,7 @@ const {
hasIssues,
executionStatus,
executionWaiting,
executionRunningThrottled,
executionRunning,
hasRunData,
runDataIterations,
isDisabled,
@@ -55,6 +55,16 @@ const dirtiness = computed(() =>
<FontAwesomeIcon icon="sync-alt" spin />
</div>
</div>
<div v-else-if="executionStatus === 'unknown'">
<!-- Do nothing, unknown means the node never executed -->
</div>
<div
v-else-if="executionRunning || executionStatus === 'running'"
data-test-id="canvas-node-status-running"
:class="[$style.status, $style.running]"
>
<FontAwesomeIcon icon="sync-alt" spin />
</div>
<div
v-else-if="hasPinnedData && !nodeHelpers.isProductionExecutionPreview.value && !isDisabled"
data-test-id="canvas-node-status-pinned"
@@ -62,16 +72,6 @@ const dirtiness = computed(() =>
>
<FontAwesomeIcon icon="thumbtack" />
</div>
<div v-else-if="executionStatus === 'unknown'">
<!-- Do nothing, unknown means the node never executed -->
</div>
<div
v-else-if="executionRunningThrottled || executionStatus === 'running'"
data-test-id="canvas-node-status-running"
:class="[$style.status, $style.running]"
>
<FontAwesomeIcon icon="sync-alt" spin />
</div>
<div v-else-if="dirtiness !== undefined">
<N8nTooltip :show-after="500" placement="bottom">
<template #content>

View File

@@ -4,7 +4,6 @@ import { useCanvasOperations } from '@/composables/useCanvasOperations';
import { useI18n } from '@/composables/useI18n';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { CHAT_TRIGGER_NODE_TYPE } from '@/constants';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { computed, useCssModule } from 'vue';
import { useRouter } from 'vue-router';
@@ -36,12 +35,11 @@ const containerClass = computed(() => ({
const router = useRouter();
const i18n = useI18n();
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
const { runEntireWorkflow } = useRunWorkflow({ router });
const { toggleChatOpen } = useCanvasOperations({ router });
const isChatOpen = computed(() => workflowsStore.logsPanelState !== LOGS_PANEL_STATE.CLOSED);
const isExecuting = computed(() => uiStore.isActionActive.workflowRunning);
const isExecuting = computed(() => workflowsStore.isWorkflowRunning);
const testId = computed(() => `execute-workflow-button-${name}`);
</script>