feat(editor): Add support for fallback nodes and new addNodes node render type in new canvas (no-changelog) (#10004)
This commit is contained in:
@@ -3,7 +3,7 @@ import type { Connection } from '@vue-flow/core';
|
||||
import type { IConnection, Workflow } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
||||
import type { CanvasElement } from '@/types';
|
||||
import type { CanvasNode } from '@/types';
|
||||
import type { ICredentialsResponse, INodeUi, IWorkflowDb, XYPosition } from '@/Interface';
|
||||
import { RemoveNodeCommand } from '@/models/history';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
@@ -76,7 +76,7 @@ describe('useCanvasOperations', () => {
|
||||
.spyOn(workflowsStore, 'setNodePositionById')
|
||||
.mockImplementation(() => {});
|
||||
const id = 'node1';
|
||||
const position: CanvasElement['position'] = { x: 10, y: 20 };
|
||||
const position: CanvasNode['position'] = { x: 10, y: 20 };
|
||||
const node = createTestNode({
|
||||
id,
|
||||
type: 'node',
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { ref } from 'vue';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { useNodeConnections } from '@/composables/useNodeConnections';
|
||||
import type { CanvasElementData } from '@/types';
|
||||
import type { CanvasNodeData } from '@/types';
|
||||
|
||||
describe('useNodeConnections', () => {
|
||||
const defaultConnections = { input: {}, output: {} };
|
||||
describe('mainInputs', () => {
|
||||
it('should return main inputs when provided with main inputs', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([
|
||||
const inputs = ref<CanvasNodeData['inputs']>([
|
||||
{ type: NodeConnectionType.Main, index: 0 },
|
||||
{ type: NodeConnectionType.Main, index: 1 },
|
||||
{ type: NodeConnectionType.Main, index: 2 },
|
||||
{ type: NodeConnectionType.AiAgent, index: 0 },
|
||||
]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([]);
|
||||
|
||||
const { mainInputs } = useNodeConnections({
|
||||
inputs,
|
||||
@@ -28,12 +28,12 @@ describe('useNodeConnections', () => {
|
||||
|
||||
describe('nonMainInputs', () => {
|
||||
it('should return non-main inputs when provided with non-main inputs', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([
|
||||
const inputs = ref<CanvasNodeData['inputs']>([
|
||||
{ type: NodeConnectionType.Main, index: 0 },
|
||||
{ type: NodeConnectionType.AiAgent, index: 0 },
|
||||
{ type: NodeConnectionType.AiAgent, index: 1 },
|
||||
]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([]);
|
||||
|
||||
const { nonMainInputs } = useNodeConnections({
|
||||
inputs,
|
||||
@@ -48,12 +48,12 @@ describe('useNodeConnections', () => {
|
||||
|
||||
describe('requiredNonMainInputs', () => {
|
||||
it('should return required non-main inputs when provided with required non-main inputs', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([
|
||||
const inputs = ref<CanvasNodeData['inputs']>([
|
||||
{ type: NodeConnectionType.Main, index: 0 },
|
||||
{ type: NodeConnectionType.AiAgent, required: true, index: 0 },
|
||||
{ type: NodeConnectionType.AiAgent, required: false, index: 1 },
|
||||
]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([]);
|
||||
|
||||
const { requiredNonMainInputs } = useNodeConnections({
|
||||
inputs,
|
||||
@@ -68,9 +68,9 @@ describe('useNodeConnections', () => {
|
||||
|
||||
describe('mainInputConnections', () => {
|
||||
it('should return main input connections when provided with main input connections', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([]);
|
||||
const connections = ref<CanvasElementData['connections']>({
|
||||
const inputs = ref<CanvasNodeData['inputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([]);
|
||||
const connections = ref<CanvasNodeData['connections']>({
|
||||
input: {
|
||||
[NodeConnectionType.Main]: [
|
||||
[{ node: 'node1', type: NodeConnectionType.Main, index: 0 }],
|
||||
@@ -93,8 +93,8 @@ describe('useNodeConnections', () => {
|
||||
|
||||
describe('mainOutputs', () => {
|
||||
it('should return main outputs when provided with main outputs', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([
|
||||
const inputs = ref<CanvasNodeData['inputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([
|
||||
{ type: NodeConnectionType.Main, index: 0 },
|
||||
{ type: NodeConnectionType.Main, index: 1 },
|
||||
{ type: NodeConnectionType.Main, index: 2 },
|
||||
@@ -114,8 +114,8 @@ describe('useNodeConnections', () => {
|
||||
|
||||
describe('nonMainOutputs', () => {
|
||||
it('should return non-main outputs when provided with non-main outputs', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([
|
||||
const inputs = ref<CanvasNodeData['inputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([
|
||||
{ type: NodeConnectionType.Main, index: 0 },
|
||||
{ type: NodeConnectionType.AiAgent, index: 0 },
|
||||
{ type: NodeConnectionType.AiAgent, index: 1 },
|
||||
@@ -134,9 +134,9 @@ describe('useNodeConnections', () => {
|
||||
|
||||
describe('mainOutputConnections', () => {
|
||||
it('should return main output connections when provided with main output connections', () => {
|
||||
const inputs = ref<CanvasElementData['inputs']>([]);
|
||||
const outputs = ref<CanvasElementData['outputs']>([]);
|
||||
const connections = ref<CanvasElementData['connections']>({
|
||||
const inputs = ref<CanvasNodeData['inputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([]);
|
||||
const connections = ref<CanvasNodeData['connections']>({
|
||||
input: {},
|
||||
output: {
|
||||
[NodeConnectionType.Main]: [
|
||||
|
||||
@@ -3,10 +3,9 @@ import { ref } from 'vue';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import type { Workflow } from 'n8n-workflow';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { useCanvasMapping } from '@/composables/useCanvasMapping';
|
||||
import type { IWorkflowDb } from '@/Interface';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import {
|
||||
createTestWorkflowObject,
|
||||
mockNode,
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
} from '@/__tests__/mocks';
|
||||
import { MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE } from '@/constants';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '../stores/workflows.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createPinia();
|
||||
@@ -37,38 +36,44 @@ afterEach(() => {
|
||||
|
||||
describe('useCanvasMapping', () => {
|
||||
it('should initialize with default props', () => {
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [],
|
||||
const nodes: INodeUi[] = [];
|
||||
const connections = {};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { elements, connections } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { nodes: mappedNodes, connections: mappedConnections } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(elements.value).toEqual([]);
|
||||
expect(connections.value).toEqual([]);
|
||||
expect(mappedNodes.value).toEqual([]);
|
||||
expect(mappedConnections.value).toEqual([]);
|
||||
});
|
||||
|
||||
describe('elements', () => {
|
||||
it('should map nodes to canvas elements', () => {
|
||||
describe('nodes', () => {
|
||||
it('should map nodes to canvas nodes', () => {
|
||||
const manualTriggerNode = mockNode({
|
||||
name: 'Manual Trigger',
|
||||
type: MANUAL_TRIGGER_NODE_TYPE,
|
||||
disabled: false,
|
||||
});
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [manualTriggerNode],
|
||||
const nodes = [manualTriggerNode];
|
||||
const connections = {};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { elements } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { nodes: mappedNodes } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(elements.value).toEqual([
|
||||
expect(mappedNodes.value).toEqual([
|
||||
{
|
||||
id: manualTriggerNode.id,
|
||||
label: manualTriggerNode.name,
|
||||
@@ -133,17 +138,20 @@ describe('useCanvasMapping', () => {
|
||||
type: MANUAL_TRIGGER_NODE_TYPE,
|
||||
disabled: true,
|
||||
});
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [manualTriggerNode],
|
||||
const nodes = [manualTriggerNode];
|
||||
const connections = {};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { elements } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { nodes: mappedNodes } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(elements.value[0]?.data?.disabled).toEqual(true);
|
||||
expect(mappedNodes.value[0]?.data?.disabled).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle execution state', () => {
|
||||
@@ -152,42 +160,49 @@ describe('useCanvasMapping', () => {
|
||||
type: MANUAL_TRIGGER_NODE_TYPE,
|
||||
disabled: true,
|
||||
});
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [manualTriggerNode],
|
||||
const nodes = [manualTriggerNode];
|
||||
const connections = {};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
useWorkflowsStore().addExecutingNode(manualTriggerNode.name);
|
||||
|
||||
const { elements } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { nodes: mappedNodes } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(elements.value[0]?.data?.execution.running).toEqual(true);
|
||||
expect(mappedNodes.value[0]?.data?.execution.running).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle input and output connections', () => {
|
||||
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [manualTriggerNode, setNode],
|
||||
connections: {
|
||||
[manualTriggerNode.name]: {
|
||||
[NodeConnectionType.Main]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
const nodes = [manualTriggerNode, setNode];
|
||||
const connections = {
|
||||
[manualTriggerNode.name]: {
|
||||
[NodeConnectionType.Main]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { elements } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { nodes: mappedNodes } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(elements.value[0]?.data?.connections.output).toHaveProperty(NodeConnectionType.Main);
|
||||
expect(elements.value[0]?.data?.connections.output[NodeConnectionType.Main][0][0]).toEqual(
|
||||
expect(mappedNodes.value[0]?.data?.connections.output).toHaveProperty(
|
||||
NodeConnectionType.Main,
|
||||
);
|
||||
expect(mappedNodes.value[0]?.data?.connections.output[NodeConnectionType.Main][0][0]).toEqual(
|
||||
expect.objectContaining({
|
||||
node: setNode.name,
|
||||
type: NodeConnectionType.Main,
|
||||
@@ -195,8 +210,8 @@ describe('useCanvasMapping', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(elements.value[1]?.data?.connections.input).toHaveProperty(NodeConnectionType.Main);
|
||||
expect(elements.value[1]?.data?.connections.input[NodeConnectionType.Main][0][0]).toEqual(
|
||||
expect(mappedNodes.value[1]?.data?.connections.input).toHaveProperty(NodeConnectionType.Main);
|
||||
expect(mappedNodes.value[1]?.data?.connections.input[NodeConnectionType.Main][0][0]).toEqual(
|
||||
expect.objectContaining({
|
||||
node: manualTriggerNode.name,
|
||||
type: NodeConnectionType.Main,
|
||||
@@ -209,24 +224,26 @@ describe('useCanvasMapping', () => {
|
||||
describe('connections', () => {
|
||||
it('should map connections to canvas connections', () => {
|
||||
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [manualTriggerNode, setNode],
|
||||
connections: {
|
||||
[manualTriggerNode.name]: {
|
||||
[NodeConnectionType.Main]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
const nodes = [manualTriggerNode, setNode];
|
||||
const connections = {
|
||||
[manualTriggerNode.name]: {
|
||||
[NodeConnectionType.Main]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { connections } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { connections: mappedConnections } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(connections.value).toEqual([
|
||||
expect(mappedConnections.value).toEqual([
|
||||
{
|
||||
data: {
|
||||
fromNodeName: manualTriggerNode.name,
|
||||
@@ -254,27 +271,29 @@ describe('useCanvasMapping', () => {
|
||||
|
||||
it('should map multiple input types to canvas connections', () => {
|
||||
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
nodes: [manualTriggerNode, setNode],
|
||||
connections: {
|
||||
[manualTriggerNode.name]: {
|
||||
[NodeConnectionType.AiTool]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.AiTool, index: 0 }],
|
||||
],
|
||||
[NodeConnectionType.AiDocument]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.AiDocument, index: 1 }],
|
||||
],
|
||||
},
|
||||
const nodes = [manualTriggerNode, setNode];
|
||||
const connections = {
|
||||
[manualTriggerNode.name]: {
|
||||
[NodeConnectionType.AiTool]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.AiTool, index: 0 }],
|
||||
],
|
||||
[NodeConnectionType.AiDocument]: [
|
||||
[{ node: setNode.name, type: NodeConnectionType.AiDocument, index: 1 }],
|
||||
],
|
||||
},
|
||||
};
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
});
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { connections } = useCanvasMapping({
|
||||
workflow: ref(workflow),
|
||||
const { connections: mappedConnections } = useCanvasMapping({
|
||||
nodes: ref(nodes),
|
||||
connections: ref(connections),
|
||||
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||
});
|
||||
|
||||
expect(connections.value).toEqual([
|
||||
expect(mappedConnections.value).toEqual([
|
||||
{
|
||||
data: {
|
||||
fromNodeName: manualTriggerNode.name,
|
||||
|
||||
@@ -12,9 +12,10 @@ import type {
|
||||
CanvasConnection,
|
||||
CanvasConnectionData,
|
||||
CanvasConnectionPort,
|
||||
CanvasElement,
|
||||
CanvasElementData,
|
||||
CanvasNode,
|
||||
CanvasNodeData,
|
||||
} from '@/types';
|
||||
import { CanvasNodeRenderType } from '@/types';
|
||||
import {
|
||||
mapLegacyConnectionsToCanvasConnections,
|
||||
mapLegacyEndpointsToCanvasConnectionPort,
|
||||
@@ -22,20 +23,23 @@ import {
|
||||
import type {
|
||||
ExecutionStatus,
|
||||
ExecutionSummary,
|
||||
IConnections,
|
||||
INodeExecutionData,
|
||||
ITaskData,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers } from 'n8n-workflow';
|
||||
import type { IWorkflowDb } from '@/Interface';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { WAIT_TIME_UNLIMITED } from '@/constants';
|
||||
import { sanitizeHtml } from '@/utils/htmlUtils';
|
||||
|
||||
export function useCanvasMapping({
|
||||
workflow,
|
||||
nodes,
|
||||
connections,
|
||||
workflowObject,
|
||||
}: {
|
||||
workflow: Ref<IWorkflowDb>;
|
||||
nodes: Ref<INodeUi[]>;
|
||||
connections: Ref<IConnections>;
|
||||
workflowObject: Ref<Workflow>;
|
||||
}) {
|
||||
const i18n = useI18n();
|
||||
@@ -44,24 +48,36 @@ export function useCanvasMapping({
|
||||
|
||||
const renderTypeByNodeType = computed(
|
||||
() =>
|
||||
workflow.value.nodes.reduce<Record<string, CanvasElementData['render']>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, CanvasNodeData['render']>>((acc, node) => {
|
||||
// @TODO Add support for sticky notes here
|
||||
|
||||
acc[node.type] = {
|
||||
type: 'default',
|
||||
options: {
|
||||
trigger: nodeTypesStore.isTriggerNode(node.type),
|
||||
configuration: nodeTypesStore.isConfigNode(workflowObject.value, node, node.type),
|
||||
configurable: nodeTypesStore.isConfigurableNode(workflowObject.value, node, node.type),
|
||||
},
|
||||
};
|
||||
switch (node.type) {
|
||||
case `${CanvasNodeRenderType.AddNodes}`:
|
||||
acc[node.type] = {
|
||||
type: CanvasNodeRenderType.AddNodes,
|
||||
options: {},
|
||||
};
|
||||
break;
|
||||
default:
|
||||
acc[node.type] = {
|
||||
type: CanvasNodeRenderType.Default,
|
||||
options: {
|
||||
trigger: nodeTypesStore.isTriggerNode(node.type),
|
||||
configuration: nodeTypesStore.isConfigNode(workflowObject.value, node, node.type),
|
||||
configurable: nodeTypesStore.isConfigurableNode(
|
||||
workflowObject.value,
|
||||
node,
|
||||
node.type,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}) ?? {},
|
||||
);
|
||||
|
||||
const nodeInputsById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, CanvasConnectionPort[]>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, CanvasConnectionPort[]>>((acc, node) => {
|
||||
const nodeTypeDescription = nodeTypesStore.getNodeType(node.type);
|
||||
const workflowObjectNode = workflowObject.value.getNode(node.name);
|
||||
|
||||
@@ -81,7 +97,7 @@ export function useCanvasMapping({
|
||||
);
|
||||
|
||||
const nodeOutputsById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, CanvasConnectionPort[]>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, CanvasConnectionPort[]>>((acc, node) => {
|
||||
const nodeTypeDescription = nodeTypesStore.getNodeType(node.type);
|
||||
const workflowObjectNode = workflowObject.value.getNode(node.name);
|
||||
|
||||
@@ -101,21 +117,21 @@ export function useCanvasMapping({
|
||||
);
|
||||
|
||||
const nodePinnedDataById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, INodeExecutionData[] | undefined>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, INodeExecutionData[] | undefined>>((acc, node) => {
|
||||
acc[node.id] = workflowsStore.pinDataByNodeName(node.name);
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const nodeExecutionRunningById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, boolean>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, boolean>>((acc, node) => {
|
||||
acc[node.id] = workflowsStore.isNodeExecuting(node.name);
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const nodeExecutionStatusById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, ExecutionStatus>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, ExecutionStatus>>((acc, node) => {
|
||||
acc[node.id] =
|
||||
workflowsStore.getWorkflowRunData?.[node.name]?.filter(Boolean)[0].executionStatus ?? 'new';
|
||||
return acc;
|
||||
@@ -123,14 +139,14 @@ export function useCanvasMapping({
|
||||
);
|
||||
|
||||
const nodeExecutionRunDataById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, ITaskData[] | null>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, ITaskData[] | null>>((acc, node) => {
|
||||
acc[node.id] = workflowsStore.getWorkflowResultDataByNodeName(node.name);
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const nodeIssuesById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, string[]>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, string[]>>((acc, node) => {
|
||||
const issues: string[] = [];
|
||||
const nodeExecutionRunData = workflowsStore.getWorkflowRunData?.[node.name];
|
||||
if (nodeExecutionRunData) {
|
||||
@@ -154,7 +170,7 @@ export function useCanvasMapping({
|
||||
);
|
||||
|
||||
const nodeHasIssuesById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, boolean>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, boolean>>((acc, node) => {
|
||||
if (['crashed', 'error'].includes(nodeExecutionStatusById.value[node.id])) {
|
||||
acc[node.id] = true;
|
||||
} else if (nodePinnedDataById.value[node.id]) {
|
||||
@@ -168,7 +184,7 @@ export function useCanvasMapping({
|
||||
);
|
||||
|
||||
const nodeExecutionWaitingById = computed(() =>
|
||||
workflow.value.nodes.reduce<Record<string, string | undefined>>((acc, node) => {
|
||||
nodes.value.reduce<Record<string, string | undefined>>((acc, node) => {
|
||||
const isExecutionSummary = (execution: object): execution is ExecutionSummary =>
|
||||
'waitTill' in execution;
|
||||
|
||||
@@ -198,12 +214,12 @@ export function useCanvasMapping({
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const elements = computed<CanvasElement[]>(() => [
|
||||
...workflow.value.nodes.map<CanvasElement>((node) => {
|
||||
const mappedNodes = computed<CanvasNode[]>(() => [
|
||||
...nodes.value.map<CanvasNode>((node) => {
|
||||
const inputConnections = workflowObject.value.connectionsByDestinationNode[node.name] ?? {};
|
||||
const outputConnections = workflowObject.value.connectionsBySourceNode[node.name] ?? {};
|
||||
|
||||
const data: CanvasElementData = {
|
||||
const data: CanvasNodeData = {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
typeVersion: node.typeVersion,
|
||||
@@ -244,31 +260,26 @@ export function useCanvasMapping({
|
||||
}),
|
||||
]);
|
||||
|
||||
const connections = computed<CanvasConnection[]>(() => {
|
||||
const mappedConnections = mapLegacyConnectionsToCanvasConnections(
|
||||
workflow.value.connections ?? [],
|
||||
workflow.value.nodes ?? [],
|
||||
const mappedConnections = computed<CanvasConnection[]>(() => {
|
||||
return mapLegacyConnectionsToCanvasConnections(connections.value ?? [], nodes.value ?? []).map(
|
||||
(connection) => {
|
||||
const type = getConnectionType(connection);
|
||||
const label = getConnectionLabel(connection);
|
||||
const data = getConnectionData(connection);
|
||||
|
||||
return {
|
||||
...connection,
|
||||
data,
|
||||
type,
|
||||
label,
|
||||
animated: data.status === 'running',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return mappedConnections.map((connection) => {
|
||||
const type = getConnectionType(connection);
|
||||
const label = getConnectionLabel(connection);
|
||||
const data = getConnectionData(connection);
|
||||
|
||||
return {
|
||||
...connection,
|
||||
data,
|
||||
type,
|
||||
label,
|
||||
animated: data.status === 'running',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function getConnectionData(connection: CanvasConnection): CanvasConnectionData {
|
||||
const fromNode = workflow.value.nodes.find(
|
||||
(node) => node.name === connection.data?.fromNodeName,
|
||||
);
|
||||
const fromNode = nodes.value.find((node) => node.name === connection.data?.fromNodeName);
|
||||
|
||||
let status: CanvasConnectionData['status'];
|
||||
if (fromNode) {
|
||||
@@ -297,9 +308,7 @@ export function useCanvasMapping({
|
||||
}
|
||||
|
||||
function getConnectionLabel(connection: CanvasConnection): string {
|
||||
const fromNode = workflow.value.nodes.find(
|
||||
(node) => node.name === connection.data?.fromNodeName,
|
||||
);
|
||||
const fromNode = nodes.value.find((node) => node.name === connection.data?.fromNodeName);
|
||||
|
||||
if (!fromNode) {
|
||||
return '';
|
||||
@@ -323,7 +332,7 @@ export function useCanvasMapping({
|
||||
}
|
||||
|
||||
return {
|
||||
connections,
|
||||
elements,
|
||||
connections: mappedConnections,
|
||||
nodes: mappedNodes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCanvasNode } from '@/composables/useCanvasNode';
|
||||
import { inject, ref } from 'vue';
|
||||
import type { CanvasNodeInjectionData } from '../types';
|
||||
import { CanvasNodeRenderType } from '../types';
|
||||
|
||||
vi.mock('vue', async () => {
|
||||
const actual = await vi.importActual('vue');
|
||||
@@ -47,7 +48,7 @@ describe('useCanvasNode', () => {
|
||||
runData: { count: 1, visible: true },
|
||||
pinnedData: { count: 1, visible: true },
|
||||
render: {
|
||||
type: 'default',
|
||||
type: CanvasNodeRenderType.Default,
|
||||
options: {
|
||||
configurable: false,
|
||||
configuration: false,
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import { CanvasNodeKey } from '@/constants';
|
||||
import { computed, inject } from 'vue';
|
||||
import type { CanvasElementData } from '@/types';
|
||||
import type { CanvasNodeData } from '@/types';
|
||||
import { CanvasNodeRenderType } from '@/types';
|
||||
|
||||
export function useCanvasNode() {
|
||||
const node = inject(CanvasNodeKey);
|
||||
const data = computed<CanvasElementData>(
|
||||
const data = computed<CanvasNodeData>(
|
||||
() =>
|
||||
node?.data.value ?? {
|
||||
id: '',
|
||||
@@ -26,7 +27,7 @@ export function useCanvasNode() {
|
||||
},
|
||||
runData: { count: 0, visible: false },
|
||||
render: {
|
||||
type: 'default',
|
||||
type: CanvasNodeRenderType.Default,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @TODO Remove this notice when Canvas V2 is the only one in use
|
||||
*/
|
||||
|
||||
import type { CanvasElement } from '@/types';
|
||||
import type { CanvasNode } from '@/types';
|
||||
import { CanvasConnectionMode } from '@/types';
|
||||
import type {
|
||||
AddedNodesAndConnections,
|
||||
@@ -105,7 +105,7 @@ export function useCanvasOperations({
|
||||
|
||||
function updateNodePosition(
|
||||
id: string,
|
||||
position: CanvasElement['position'],
|
||||
position: CanvasNode['position'],
|
||||
{ trackHistory = false, trackBulk = true } = {},
|
||||
) {
|
||||
const node = workflowsStore.getNodeById(id);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CanvasElementData } from '@/types';
|
||||
import type { CanvasNodeData } from '@/types';
|
||||
import type { MaybeRef } from 'vue';
|
||||
import { computed, unref } from 'vue';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
@@ -8,9 +8,9 @@ export function useNodeConnections({
|
||||
outputs,
|
||||
connections,
|
||||
}: {
|
||||
inputs: MaybeRef<CanvasElementData['inputs']>;
|
||||
outputs: MaybeRef<CanvasElementData['outputs']>;
|
||||
connections: MaybeRef<CanvasElementData['connections']>;
|
||||
inputs: MaybeRef<CanvasNodeData['inputs']>;
|
||||
outputs: MaybeRef<CanvasNodeData['outputs']>;
|
||||
connections: MaybeRef<CanvasNodeData['connections']>;
|
||||
}) {
|
||||
/**
|
||||
* Inputs
|
||||
|
||||
Reference in New Issue
Block a user