From a45899765c87f4db9939a6d8083e6dc35851f1a6 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Wed, 10 Jul 2024 17:51:18 +0300 Subject: [PATCH] feat(editor): Add support for configurable configuration nodes in new canvas (no-changelog) (#9991) --- .../editor-ui/src/__tests__/data/canvas.ts | 7 +- .../elements/nodes/CanvasNodeRenderer.spec.ts | 16 ++- .../elements/nodes/CanvasNodeRenderer.vue | 17 +-- .../elements/nodes/CanvasNodeToolbar.spec.ts | 5 +- .../elements/nodes/CanvasNodeToolbar.vue | 9 +- .../CanvasNodeConfigurable.spec.ts | 107 -------------- .../render-types/CanvasNodeConfigurable.vue | 134 ------------------ .../CanvasNodeConfiguration.spec.ts | 80 ----------- .../render-types/CanvasNodeConfiguration.vue | 79 ----------- .../render-types/CanvasNodeDefault.spec.ts | 99 ++++++++++++- .../nodes/render-types/CanvasNodeDefault.vue | 85 ++++++++++- .../CanvasNodeDefault.spec.ts.snap | 77 ++++++++++ .../src/composables/useCanvasMapping.spec.ts | 9 +- .../src/composables/useCanvasMapping.ts | 27 ++-- .../src/composables/useCanvasNode.spec.ts | 11 +- .../src/composables/useCanvasNode.ts | 8 +- packages/editor-ui/src/types/canvas.ts | 5 +- 17 files changed, 318 insertions(+), 457 deletions(-) delete mode 100644 packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfigurable.spec.ts delete mode 100644 packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfigurable.vue delete mode 100644 packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.spec.ts delete mode 100644 packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.vue create mode 100644 packages/editor-ui/src/components/canvas/elements/nodes/render-types/__snapshots__/CanvasNodeDefault.spec.ts.snap diff --git a/packages/editor-ui/src/__tests__/data/canvas.ts b/packages/editor-ui/src/__tests__/data/canvas.ts index 11253bfec2..e5ccb071a5 100644 --- a/packages/editor-ui/src/__tests__/data/canvas.ts +++ b/packages/editor-ui/src/__tests__/data/canvas.ts @@ -14,7 +14,10 @@ export function createCanvasNodeData({ issues = { items: [], visible: false }, pinnedData = { count: 0, visible: false }, runData = { count: 0, visible: false }, - renderType = 'default', + render = { + type: 'default', + options: { configurable: false, configuration: false, trigger: false }, + }, }: Partial = {}): CanvasElementData { return { execution, @@ -28,7 +31,7 @@ export function createCanvasNodeData({ inputs, outputs, connections, - renderType, + render, }; } diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.spec.ts b/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.spec.ts index 8c9fb24232..72e9a30ffe 100644 --- a/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.spec.ts +++ b/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.spec.ts @@ -21,7 +21,7 @@ describe('CanvasNodeRenderer', () => { }, }); - expect(getByTestId('canvas-node-default')).toBeInTheDocument(); + expect(getByTestId('canvas-default-node')).toBeInTheDocument(); }); it('should render configuration node correctly', async () => { @@ -30,14 +30,17 @@ describe('CanvasNodeRenderer', () => { provide: { ...createCanvasNodeProvide({ data: { - renderType: 'configuration', + render: { + type: 'default', + options: { configuration: true }, + }, }, }), }, }, }); - expect(getByTestId('canvas-node-configuration')).toBeInTheDocument(); + expect(getByTestId('canvas-configuration-node')).toBeInTheDocument(); }); it('should render configurable node correctly', async () => { @@ -46,13 +49,16 @@ describe('CanvasNodeRenderer', () => { provide: { ...createCanvasNodeProvide({ data: { - renderType: 'configurable', + render: { + type: 'default', + options: { configurable: true }, + }, }, }), }, }, }); - expect(getByTestId('canvas-node-configurable')).toBeInTheDocument(); + expect(getByTestId('canvas-configurable-node')).toBeInTheDocument(); }); }); diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.vue b/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.vue index 65b13bf891..6ebe849980 100644 --- a/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.vue +++ b/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNodeRenderer.vue @@ -1,8 +1,6 @@ - - - - diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.spec.ts b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.spec.ts deleted file mode 100644 index 92884d634f..0000000000 --- a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import CanvasNodeConfiguration from '@/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.vue'; -import { createComponentRenderer } from '@/__tests__/render'; -import { createCanvasNodeProvide } from '@/__tests__/data'; -import { createTestingPinia } from '@pinia/testing'; -import { setActivePinia } from 'pinia'; - -const renderComponent = createComponentRenderer(CanvasNodeConfiguration); - -beforeEach(() => { - const pinia = createTestingPinia(); - setActivePinia(pinia); -}); - -describe('CanvasNodeConfiguration', () => { - it('should render node correctly', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide(), - }, - }, - }); - - expect(getByText('Test Node')).toBeInTheDocument(); - }); - - describe('selected', () => { - it('should apply selected class when node is selected', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ selected: true }), - }, - }, - }); - expect(getByText('Test Node').closest('.node')).toHaveClass('selected'); - }); - - it('should not apply selected class when node is not selected', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide(), - }, - }, - }); - expect(getByText('Test Node').closest('.node')).not.toHaveClass('selected'); - }); - }); - - describe('disabled', () => { - it('should apply disabled class when node is disabled', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide({ - data: { - disabled: true, - }, - }), - }, - }, - }); - - expect(getByText('Test Node').closest('.node')).toHaveClass('disabled'); - expect(getByText('(Deactivated)')).toBeVisible(); - }); - - it('should not apply disabled class when node is enabled', () => { - const { getByText } = renderComponent({ - global: { - provide: { - ...createCanvasNodeProvide(), - }, - }, - }); - expect(getByText('Test Node').closest('.node')).not.toHaveClass('disabled'); - }); - }); -}); diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.vue b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.vue deleted file mode 100644 index de994dd74d..0000000000 --- a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeConfiguration.vue +++ /dev/null @@ -1,79 +0,0 @@ - - - - - diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.spec.ts b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.spec.ts index ad05f860a6..3ca88e528e 100644 --- a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.spec.ts +++ b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.spec.ts @@ -14,7 +14,7 @@ beforeEach(() => { describe('CanvasNodeDefault', () => { it('should render node correctly', () => { - const { getByText } = renderComponent({ + const { getByTestId } = renderComponent({ global: { provide: { ...createCanvasNodeProvide(), @@ -22,7 +22,7 @@ describe('CanvasNodeDefault', () => { }, }); - expect(getByText('Test Node')).toBeInTheDocument(); + expect(getByTestId('canvas-default-node')).toMatchSnapshot(); }); describe('outputs', () => { @@ -40,7 +40,7 @@ describe('CanvasNodeDefault', () => { }); const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--node-main-output-count': '1' }); // height calculation based on the number of outputs + expect(nodeElement).toHaveStyle({ '--canvas-node--main-output-count': '1' }); // height calculation based on the number of outputs }); it('should adjust height css variable based on the number of outputs (multiple outputs)', () => { @@ -61,7 +61,7 @@ describe('CanvasNodeDefault', () => { }); const nodeElement = getByText('Test Node').closest('.node'); - expect(nodeElement).toHaveStyle({ '--node-main-output-count': '3' }); // height calculation based on the number of outputs + expect(nodeElement).toHaveStyle({ '--canvas-node--main-output-count': '3' }); // height calculation based on the number of outputs }); }); @@ -131,4 +131,95 @@ describe('CanvasNodeDefault', () => { expect(getByText('Test Node').closest('.node')).toHaveClass('running'); }); }); + + describe('configurable', () => { + it('should render configurable node correctly', () => { + const { getByTestId } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide({ + data: { + render: { + type: 'default', + options: { configurable: true }, + }, + }, + }), + }, + }, + }); + + expect(getByTestId('canvas-configurable-node')).toMatchSnapshot(); + }); + + describe('inputs', () => { + it('should adjust width css variable based on the number of non-main inputs', () => { + const { getByText } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide({ + data: { + inputs: [ + { type: NodeConnectionType.Main, index: 0 }, + { type: NodeConnectionType.AiTool, index: 0 }, + { type: NodeConnectionType.AiDocument, index: 0, required: true }, + { type: NodeConnectionType.AiMemory, index: 0, required: true }, + ], + render: { + type: 'default', + options: { + configurable: true, + }, + }, + }, + }), + }, + }, + }); + + const nodeElement = getByText('Test Node').closest('.node'); + expect(nodeElement).toHaveStyle({ '--configurable-node--input-count': '3' }); + }); + }); + }); + + describe('configuration', () => { + it('should render configuration node correctly', () => { + const { getByTestId } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide({ + data: { + render: { + type: 'default', + options: { configuration: true }, + }, + }, + }), + }, + }, + }); + + expect(getByTestId('canvas-configuration-node')).toMatchSnapshot(); + }); + + it('should render configurable configuration node correctly', () => { + const { getByTestId } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide({ + data: { + render: { + type: 'default', + options: { configurable: true, configuration: true }, + }, + }, + }), + }, + }, + }); + + expect(getByTestId('canvas-configurable-node')).toMatchSnapshot(); + }); + }); }); diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue index 895b8a6579..ae73b2de3d 100644 --- a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue +++ b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue @@ -5,6 +5,7 @@ import { useI18n } from '@/composables/useI18n'; import CanvasNodeDisabledStrikeThrough from './parts/CanvasNodeDisabledStrikeThrough.vue'; import CanvasNodeStatusIcons from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeStatusIcons.vue'; import { useCanvasNode } from '@/composables/useCanvasNode'; +import { NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS } from '@/constants'; const $style = useCssModule(); const i18n = useI18n(); @@ -20,8 +21,9 @@ const { executionRunning, hasRunData, hasIssues, + renderOptions, } = useCanvasNode(); -const { mainOutputs } = useNodeConnections({ +const { mainOutputs, nonMainInputs, requiredNonMainInputs } = useNodeConnections({ inputs, outputs, connections, @@ -36,18 +38,47 @@ const classes = computed(() => { [$style.error]: hasIssues.value, [$style.pinned]: hasPinnedData.value, [$style.running]: executionRunning.value, + [$style.configurable]: renderOptions.value.configurable, + [$style.configuration]: renderOptions.value.configuration, + [$style.trigger]: renderOptions.value.trigger, }; }); const styles = computed(() => { - return { - '--node-main-output-count': mainOutputs.value.length, - }; + const stylesObject: Record = {}; + + if (renderOptions.value.configurable && requiredNonMainInputs.value.length > 0) { + let spacerCount = 0; + if (NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS) { + const requiredNonMainInputsCount = requiredNonMainInputs.value.length; + const optionalNonMainInputsCount = nonMainInputs.value.length - requiredNonMainInputsCount; + spacerCount = requiredNonMainInputsCount > 0 && optionalNonMainInputsCount > 0 ? 1 : 0; + } + + stylesObject['--configurable-node--input-count'] = nonMainInputs.value.length + spacerCount; + } + + stylesObject['--canvas-node--main-output-count'] = mainOutputs.value.length; + + return stylesObject; +}); + +const dataTestId = computed(() => { + let type = 'default'; + if (renderOptions.value.configurable) { + type = 'configurable'; + } else if (renderOptions.value.configuration) { + type = 'configuration'; + } else if (renderOptions.value.trigger) { + type = 'trigger'; + } + + return `canvas-${type}-node`; });