diff --git a/packages/frontend/editor-ui/src/__tests__/mocks.ts b/packages/frontend/editor-ui/src/__tests__/mocks.ts index 876fa9b134..a204ee0f99 100644 --- a/packages/frontend/editor-ui/src/__tests__/mocks.ts +++ b/packages/frontend/editor-ui/src/__tests__/mocks.ts @@ -62,6 +62,8 @@ export const mockNodeTypeDescription = ({ codex = undefined, properties = [], group, + hidden, + description, }: { name?: INodeTypeDescription['name']; icon?: INodeTypeDescription['icon']; @@ -72,12 +74,14 @@ export const mockNodeTypeDescription = ({ codex?: INodeTypeDescription['codex']; properties?: INodeTypeDescription['properties']; group?: INodeTypeDescription['group']; + hidden?: INodeTypeDescription['hidden']; + description?: INodeTypeDescription['description']; } = {}) => mock({ name, icon, displayName: name, - description: '', + description: description ?? '', version, defaults: { name, @@ -93,6 +97,7 @@ export const mockNodeTypeDescription = ({ documentationUrl: 'https://docs', iconUrl: 'nodes/test-node/icon.svg', webhooks: undefined, + hidden, }); export const mockLoadedNodeType = (name: string) => diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/__snapshots__/viewsData.spec.ts.snap b/packages/frontend/editor-ui/src/components/Node/NodeCreator/__snapshots__/viewsData.spec.ts.snap new file mode 100644 index 0000000000..f5336a7432 --- /dev/null +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/__snapshots__/viewsData.spec.ts.snap @@ -0,0 +1,134 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`viewsData > AIView > should return ai view with ai transform node 1`] = ` +{ + "items": [ + { + "key": "ai_templates_root", + "properties": { + "description": "See what's possible and get started 5x faster", + "icon": "box-open", + "name": "ai_templates_root", + "tag": { + "text": "Recommended", + "type": "info", + }, + "title": "AI Templates", + "url": "template-repository-url.n8n.io?test=value&utm_user_role=AdvancedAI", + }, + "type": "link", + }, + { + "key": "agent", + "properties": { + "description": "example mock agent node", + "displayName": "agent", + "group": [], + "icon": "fa:pen", + "iconUrl": "nodes/test-node/icon.svg", + "name": "agent", + "title": "agent", + }, + "type": "node", + }, + { + "key": "chain", + "properties": { + "description": "example mock chain node", + "displayName": "chain", + "group": [], + "icon": "fa:pen", + "iconUrl": "nodes/test-node/icon.svg", + "name": "chain", + "title": "chain", + }, + "type": "node", + }, + { + "key": "n8n-nodes-base.aiTransform", + "properties": { + "description": "", + "displayName": "n8n-nodes-base.aiTransform", + "group": [], + "icon": "fa:pen", + "iconUrl": "nodes/test-node/icon.svg", + "name": "n8n-nodes-base.aiTransform", + "title": "n8n-nodes-base.aiTransform", + }, + "type": "node", + }, + { + "key": "AI Other", + "properties": { + "description": "Embeddings, Vector Stores, LLMs and other AI nodes", + "icon": "robot", + "title": "Other AI Nodes", + }, + "type": "view", + }, + ], + "subtitle": "Select an AI Node to add to your workflow", + "title": "AI Nodes", + "value": "AI", +} +`; + +exports[`viewsData > AIView > should return ai view without ai transform node if ask ai is not enabled 1`] = ` +{ + "items": [ + { + "key": "ai_templates_root", + "properties": { + "description": "See what's possible and get started 5x faster", + "icon": "box-open", + "name": "ai_templates_root", + "tag": { + "text": "Recommended", + "type": "info", + }, + "title": "AI Templates", + "url": "template-repository-url.n8n.io?test=value&utm_user_role=AdvancedAI", + }, + "type": "link", + }, + { + "key": "agent", + "properties": { + "description": "example mock agent node", + "displayName": "agent", + "group": [], + "icon": "fa:pen", + "iconUrl": "nodes/test-node/icon.svg", + "name": "agent", + "title": "agent", + }, + "type": "node", + }, + { + "key": "chain", + "properties": { + "description": "example mock chain node", + "displayName": "chain", + "group": [], + "icon": "fa:pen", + "iconUrl": "nodes/test-node/icon.svg", + "name": "chain", + "title": "chain", + }, + "type": "node", + }, + { + "key": "AI Other", + "properties": { + "description": "Embeddings, Vector Stores, LLMs and other AI nodes", + "icon": "robot", + "title": "Other AI Nodes", + }, + "type": "view", + }, + ], + "subtitle": "Select an AI Node to add to your workflow", + "title": "AI Nodes", + "value": "AI", +} +`; diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.spec.ts b/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.spec.ts new file mode 100644 index 0000000000..28327ca5df --- /dev/null +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.spec.ts @@ -0,0 +1,90 @@ +import { setActivePinia } from 'pinia'; +import { createTestingPinia } from '@pinia/testing'; +import { AI_CATEGORY_AGENTS, AI_CATEGORY_CHAINS, AI_TRANSFORM_NODE_TYPE } from '@/constants'; +import type { INodeTypeDescription } from 'n8n-workflow'; +import { START_NODE_TYPE } from 'n8n-workflow'; +import { useSettingsStore } from '@/stores/settings.store'; +import { AIView } from './viewsData'; +import { mockNodeTypeDescription } from '@/__tests__/mocks'; +import { useTemplatesStore } from '@/stores/templates.store'; + +const getNodeType = vi.fn(); + +const aiTransformNode = mockNodeTypeDescription({ name: AI_TRANSFORM_NODE_TYPE }); + +const otherNodes = ( + [ + { name: START_NODE_TYPE }, + { + name: 'agentHidden', + description: 'example mock agent node', + hidden: true, + codex: { subcategories: { AI: [AI_CATEGORY_AGENTS] } }, + }, + { + name: 'agent', + description: 'example mock agent node', + codex: { subcategories: { AI: [AI_CATEGORY_AGENTS] } }, + }, + { + name: 'chainHidden', + description: 'example mock chain node', + hidden: true, + codex: { subcategories: { AI: [AI_CATEGORY_CHAINS] } }, + }, + { + name: 'chain', + description: 'example mock chain node', + codex: { subcategories: { AI: [AI_CATEGORY_CHAINS] } }, + }, + ] as Array> +).map(mockNodeTypeDescription); + +vi.mock('@/stores/nodeTypes.store', () => ({ + useNodeTypesStore: vi.fn(() => ({ + getNodeType, + allLatestNodeTypes: [aiTransformNode, ...otherNodes], + })), +})); + +describe('viewsData', () => { + beforeAll(() => { + setActivePinia(createTestingPinia()); + + const templatesStore = useTemplatesStore(); + + vi.spyOn(templatesStore, 'websiteTemplateRepositoryParameters', 'get').mockImplementation( + () => new URLSearchParams({ test: 'value' }), + ); + vi.spyOn(templatesStore, 'constructTemplateRepositoryURL').mockImplementation( + (params) => `template-repository-url.n8n.io?${params.toString()}`, + ); + getNodeType.mockImplementation((nodeName: string) => { + if (nodeName === AI_TRANSFORM_NODE_TYPE) { + return aiTransformNode; + } + + return null; + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('AIView', () => { + test('should return ai view with ai transform node', () => { + const settingsStore = useSettingsStore(); + vi.spyOn(settingsStore, 'isAskAiEnabled', 'get').mockReturnValue(true); + + expect(AIView([])).toMatchSnapshot(); + }); + + test('should return ai view without ai transform node if ask ai is not enabled', () => { + const settingsStore = useSettingsStore(); + vi.spyOn(settingsStore, 'isAskAiEnabled', 'get').mockReturnValue(false); + + expect(AIView([])).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.ts b/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.ts index ba92d2be48..2920687651 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.ts +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/viewsData.ts @@ -67,6 +67,7 @@ import type { NodeConnectionType } from 'n8n-workflow'; import { useTemplatesStore } from '@/stores/templates.store'; import type { BaseTextKey } from '@/plugins/i18n'; import { camelCase } from 'lodash-es'; +import { useSettingsStore } from '@/stores/settings.store'; export interface NodeViewItemSection { key: string; @@ -149,8 +150,9 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView { const websiteCategoryURL = templatesStore.constructTemplateRepositoryURL(websiteCategoryURLParams); + const askAiEnabled = useSettingsStore().isAskAiEnabled; const aiTransformNode = nodeTypesStore.getNodeType(AI_TRANSFORM_NODE_TYPE); - const transformNode = aiTransformNode ? [getNodeView(aiTransformNode)] : []; + const transformNode = askAiEnabled && aiTransformNode ? [getNodeView(aiTransformNode)] : []; return { value: AI_NODE_CREATOR_VIEW, diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json index 7c5eda4de7..1fc90bcfa6 100644 --- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json @@ -1281,7 +1281,7 @@ "nodeCreator.aiPanel.aiNodes": "AI Nodes", "nodeCreator.aiPanel.aiOtherNodes": "Other AI Nodes", "nodeCreator.aiPanel.aiOtherNodesDescription": "Embeddings, Vector Stores, LLMs and other AI nodes", - "nodeCreator.aiPanel.selectAiNode": "Select an Al Node to add to your workflow", + "nodeCreator.aiPanel.selectAiNode": "Select an AI Node to add to your workflow", "nodeCreator.aiPanel.nodesForAi": "Build autonomous agents, summarize or search documents, etc.", "nodeCreator.aiPanel.newTag": "New", "nodeCreator.aiPanel.langchainAiNodes": "AI",