feat(editor): Add node name and version to NDV node settings (#7731)

<img width="580" alt="image"
src="https://github.com/n8n-io/n8n/assets/8850410/85ac1c6e-9116-436a-a2ed-8d0ac162a287">

<img width="580" alt="image"
src="https://github.com/n8n-io/n8n/assets/8850410/08b37377-cef5-4f80-80c0-addfdd37f728">

---------

Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
Elias Meire 2023-11-23 18:28:07 +01:00 committed by GitHub
parent 902beffce5
commit da851986f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 3 deletions

View file

@ -1,7 +1,6 @@
import { WorkflowPage, NDV } from '../pages';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { getPopper, getVisiblePopper, getVisibleSelect } from '../utils'; import { NDV, WorkflowPage } from '../pages';
import { META_KEY } from '../constants'; import { getVisibleSelect } from '../utils';
const workflowPage = new WorkflowPage(); const workflowPage = new WorkflowPage();
const ndv = new NDV(); const ndv = new NDV();
@ -368,4 +367,23 @@ describe('NDV', () => {
// Should call the endpoint only once (on mount), not for every keystroke // Should call the endpoint only once (on mount), not for every keystroke
cy.get('@fetchParameterOptions').should('have.been.calledOnce'); 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();
});
}); });

View file

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

View file

@ -81,6 +81,8 @@ export class NDV extends BasePage {
sqlEditorContainer: () => cy.getByTestId('sql-editor-container'), sqlEditorContainer: () => cy.getByTestId('sql-editor-container'),
searchInput: () => cy.getByTestId('ndv-search'), searchInput: () => cy.getByTestId('ndv-search'),
pagination: () => cy.getByTestId('ndv-data-pagination'), pagination: () => cy.getByTestId('ndv-data-pagination'),
nodeVersion: () => cy.getByTestId('node-version'),
nodeSettingsTab: () => cy.getByTestId('tab-settings'),
}; };
actions = { actions = {
@ -225,6 +227,10 @@ export class NDV extends BasePage {
}); });
this.actions.validateExpressionPreview(fieldName, `node doesn't exist`); this.actions.validateExpressionPreview(fieldName, `node doesn't exist`);
}, },
openSettings: () => {
this.getters.nodeSettingsTab().click();
},
}; };
} }

View file

@ -35,6 +35,7 @@
<div <div
v-else v-else
:class="{ [$style.tab]: true, [$style.activeTab]: modelValue === option.value }" :class="{ [$style.tab]: true, [$style.activeTab]: modelValue === option.value }"
:data-test-id="`tab-${option.value}`"
@click="() => handleTabClick(option.value)" @click="() => handleTabClick(option.value)"
> >
<n8n-icon v-if="option.icon" :icon="option.icon" size="medium" /> <n8n-icon v-if="option.icon" :icon="option.icon" size="medium" />

View file

@ -152,6 +152,17 @@
@valueChanged="valueChanged" @valueChanged="valueChanged"
@parameterBlur="onParameterBlur" @parameterBlur="onParameterBlur"
/> />
<div class="node-version" data-test-id="node-version">
{{
$locale.baseText('nodeSettings.nodeVersion', {
interpolate: {
node: nodeType?.displayName as string,
version: node.typeVersion.toString(),
},
})
}}
<span>({{ nodeVersionTag }})</span>
</div>
</div> </div>
</div> </div>
<n8n-block-ui :show="blockUI" /> <n8n-block-ui :show="blockUI" />
@ -258,6 +269,29 @@ export default defineComponent({
return ''; 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 { nodeTypeDescription(): string {
if (this.nodeType?.description) { if (this.nodeType?.description) {
const shortNodeType = this.$locale.shortNodeType(this.nodeType.name); const shortNodeType = this.$locale.shortNodeType(this.nodeType.name);
@ -1126,6 +1160,14 @@ export default defineComponent({
top: -25px; 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 { .parameter-value {
input.expression { input.expression {
border-style: dashed; border-style: dashed;

View file

@ -995,6 +995,11 @@
"nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)", "nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)",
"nodeSettings.waitBetweenTries.displayName": "Wait Between Tries (ms)", "nodeSettings.waitBetweenTries.displayName": "Wait Between Tries (ms)",
"nodeSettings.hasForeignCredential": "To edit this node, either:<br/>a) Ask {owner} to share the credential with you, or<br/>b) Duplicate the node and add your own credential", "nodeSettings.hasForeignCredential": "To edit this node, either:<br/>a) Ask {owner} to share the credential with you, or<br/>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.openNodesPanel": "Open nodes panel",
"nodeView.addATriggerNodeFirst": "Add a <a data-action='showNodeCreator'>Trigger Node</a> first", "nodeView.addATriggerNodeFirst": "Add a <a data-action='showNodeCreator'>Trigger Node</a> first",
"nodeView.addOrEnableTriggerNode": "<a data-action='showNodeCreator'>Add</a> or enable a Trigger node to execute the workflow", "nodeView.addOrEnableTriggerNode": "<a data-action='showNodeCreator'>Add</a> or enable a Trigger node to execute the workflow",

View file

@ -84,6 +84,11 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
return nodeType ?? null; return nodeType ?? null;
}; };
}, },
getNodeVersions() {
return (nodeTypeName: string): number[] => {
return Object.keys(this.nodeTypes[nodeTypeName] ?? {}).map(Number);
};
},
getCredentialOnlyNodeType() { getCredentialOnlyNodeType() {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => { return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
const credentialName = getCredentialTypeName(nodeTypeName); const credentialName = getCredentialTypeName(nodeTypeName);