fix(editor): Inline expression previews are not displayed in NDV (#14475)

This commit is contained in:
Suguru Inoue
2025-04-08 16:38:28 +02:00
committed by GitHub
parent 048b9d7589
commit aee83bf344
7 changed files with 156 additions and 53 deletions

View File

@@ -126,6 +126,10 @@ export function getNodeOutputErrorMessage() {
return getOutputPanel().findChildByTestId('node-error-message'); return getOutputPanel().findChildByTestId('node-error-message');
} }
export function getParameterExpressionPreviewValue() {
return cy.getByTestId('parameter-expression-preview-value');
}
/** /**
* Actions * Actions
*/ */
@@ -264,3 +268,7 @@ export function populateFixedCollection<T extends readonly string[]>(
} }
} }
} }
export function assertInlineExpressionValid() {
cy.getByTestId('inline-expression-editor-input').find('.cm-valid-resolvable').should('exist');
}

View File

@@ -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');
});
});

View File

@@ -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"
}
}

View File

@@ -1547,7 +1547,7 @@ export interface IN8nPromptResponse {
export type InputPanel = { export type InputPanel = {
nodeName?: string; nodeName?: string;
run: number; run?: number;
branch?: number; branch?: number;
data: { data: {
isEmpty: boolean; isEmpty: boolean;
@@ -1555,7 +1555,6 @@ export type InputPanel = {
}; };
export type OutputPanel = { export type OutputPanel = {
run: number;
branch?: number; branch?: number;
data: { data: {
isEmpty: boolean; isEmpty: boolean;

View File

@@ -5,7 +5,7 @@ import { useI18n } from '@/composables/useI18n';
import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { N8nButton, N8nRadioButtons, N8nText, N8nTooltip } from '@n8n/design-system'; 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 { ElTree, type TreeNode as ElTreeNode } from 'element-plus';
import { import {
createAiData, createAiData,
@@ -115,9 +115,6 @@ function handleToggleExpanded(treeNode: ElTreeNode) {
async function handleOpenNdv(treeNode: TreeNode) { async function handleOpenNdv(treeNode: TreeNode) {
ndvStore.setActiveNodeName(treeNode.node); 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) { async function handleTriggerPartialExecution(treeNode: TreeNode) {

View File

@@ -78,6 +78,9 @@ const { APP_Z_INDEXES } = useStyles();
const settingsEventBus = createEventBus(); const settingsEventBus = createEventBus();
const redrawRequired = ref(false); const redrawRequired = ref(false);
const runInputIndex = ref(-1);
const runOutputIndex = ref(-1);
const isLinkingEnabled = ref(true);
const selectedInput = ref<string | undefined>(); const selectedInput = ref<string | undefined>();
const triggerWaitingWarningEnabled = ref(false); const triggerWaitingWarningEnabled = ref(false);
const isDragging = ref(false); const isDragging = ref(false);
@@ -245,6 +248,12 @@ const maxOutputRun = computed(() => {
return 0; return 0;
}); });
const outputRun = computed(() =>
runOutputIndex.value === -1
? maxOutputRun.value
: Math.min(runOutputIndex.value, maxOutputRun.value),
);
const maxInputRun = computed(() => { const maxInputRun = computed(() => {
if (inputNode.value === null || activeNode.value === null) { if (inputNode.value === null || activeNode.value === null) {
return 0; return 0;
@@ -281,23 +290,15 @@ const maxInputRun = computed(() => {
return 0; 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(() => { const inputRun = computed(() => {
if (isLinkingEnabled.value && maxOutputRun.value === maxInputRun.value) { if (isLinkingEnabled.value && maxOutputRun.value === maxInputRun.value) {
return outputRun.value; return outputRun.value;
} }
if (ndvStore.input.run === -1) { if (runInputIndex.value === -1) {
return maxInputRun.value; return maxInputRun.value;
} }
return Math.min(ndvStore.input.run, maxInputRun.value); return Math.min(runInputIndex.value, maxInputRun.value);
}); });
const canLinkRuns = computed( const canLinkRuns = computed(
@@ -442,13 +443,13 @@ const onPanelsInit = (e: { position: number }) => {
}; };
const onLinkRunToOutput = () => { const onLinkRunToOutput = () => {
ndvStore.setRunIndexLinkingEnabled(true); isLinkingEnabled.value = true;
trackLinking('output'); trackLinking('output');
}; };
const onUnlinkRun = (pane: string) => { const onUnlinkRun = (pane: string) => {
ndvStore.setInputRunIndex(outputRun.value); runInputIndex.value = runOutputIndex.value;
ndvStore.setRunIndexLinkingEnabled(false); isLinkingEnabled.value = false;
trackLinking(pane); trackLinking(pane);
}; };
@@ -475,8 +476,8 @@ const trackLinking = (pane: string) => {
}; };
const onLinkRunToInput = () => { const onLinkRunToInput = () => {
ndvStore.setOutputRunIndex(inputRun.value); runOutputIndex.value = runInputIndex.value;
ndvStore.setRunIndexLinkingEnabled(true); isLinkingEnabled.value = true;
trackLinking('input'); trackLinking('input');
}; };
@@ -552,12 +553,15 @@ const trackRunChange = (run: number, pane: string) => {
}; };
const onRunOutputIndexChange = (run: number) => { const onRunOutputIndexChange = (run: number) => {
ndvStore.setOutputRunIndex(run); runOutputIndex.value = run;
trackRunChange(run, 'output'); trackRunChange(run, 'output');
}; };
const onRunInputIndexChange = (run: number) => { const onRunInputIndexChange = (run: number) => {
ndvStore.setInputRunIndex(run); runInputIndex.value = run;
if (linked.value) {
runOutputIndex.value = run;
}
trackRunChange(run, 'input'); trackRunChange(run, 'input');
}; };
@@ -566,8 +570,8 @@ const onOutputTableMounted = (e: { avgRowHeight: number }) => {
}; };
const onInputNodeChange = (value: string, index: number) => { const onInputNodeChange = (value: string, index: number) => {
ndvStore.setInputRunIndex(-1); runInputIndex.value = -1;
ndvStore.setRunIndexLinkingEnabled(true); isLinkingEnabled.value = true;
selectedInput.value = value; selectedInput.value = value;
telemetry.track('User changed ndv input dropdown', { telemetry.track('User changed ndv input dropdown', {
@@ -617,6 +621,9 @@ watch(
} }
if (node && node.name !== oldNode?.name && !isActiveStickyNode.value) { if (node && node.name !== oldNode?.name && !isActiveStickyNode.value) {
runInputIndex.value = -1;
runOutputIndex.value = -1;
isLinkingEnabled.value = true;
selectedInput.value = undefined; selectedInput.value = undefined;
triggerWaitingWarningEnabled.value = false; triggerWaitingWarningEnabled.value = false;
avgOutputRowHeight.value = 0; avgOutputRowHeight.value = 0;
@@ -667,12 +674,26 @@ watch(
{ immediate: true }, { immediate: true },
); );
watch(maxOutputRun, () => {
runOutputIndex.value = -1;
});
watch(maxInputRun, () => {
runInputIndex.value = -1;
});
watch(inputNodeName, (nodeName) => { watch(inputNodeName, (nodeName) => {
setTimeout(() => { setTimeout(() => {
ndvStore.setInputNodeName(nodeName); ndvStore.setInputNodeName(nodeName);
}, 0); }, 0);
}); });
watch(inputRun, (inputRun) => {
setTimeout(() => {
ndvStore.setInputRunIndex(inputRun);
}, 0);
});
onMounted(() => { onMounted(() => {
dataPinningEventBus.on('data-pinning-discovery', setIsTooltipVisible); dataPinningEventBus.on('data-pinning-discovery', setIsTooltipVisible);
}); });

View File

@@ -38,7 +38,6 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
const localStorageAutoCompleteIsOnboarded = useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED); const localStorageAutoCompleteIsOnboarded = useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED);
const activeNodeName = ref<string | null>(null); const activeNodeName = ref<string | null>(null);
const isRunIndexLinkingEnabled = ref(true);
const mainPanelDimensions = ref<MainPanelDimensions>({ const mainPanelDimensions = ref<MainPanelDimensions>({
unknown: { ...DEFAULT_MAIN_PANEL_DIMENSIONS }, unknown: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
regular: { ...DEFAULT_MAIN_PANEL_DIMENSIONS }, regular: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
@@ -49,7 +48,7 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
const pushRef = ref(''); const pushRef = ref('');
const input = ref<InputPanel>({ const input = ref<InputPanel>({
nodeName: undefined, nodeName: undefined,
run: -1, run: undefined,
branch: undefined, branch: undefined,
data: { data: {
isEmpty: true, isEmpty: true,
@@ -60,7 +59,6 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
'schema', 'schema',
); );
const output = ref<OutputPanel>({ const output = ref<OutputPanel>({
run: -1,
branch: undefined, branch: undefined,
data: { data: {
isEmpty: true, isEmpty: true,
@@ -215,36 +213,15 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
const isNDVOpen = computed(() => activeNodeName.value !== null); const isNDVOpen = computed(() => activeNodeName.value !== null);
const setActiveNodeName = (nodeName: string | null): void => { const setActiveNodeName = (nodeName: string | null): void => {
if (nodeName === activeNodeName.value) {
return;
}
activeNodeName.value = nodeName; activeNodeName.value = nodeName;
// Reset run index
input.value.run = -1;
output.value.run = -1;
isRunIndexLinkingEnabled.value = true;
}; };
const setInputNodeName = (nodeName: string | undefined): void => { const setInputNodeName = (nodeName: string | undefined): void => {
input.value.nodeName = nodeName; input.value.nodeName = nodeName;
}; };
const setInputRunIndex = (run: number = -1): void => { const setInputRunIndex = (run?: number): void => {
input.value.run = run; 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: { const setMainPanelDimensions = (params: {
@@ -427,12 +404,9 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
expressionOutputItemIndex, expressionOutputItemIndex,
isTableHoverOnboarded, isTableHoverOnboarded,
mainPanelDimensions, mainPanelDimensions,
isRunIndexLinkingEnabled,
setActiveNodeName, setActiveNodeName,
setInputNodeName, setInputNodeName,
setInputRunIndex, setInputRunIndex,
setOutputRunIndex,
setRunIndexLinkingEnabled,
setMainPanelDimensions, setMainPanelDimensions,
setNDVPushRef, setNDVPushRef,
resetNDVPushRef, resetNDVPushRef,