diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts index 1423801a5e..7ebba5636f 100644 --- a/cypress/composables/ndv.ts +++ b/cypress/composables/ndv.ts @@ -126,6 +126,10 @@ export function getNodeOutputErrorMessage() { return getOutputPanel().findChildByTestId('node-error-message'); } +export function getParameterExpressionPreviewValue() { + return cy.getByTestId('parameter-expression-preview-value'); +} + /** * Actions */ @@ -264,3 +268,7 @@ export function populateFixedCollection( } } } + +export function assertInlineExpressionValid() { + cy.getByTestId('inline-expression-editor-input').find('.cm-valid-resolvable').should('exist'); +} diff --git a/cypress/e2e/9999-SUG-38-inline-expression-preview.cy.ts b/cypress/e2e/9999-SUG-38-inline-expression-preview.cy.ts new file mode 100644 index 0000000000..b7d17f780d --- /dev/null +++ b/cypress/e2e/9999-SUG-38-inline-expression-preview.cy.ts @@ -0,0 +1,24 @@ +import { + assertInlineExpressionValid, + getParameterExpressionPreviewValue, +} from '../composables/ndv'; +import { + clickZoomToFit, + executeWorkflowAndWait, + navigateToNewWorkflowPage, + openNode, + pasteWorkflow, +} from '../composables/workflow'; +import Workflow from '../fixtures/Test_9999-SUG-38.json'; + +describe('SUG-38 Inline expression previews are not displayed in NDV', () => { + it("should show resolved inline expression preview in NDV if the node's input data is populated", () => { + navigateToNewWorkflowPage(); + pasteWorkflow(Workflow); + clickZoomToFit(); + executeWorkflowAndWait(); + openNode('Repro1'); + assertInlineExpressionValid(); + getParameterExpressionPreviewValue().should('have.text', 'hello there'); + }); +}); diff --git a/cypress/fixtures/Test_9999-SUG-38.json b/cypress/fixtures/Test_9999-SUG-38.json new file mode 100644 index 0000000000..73909c40f3 --- /dev/null +++ b/cypress/fixtures/Test_9999-SUG-38.json @@ -0,0 +1,80 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [-240, 180], + "id": "cd9b8124-567e-43d9-b4d1-638b111cd049", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "3a40d9f2-0eed-4a92-9287-9d6ec9ce90e8", + "name": "message", + "value": "hello there", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [-20, 180], + "id": "6e58ae14-4851-4e9d-9465-4155b6e2f278", + "name": "Edit Fields1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "9e957377-c5f2-4254-89d8-334d32a8cfb6", + "name": "test", + "value": "={{ $json.message }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [200, 180], + "id": "c4e9d792-51e9-4296-ba66-afac3cf378dd", + "name": "Repro1" + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "Repro1", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "instanceId": "cdc3bfdf3e6244f221ab6e71b2115a631406ae45a034bfca5e9731cf64f4eb64" + } +} diff --git a/packages/frontend/editor-ui/src/Interface.ts b/packages/frontend/editor-ui/src/Interface.ts index 7c46919417..2016b7db0a 100644 --- a/packages/frontend/editor-ui/src/Interface.ts +++ b/packages/frontend/editor-ui/src/Interface.ts @@ -1547,7 +1547,7 @@ export interface IN8nPromptResponse { export type InputPanel = { nodeName?: string; - run: number; + run?: number; branch?: number; data: { isEmpty: boolean; @@ -1555,7 +1555,6 @@ export type InputPanel = { }; export type OutputPanel = { - run: number; branch?: number; data: { isEmpty: boolean; diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue index 46369b6218..260cb2c537 100644 --- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue +++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue @@ -5,7 +5,7 @@ import { useI18n } from '@/composables/useI18n'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { N8nButton, N8nRadioButtons, N8nText, N8nTooltip } from '@n8n/design-system'; -import { computed, nextTick } from 'vue'; +import { computed } from 'vue'; import { ElTree, type TreeNode as ElTreeNode } from 'element-plus'; import { createAiData, @@ -115,9 +115,6 @@ function handleToggleExpanded(treeNode: ElTreeNode) { async function handleOpenNdv(treeNode: TreeNode) { ndvStore.setActiveNodeName(treeNode.node); - - // HACK: defer setting the output run index to not be overridden by other effects - await nextTick(() => ndvStore.setOutputRunIndex(treeNode.runIndex)); } async function handleTriggerPartialExecution(treeNode: TreeNode) { diff --git a/packages/frontend/editor-ui/src/components/NodeDetailsView.vue b/packages/frontend/editor-ui/src/components/NodeDetailsView.vue index f797124d89..d924b98486 100644 --- a/packages/frontend/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/frontend/editor-ui/src/components/NodeDetailsView.vue @@ -78,6 +78,9 @@ const { APP_Z_INDEXES } = useStyles(); const settingsEventBus = createEventBus(); const redrawRequired = ref(false); +const runInputIndex = ref(-1); +const runOutputIndex = ref(-1); +const isLinkingEnabled = ref(true); const selectedInput = ref(); const triggerWaitingWarningEnabled = ref(false); const isDragging = ref(false); @@ -245,6 +248,12 @@ const maxOutputRun = computed(() => { return 0; }); +const outputRun = computed(() => + runOutputIndex.value === -1 + ? maxOutputRun.value + : Math.min(runOutputIndex.value, maxOutputRun.value), +); + const maxInputRun = computed(() => { if (inputNode.value === null || activeNode.value === null) { return 0; @@ -281,23 +290,15 @@ const maxInputRun = computed(() => { return 0; }); -const isLinkingEnabled = computed(() => ndvStore.isRunIndexLinkingEnabled); - -const outputRun = computed(() => - ndvStore.output.run === -1 - ? maxOutputRun.value - : Math.min(ndvStore.output.run, maxOutputRun.value), -); - const inputRun = computed(() => { if (isLinkingEnabled.value && maxOutputRun.value === maxInputRun.value) { return outputRun.value; } - if (ndvStore.input.run === -1) { + if (runInputIndex.value === -1) { return maxInputRun.value; } - return Math.min(ndvStore.input.run, maxInputRun.value); + return Math.min(runInputIndex.value, maxInputRun.value); }); const canLinkRuns = computed( @@ -442,13 +443,13 @@ const onPanelsInit = (e: { position: number }) => { }; const onLinkRunToOutput = () => { - ndvStore.setRunIndexLinkingEnabled(true); + isLinkingEnabled.value = true; trackLinking('output'); }; const onUnlinkRun = (pane: string) => { - ndvStore.setInputRunIndex(outputRun.value); - ndvStore.setRunIndexLinkingEnabled(false); + runInputIndex.value = runOutputIndex.value; + isLinkingEnabled.value = false; trackLinking(pane); }; @@ -475,8 +476,8 @@ const trackLinking = (pane: string) => { }; const onLinkRunToInput = () => { - ndvStore.setOutputRunIndex(inputRun.value); - ndvStore.setRunIndexLinkingEnabled(true); + runOutputIndex.value = runInputIndex.value; + isLinkingEnabled.value = true; trackLinking('input'); }; @@ -552,12 +553,15 @@ const trackRunChange = (run: number, pane: string) => { }; const onRunOutputIndexChange = (run: number) => { - ndvStore.setOutputRunIndex(run); + runOutputIndex.value = run; trackRunChange(run, 'output'); }; const onRunInputIndexChange = (run: number) => { - ndvStore.setInputRunIndex(run); + runInputIndex.value = run; + if (linked.value) { + runOutputIndex.value = run; + } trackRunChange(run, 'input'); }; @@ -566,8 +570,8 @@ const onOutputTableMounted = (e: { avgRowHeight: number }) => { }; const onInputNodeChange = (value: string, index: number) => { - ndvStore.setInputRunIndex(-1); - ndvStore.setRunIndexLinkingEnabled(true); + runInputIndex.value = -1; + isLinkingEnabled.value = true; selectedInput.value = value; telemetry.track('User changed ndv input dropdown', { @@ -617,6 +621,9 @@ watch( } if (node && node.name !== oldNode?.name && !isActiveStickyNode.value) { + runInputIndex.value = -1; + runOutputIndex.value = -1; + isLinkingEnabled.value = true; selectedInput.value = undefined; triggerWaitingWarningEnabled.value = false; avgOutputRowHeight.value = 0; @@ -667,12 +674,26 @@ watch( { immediate: true }, ); +watch(maxOutputRun, () => { + runOutputIndex.value = -1; +}); + +watch(maxInputRun, () => { + runInputIndex.value = -1; +}); + watch(inputNodeName, (nodeName) => { setTimeout(() => { ndvStore.setInputNodeName(nodeName); }, 0); }); +watch(inputRun, (inputRun) => { + setTimeout(() => { + ndvStore.setInputRunIndex(inputRun); + }, 0); +}); + onMounted(() => { dataPinningEventBus.on('data-pinning-discovery', setIsTooltipVisible); }); diff --git a/packages/frontend/editor-ui/src/stores/ndv.store.ts b/packages/frontend/editor-ui/src/stores/ndv.store.ts index c84862d799..70b4f8d417 100644 --- a/packages/frontend/editor-ui/src/stores/ndv.store.ts +++ b/packages/frontend/editor-ui/src/stores/ndv.store.ts @@ -38,7 +38,6 @@ export const useNDVStore = defineStore(STORES.NDV, () => { const localStorageAutoCompleteIsOnboarded = useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED); const activeNodeName = ref(null); - const isRunIndexLinkingEnabled = ref(true); const mainPanelDimensions = ref({ unknown: { ...DEFAULT_MAIN_PANEL_DIMENSIONS }, regular: { ...DEFAULT_MAIN_PANEL_DIMENSIONS }, @@ -49,7 +48,7 @@ export const useNDVStore = defineStore(STORES.NDV, () => { const pushRef = ref(''); const input = ref({ nodeName: undefined, - run: -1, + run: undefined, branch: undefined, data: { isEmpty: true, @@ -60,7 +59,6 @@ export const useNDVStore = defineStore(STORES.NDV, () => { 'schema', ); const output = ref({ - run: -1, branch: undefined, data: { isEmpty: true, @@ -215,36 +213,15 @@ export const useNDVStore = defineStore(STORES.NDV, () => { const isNDVOpen = computed(() => activeNodeName.value !== null); const setActiveNodeName = (nodeName: string | null): void => { - if (nodeName === activeNodeName.value) { - return; - } - activeNodeName.value = nodeName; - - // Reset run index - input.value.run = -1; - output.value.run = -1; - isRunIndexLinkingEnabled.value = true; }; const setInputNodeName = (nodeName: string | undefined): void => { input.value.nodeName = nodeName; }; - const setInputRunIndex = (run: number = -1): void => { + const setInputRunIndex = (run?: number): void => { input.value.run = run; - - if (isRunIndexLinkingEnabled.value) { - setOutputRunIndex(run); - } - }; - - const setOutputRunIndex = (run: number = -1): void => { - output.value.run = run; - }; - - const setRunIndexLinkingEnabled = (value: boolean): void => { - isRunIndexLinkingEnabled.value = value; }; const setMainPanelDimensions = (params: { @@ -427,12 +404,9 @@ export const useNDVStore = defineStore(STORES.NDV, () => { expressionOutputItemIndex, isTableHoverOnboarded, mainPanelDimensions, - isRunIndexLinkingEnabled, setActiveNodeName, setInputNodeName, setInputRunIndex, - setOutputRunIndex, - setRunIndexLinkingEnabled, setMainPanelDimensions, setNDVPushRef, resetNDVPushRef,