mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 05:17:28 -08:00
fix(editor): Fix performance issues related to expressions and pinned data (#9882)
Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
parent
2885091ced
commit
13d83f2037
|
@ -61,7 +61,7 @@
|
||||||
/>
|
/>
|
||||||
<InputPanel
|
<InputPanel
|
||||||
v-else-if="!isTriggerNode"
|
v-else-if="!isTriggerNode"
|
||||||
:workflow="workflow"
|
:workflow="workflowObject"
|
||||||
:can-link-runs="canLinkRuns"
|
:can-link-runs="canLinkRuns"
|
||||||
:run-index="inputRun"
|
:run-index="inputRun"
|
||||||
:linked-runs="linked"
|
:linked-runs="linked"
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
<template #output>
|
<template #output>
|
||||||
<OutputPanel
|
<OutputPanel
|
||||||
data-test-id="output-panel"
|
data-test-id="output-panel"
|
||||||
:workflow="workflow"
|
:workflow="workflowObject"
|
||||||
:can-link-runs="canLinkRuns"
|
:can-link-runs="canLinkRuns"
|
||||||
:run-index="outputRun"
|
:run-index="outputRun"
|
||||||
:linked-runs="linked"
|
:linked-runs="linked"
|
||||||
|
@ -141,7 +141,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
import type { IRunData, ConnectionTypes } from 'n8n-workflow';
|
import type { IRunData, ConnectionTypes, Workflow } from 'n8n-workflow';
|
||||||
import { jsonParse, NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
import { jsonParse, NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
||||||
import type { IUpdateInformation, TargetItem } from '@/Interface';
|
import type { IUpdateInformation, TargetItem } from '@/Interface';
|
||||||
|
|
||||||
|
@ -171,8 +171,6 @@ import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useMessage } from '@/composables/useMessage';
|
import { useMessage } from '@/composables/useMessage';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
import { usePinnedData } from '@/composables/usePinnedData';
|
import { usePinnedData } from '@/composables/usePinnedData';
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
@ -188,6 +186,7 @@ const emit = defineEmits([
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
workflowObject: Workflow;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
renaming?: boolean;
|
renaming?: boolean;
|
||||||
isProductionExecutionPreview?: boolean;
|
isProductionExecutionPreview?: boolean;
|
||||||
|
@ -202,8 +201,6 @@ const externalHooks = useExternalHooks();
|
||||||
const nodeHelpers = useNodeHelpers();
|
const nodeHelpers = useNodeHelpers();
|
||||||
const { activeNode } = storeToRefs(ndvStore);
|
const { activeNode } = storeToRefs(ndvStore);
|
||||||
const pinnedData = usePinnedData(activeNode);
|
const pinnedData = usePinnedData(activeNode);
|
||||||
const router = useRouter();
|
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
|
||||||
const workflowActivate = useWorkflowActivate();
|
const workflowActivate = useWorkflowActivate();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
|
@ -266,12 +263,12 @@ const workflowRunData = computed(() => {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflow = computed(() => workflowHelpers.getCurrentWorkflow());
|
|
||||||
|
|
||||||
const parentNodes = computed(() => {
|
const parentNodes = computed(() => {
|
||||||
if (activeNode.value) {
|
if (activeNode.value) {
|
||||||
return (
|
return (
|
||||||
workflow.value.getParentNodesByDepth(activeNode.value.name, 1).map(({ name }) => name) || []
|
props.workflowObject
|
||||||
|
.getParentNodesByDepth(activeNode.value.name, 1)
|
||||||
|
.map(({ name }) => name) || []
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@ -374,13 +371,17 @@ const maxInputRun = computed(() => {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowNode = workflow.value.getNode(activeNode.value.name);
|
const workflowNode = props.workflowObject.getNode(activeNode.value.name);
|
||||||
|
|
||||||
if (!workflowNode || !activeNodeType.value) {
|
if (!workflowNode || !activeNodeType.value) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputs = NodeHelpers.getNodeOutputs(workflow.value, workflowNode, activeNodeType.value);
|
const outputs = NodeHelpers.getNodeOutputs(
|
||||||
|
props.workflowObject,
|
||||||
|
workflowNode,
|
||||||
|
activeNodeType.value,
|
||||||
|
);
|
||||||
|
|
||||||
let node = inputNode.value;
|
let node = inputNode.value;
|
||||||
|
|
||||||
|
@ -729,11 +730,7 @@ watch(
|
||||||
}
|
}
|
||||||
|
|
||||||
void externalHooks.run('dataDisplay.nodeTypeChanged', {
|
void externalHooks.run('dataDisplay.nodeTypeChanged', {
|
||||||
nodeSubtitle: nodeHelpers.getNodeSubtitle(
|
nodeSubtitle: nodeHelpers.getNodeSubtitle(node, activeNodeType.value, props.workflowObject),
|
||||||
node,
|
|
||||||
activeNodeType.value,
|
|
||||||
workflowHelpers.getCurrentWorkflow(),
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -37,23 +37,9 @@ async function createPiniaWithActiveNode() {
|
||||||
await useSettingsStore().getSettings();
|
await useSettingsStore().getSettings();
|
||||||
await useUsersStore().loginWithCookie();
|
await useUsersStore().loginWithCookie();
|
||||||
|
|
||||||
return pinia;
|
return { pinia, currentWorkflow: workflowsStore.getCurrentWorkflow() };
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderComponent = createComponentRenderer(NodeDetailsView, {
|
|
||||||
props: {
|
|
||||||
teleported: false,
|
|
||||||
appendToBody: false,
|
|
||||||
},
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
$route: {
|
|
||||||
name: VIEWS.WORKFLOW,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('NodeDetailsView', () => {
|
describe('NodeDetailsView', () => {
|
||||||
let server: ReturnType<typeof setupServer>;
|
let server: ReturnType<typeof setupServer>;
|
||||||
|
|
||||||
|
@ -70,8 +56,25 @@ describe('NodeDetailsView', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render correctly', async () => {
|
it('should render correctly', async () => {
|
||||||
|
const { pinia, currentWorkflow } = await createPiniaWithActiveNode();
|
||||||
|
|
||||||
|
const renderComponent = createComponentRenderer(NodeDetailsView, {
|
||||||
|
props: {
|
||||||
|
teleported: false,
|
||||||
|
appendToBody: false,
|
||||||
|
workflowObject: currentWorkflow,
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$route: {
|
||||||
|
name: VIEWS.WORKFLOW,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const wrapper = renderComponent({
|
const wrapper = renderComponent({
|
||||||
pinia: await createPiniaWithActiveNode(),
|
pinia,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
|
|
|
@ -337,6 +337,23 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateCachedWorkflow() {
|
||||||
|
const nodeTypes = getNodeTypes();
|
||||||
|
const nodes = getNodes();
|
||||||
|
const connections = allConnections.value;
|
||||||
|
|
||||||
|
cachedWorkflow = new Workflow({
|
||||||
|
id: workflowId.value,
|
||||||
|
name: workflowName.value,
|
||||||
|
nodes,
|
||||||
|
connections,
|
||||||
|
active: false,
|
||||||
|
nodeTypes,
|
||||||
|
settings: workflowSettings.value,
|
||||||
|
pinData: pinnedWorkflowData.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow {
|
function getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow {
|
||||||
const nodeTypes = getNodeTypes();
|
const nodeTypes = getNodeTypes();
|
||||||
let cachedWorkflowId: string | undefined = workflowId.value;
|
let cachedWorkflowId: string | undefined = workflowId.value;
|
||||||
|
@ -362,7 +379,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
function getCurrentWorkflow(copyData?: boolean): Workflow {
|
function getCurrentWorkflow(copyData?: boolean): Workflow {
|
||||||
const nodes = getNodes();
|
const nodes = getNodes();
|
||||||
const connections = allConnections.value;
|
const connections = allConnections.value;
|
||||||
const cacheKey = JSON.stringify({ nodes, connections, pinData: pinnedWorkflowData.value });
|
const cacheKey = JSON.stringify({ nodes, connections });
|
||||||
if (!copyData && cachedWorkflow && cacheKey === cachedWorkflowKey) {
|
if (!copyData && cachedWorkflow && cacheKey === cachedWorkflowKey) {
|
||||||
return cachedWorkflow;
|
return cachedWorkflow;
|
||||||
}
|
}
|
||||||
|
@ -641,6 +658,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
...workflow.value,
|
...workflow.value,
|
||||||
pinData: pinData || {},
|
pinData: pinData || {},
|
||||||
};
|
};
|
||||||
|
updateCachedWorkflow();
|
||||||
|
|
||||||
dataPinningEventBus.emit('pin-data', pinData || {});
|
dataPinningEventBus.emit('pin-data', pinData || {});
|
||||||
}
|
}
|
||||||
|
@ -711,6 +729,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
uiStore.stateIsDirty = true;
|
uiStore.stateIsDirty = true;
|
||||||
|
updateCachedWorkflow();
|
||||||
|
|
||||||
dataPinningEventBus.emit('pin-data', { [payload.node.name]: storedPinData });
|
dataPinningEventBus.emit('pin-data', { [payload.node.name]: storedPinData });
|
||||||
}
|
}
|
||||||
|
@ -727,6 +746,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
uiStore.stateIsDirty = true;
|
uiStore.stateIsDirty = true;
|
||||||
|
updateCachedWorkflow();
|
||||||
|
|
||||||
dataPinningEventBus.emit('unpin-data', { [payload.node.name]: undefined });
|
dataPinningEventBus.emit('unpin-data', { [payload.node.name]: undefined });
|
||||||
}
|
}
|
||||||
|
|
|
@ -709,6 +709,7 @@ onBeforeUnmount(() => {
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<NodeDetailsView
|
<NodeDetailsView
|
||||||
|
:workflow-object="editableWorkflowObject"
|
||||||
:read-only="isReadOnlyRoute || isReadOnlyEnvironment"
|
:read-only="isReadOnlyRoute || isReadOnlyEnvironment"
|
||||||
:is-production-execution-preview="isProductionExecutionPreview"
|
:is-production-execution-preview="isProductionExecutionPreview"
|
||||||
:renaming="false"
|
:renaming="false"
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NodeDetailsView
|
<NodeDetailsView
|
||||||
|
:workflow-object="currentWorkflowObject"
|
||||||
:read-only="isReadOnlyRoute || readOnlyEnv"
|
:read-only="isReadOnlyRoute || readOnlyEnv"
|
||||||
:renaming="renamingActive"
|
:renaming="renamingActive"
|
||||||
:is-production-execution-preview="isProductionExecutionPreview"
|
:is-production-execution-preview="isProductionExecutionPreview"
|
||||||
|
|
Loading…
Reference in a new issue