diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index f86c61ae020f1..6043f14d93e25 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -1,7 +1,6 @@ -import { WorkflowPage, NDV } from '../pages'; import { v4 as uuid } from 'uuid'; -import { getPopper, getVisiblePopper, getVisibleSelect } from '../utils'; -import { META_KEY } from '../constants'; +import { NDV, WorkflowPage } from '../pages'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -368,4 +367,23 @@ describe('NDV', () => { // Should call the endpoint only once (on mount), not for every keystroke cy.get('@fetchParameterOptions').should('have.been.calledOnce'); }); + + it('should show node name and version in settings', () => { + cy.createFixtureWorkflow('Test_workflow_ndv_version.json', `NDV test version ${uuid()}`); + + workflowPage.actions.openNode('Edit Fields (old)'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Set node version 2 (Latest version: 3.2)'); + ndv.actions.close(); + + workflowPage.actions.openNode('Edit Fields (latest)'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Edit Fields (Set) node version 3.2 (Latest)'); + ndv.actions.close(); + + workflowPage.actions.openNode('Function'); + ndv.actions.openSettings(); + ndv.getters.nodeVersion().should('have.text', 'Function node version 1 (Deprecated)'); + ndv.actions.close(); + }); }); diff --git a/cypress/fixtures/Test_workflow_ndv_version.json b/cypress/fixtures/Test_workflow_ndv_version.json new file mode 100644 index 0000000000000..36e0815bb2f30 --- /dev/null +++ b/cypress/fixtures/Test_workflow_ndv_version.json @@ -0,0 +1,49 @@ +{ + "name": "Node versions", + "nodes": [ + { + "parameters": {}, + "id": "aadaed66-84ed-4cf8-bf21-082e9a65db76", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 1540, + 780 + ] + }, + { + "parameters": {}, + "id": "93d73a85-82f0-4380-a032-713d5dc82b32", + "name": "Function", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [ + 2040, + 780 + ] + }, + { + "id": "50f322d9-c622-4dd0-8d38-e851502739dd", + "name": "Edit Fields (old)", + "type": "n8n-nodes-base.set", + "typeVersion": 2, + "position": [ + 1880, + 780 + ] + }, + { + "id": "93aaadac-55fe-4618-b1eb-f63e61d1446a", + "name": "Edit Fields (latest)", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1720, + 780 + ] + } + ], + "pinData": {}, + "connections": {} + } diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 1c950be08c3e0..58749721a4e02 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -81,6 +81,8 @@ export class NDV extends BasePage { sqlEditorContainer: () => cy.getByTestId('sql-editor-container'), searchInput: () => cy.getByTestId('ndv-search'), pagination: () => cy.getByTestId('ndv-data-pagination'), + nodeVersion: () => cy.getByTestId('node-version'), + nodeSettingsTab: () => cy.getByTestId('tab-settings'), }; actions = { @@ -225,6 +227,10 @@ export class NDV extends BasePage { }); this.actions.validateExpressionPreview(fieldName, `node doesn't exist`); }, + + openSettings: () => { + this.getters.nodeSettingsTab().click(); + }, }; } diff --git a/packages/design-system/src/components/N8nTabs/Tabs.vue b/packages/design-system/src/components/N8nTabs/Tabs.vue index 5425e9e1e306d..84243ddd1f1ed 100644 --- a/packages/design-system/src/components/N8nTabs/Tabs.vue +++ b/packages/design-system/src/components/N8nTabs/Tabs.vue @@ -35,6 +35,7 @@
diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index f96d6016ab55d..bbe44b562e538 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -152,6 +152,17 @@ @valueChanged="valueChanged" @parameterBlur="onParameterBlur" /> +
+ {{ + $locale.baseText('nodeSettings.nodeVersion', { + interpolate: { + node: nodeType?.displayName as string, + version: node.typeVersion.toString(), + }, + }) + }} + ({{ nodeVersionTag }}) +
@@ -258,6 +269,29 @@ export default defineComponent({ return ''; }, + nodeTypeVersions(): number[] { + if (!this.node) return []; + return this.nodeTypesStore.getNodeVersions(this.node.type); + }, + latestVersion(): number { + return Math.max(...this.nodeTypeVersions); + }, + isLatestNodeVersion(): boolean { + return this.latestVersion === this.node?.typeVersion; + }, + nodeVersionTag(): string { + if (!this.nodeType || this.nodeType.hidden) { + return this.$locale.baseText('nodeSettings.deprecated'); + } + + if (this.isLatestNodeVersion) { + return this.$locale.baseText('nodeSettings.latest'); + } + + return this.$locale.baseText('nodeSettings.latestVersion', { + interpolate: { version: this.latestVersion.toString() }, + }); + }, nodeTypeDescription(): string { if (this.nodeType?.description) { const shortNodeType = this.$locale.shortNodeType(this.nodeType.name); @@ -1126,6 +1160,14 @@ export default defineComponent({ top: -25px; } +.node-version { + border-top: var(--border-base); + font-size: var(--font-size-xs); + font-size: var(--font-size-2xs); + padding: var(--spacing-xs) 0 var(--spacing-2xs) 0; + color: var(--color-text-light); +} + .parameter-value { input.expression { border-style: dashed; diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 102627a3244a3..49865fbf2efdc 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -995,6 +995,11 @@ "nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)", "nodeSettings.waitBetweenTries.displayName": "Wait Between Tries (ms)", "nodeSettings.hasForeignCredential": "To edit this node, either:
a) Ask {owner} to share the credential with you, or
b) Duplicate the node and add your own credential", + "nodeSettings.latest": "Latest", + "nodeSettings.deprecated": "Deprecated", + "nodeSettings.latestVersion": "Latest version: {version}", + "nodeSettings.nodeVersion": "{node} node version {version}", + "nodeView.addNode": "Add node", "nodeView.openNodesPanel": "Open nodes panel", "nodeView.addATriggerNodeFirst": "Add a Trigger Node first", "nodeView.addOrEnableTriggerNode": "Add or enable a Trigger node to execute the workflow", diff --git a/packages/editor-ui/src/stores/nodeTypes.store.ts b/packages/editor-ui/src/stores/nodeTypes.store.ts index 8323bd0907776..2659b5c98ef2f 100644 --- a/packages/editor-ui/src/stores/nodeTypes.store.ts +++ b/packages/editor-ui/src/stores/nodeTypes.store.ts @@ -84,6 +84,11 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, { return nodeType ?? null; }; }, + getNodeVersions() { + return (nodeTypeName: string): number[] => { + return Object.keys(this.nodeTypes[nodeTypeName] ?? {}).map(Number); + }; + }, getCredentialOnlyNodeType() { return (nodeTypeName: string, version?: number): INodeTypeDescription | null => { const credentialName = getCredentialTypeName(nodeTypeName);