fix(editor): Show node descriptions for installed community nodes (#34914)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alexander Gekov
2026-08-03 14:48:18 +03:00
committed by GitHub
parent 7eabd74811
commit 0839326a9b
8 changed files with 111 additions and 9 deletions
@@ -14,7 +14,7 @@ export const versionDescription: INodeTypeDescription = {
version: [1, 1.1],
defaultVersion: 1.1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Interact with all models available on Qwen Cloud',
description: 'Message Qwen models, analyze images, and generate images and video',
defaults: {
name: 'Qwen Cloud',
},
@@ -14,7 +14,7 @@ export const versionDescription: INodeTypeDescription = {
group: ['transform'],
version: 1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Interact with Anthropic AI models',
description: 'Message Claude, analyze documents and images, manage files, and work with prompts',
defaults: {
name: 'Anthropic',
},
@@ -17,7 +17,8 @@ export const versionDescription: INodeTypeDescription = {
version: [1, 1.1, 1.2],
defaultVersion: 1.2,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Interact with Google Gemini AI models',
description:
'Message Gemini, analyze documents and audio, generate images and video, and search files',
defaults: {
name: 'Google Gemini',
},
@@ -13,7 +13,7 @@ export const versionDescription: INodeTypeDescription = {
group: ['transform'],
version: 1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Interact with MiniMax AI models',
description: 'Message MiniMax, generate speech, images, and video',
defaults: {
name: 'MiniMax',
},
@@ -11,7 +11,7 @@ export const versionDescription: INodeTypeDescription = {
group: ['transform'],
version: 1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Interact with Moonshot Kimi AI models',
description: 'Message Kimi and analyze images with Moonshot models',
defaults: {
name: 'Moonshot Kimi',
},
@@ -11,7 +11,7 @@ export const versionDescription: INodeTypeDescription = {
group: ['transform'],
version: 1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Interact with Ollama AI models',
description: 'Message local models and analyze images with Ollama',
defaults: {
name: 'Ollama',
},
@@ -1,9 +1,15 @@
import type { Pinia } from 'pinia';
import { createPinia, setActivePinia } from 'pinia';
import { DRAG_EVENT_DATA_KEY, MESSAGE_AN_AGENT_NODE_TYPE } from '@/app/constants';
import {
AI_CATEGORY_OTHER_TOOLS,
DEFAULT_SUBCATEGORY,
DRAG_EVENT_DATA_KEY,
MESSAGE_AN_AGENT_NODE_TYPE,
} from '@/app/constants';
import { createComponentRenderer } from '@/__tests__/render';
import { mockSimplifiedNodeType } from '../../__tests__/utils';
import { useViewStacks } from '../../composables/useViewStacks';
import NodeItem from './NodeItem.vue';
import type { AddedNodesAndConnections } from '@/Interface';
@@ -45,6 +51,10 @@ function dispatchDragStart(element: Element) {
return dataTransfer;
}
function getDescription(container: Element) {
return container.querySelector('[data-test-id="node-creator-item-description"]')?.textContent;
}
describe('NodeItem', () => {
let pinia: Pinia;
@@ -52,6 +62,7 @@ describe('NodeItem', () => {
pinia = createPinia();
setActivePinia(pinia);
vi.clearAllMocks();
useViewStacks().resetViewStacks();
});
it('is draggable and has no action arrow for a regular node', () => {
@@ -113,4 +124,93 @@ describe('NodeItem', () => {
JSON.stringify(addedNodesAndConnections),
);
});
describe('description visibility', () => {
it('shows description for a preview community node in the default subcategory', () => {
const { container } = render({
pinia,
props: {
nodeType: mockSimplifiedNodeType({
name: 'n8n-nodes-preview-firecrawl.firecrawl',
displayName: 'Firecrawl',
description: 'Scrape websites with Firecrawl',
}),
subcategory: DEFAULT_SUBCATEGORY,
},
});
expect(getDescription(container)).toBe('Scrape websites with Firecrawl');
});
it('shows description for an installed community node in the default subcategory', () => {
const { container } = render({
pinia,
props: {
nodeType: mockSimplifiedNodeType({
name: '@mendable/n8n-nodes-firecrawl.firecrawl',
displayName: 'Firecrawl',
description: 'Scrape websites with Firecrawl',
}),
subcategory: DEFAULT_SUBCATEGORY,
},
});
expect(getDescription(container)).toBe('Scrape websites with Firecrawl');
});
it('hides description for a core node in the default subcategory', () => {
const { container } = render({
pinia,
props: {
nodeType: mockSimplifiedNodeType({
name: 'n8n-nodes-base.slack',
displayName: 'Slack',
description: 'Consume Slack API',
}),
subcategory: DEFAULT_SUBCATEGORY,
},
});
expect(getDescription(container)).toBeUndefined();
});
it('shows description for an installed community node in the tools subcategory', () => {
const { container } = render({
pinia,
props: {
nodeType: mockSimplifiedNodeType({
name: '@mendable/n8n-nodes-firecrawl.firecrawl',
displayName: 'Firecrawl',
description: 'Scrape websites with Firecrawl',
}),
subcategory: AI_CATEGORY_OTHER_TOOLS,
},
});
expect(getDescription(container)).toBe('Scrape websites with Firecrawl');
});
it('shows description for a core node in the default subcategory while searching', () => {
useViewStacks().pushViewStack({
title: 'AI Nodes',
mode: 'nodes',
items: [],
search: 'anthr',
});
const { container } = render({
pinia,
props: {
nodeType: mockSimplifiedNodeType({
name: '@n8n/n8n-nodes-langchain.anthropic',
displayName: 'Anthropic',
description: 'Interact with Anthropic AI models',
}),
subcategory: DEFAULT_SUBCATEGORY,
},
});
expect(getDescription(container)).toBe('Interact with Anthropic AI models');
});
});
});
@@ -57,7 +57,7 @@ const draggablePosition = ref({ x: -100, y: -100 });
const draggableDataTransfer = ref(null as Element | null);
const description = computed<string>(() => {
if (isCommunityNodePreview.value) {
if (isCommunityNodePreview.value || isCommunityNode.value) {
return props.nodeType.description;
}
if (isSendAndWaitCategory.value) {
@@ -65,7 +65,8 @@ const description = computed<string>(() => {
}
if (
props.subcategory === DEFAULT_SUBCATEGORY &&
!props.nodeType.name.startsWith(CREDENTIAL_ONLY_NODE_PREFIX)
!props.nodeType.name.startsWith(CREDENTIAL_ONLY_NODE_PREFIX) &&
!activeViewStack.search
) {
return '';
}