mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Improve manual description in nodes as tools (#15373)
This commit is contained in:
@@ -325,7 +325,6 @@ describe('LoadNodesAndCredentials', () => {
|
|||||||
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
||||||
displayName: 'Description',
|
displayName: 'Description',
|
||||||
name: 'toolDescription',
|
name: 'toolDescription',
|
||||||
placeholder: 'e.g. A test node',
|
|
||||||
required: true,
|
required: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
@@ -380,7 +379,6 @@ describe('LoadNodesAndCredentials', () => {
|
|||||||
typeOptions: { rows: 2 },
|
typeOptions: { rows: 2 },
|
||||||
description:
|
description:
|
||||||
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
||||||
placeholder: 'e.g. A test node',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
codex: {
|
codex: {
|
||||||
|
|||||||
@@ -484,7 +484,6 @@ export class LoadNodesAndCredentials {
|
|||||||
typeOptions: { rows: 2 },
|
typeOptions: { rows: 2 },
|
||||||
description:
|
description:
|
||||||
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
||||||
placeholder: `e.g. ${item.description.description}`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
item.description.properties.unshift(descProp);
|
item.description.properties.unshift(descProp);
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ describe('createNodeAsTool', () => {
|
|||||||
description: {
|
description: {
|
||||||
name: 'TestNode',
|
name: 'TestNode',
|
||||||
description: 'Test node description',
|
description: 'Test node description',
|
||||||
|
defaults: {
|
||||||
|
name: 'Test Node',
|
||||||
|
},
|
||||||
|
properties: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const node = mock<INode>({ name: 'Test_Node' });
|
const node = mock<INode>({ name: 'Test_Node' });
|
||||||
@@ -54,9 +58,7 @@ describe('createNodeAsTool', () => {
|
|||||||
|
|
||||||
expect(tool).toBeDefined();
|
expect(tool).toBeDefined();
|
||||||
expect(tool.name).toBe('Test_Node');
|
expect(tool.name).toBe('Test_Node');
|
||||||
expect(tool.description).toBe(
|
expect(tool.description).toBe('testOperation testResource in Test Node');
|
||||||
'Test node description\n Resource: testResource\n Operation: testOperation',
|
|
||||||
);
|
|
||||||
expect(tool.schema).toBeDefined();
|
expect(tool.schema).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { DynamicStructuredTool } from '@langchain/core/tools';
|
import { DynamicStructuredTool } from '@langchain/core/tools';
|
||||||
import { generateZodSchema, NodeOperationError, traverseNodeParameters } from 'n8n-workflow';
|
|
||||||
import type { IDataObject, INode, INodeType, FromAIArgument } from 'n8n-workflow';
|
import type { IDataObject, INode, INodeType, FromAIArgument } from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
generateZodSchema,
|
||||||
|
NodeOperationError,
|
||||||
|
traverseNodeParameters,
|
||||||
|
NodeHelpers,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export type CreateNodeAsToolOptions = {
|
export type CreateNodeAsToolOptions = {
|
||||||
@@ -83,31 +88,6 @@ function getSchema(node: INode) {
|
|||||||
return z.object(schemaObj).required();
|
return z.object(schemaObj).required();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a description for a node based on the provided parameters.
|
|
||||||
* @param node The node type.
|
|
||||||
* @param nodeParameters The parameters of the node.
|
|
||||||
* @returns A string description for the node.
|
|
||||||
*/
|
|
||||||
function makeDescription(node: INode, nodeType: INodeType): string {
|
|
||||||
if (node.parameters.descriptionType === 'auto') {
|
|
||||||
const resource = node.parameters.resource as string;
|
|
||||||
const operation = node.parameters.operation as string;
|
|
||||||
let description = nodeType.description.description;
|
|
||||||
if (resource) {
|
|
||||||
description += `\n Resource: ${resource}`;
|
|
||||||
}
|
|
||||||
if (operation) {
|
|
||||||
description += `\n Operation: ${operation}`;
|
|
||||||
}
|
|
||||||
return description.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users can define custom descriptions when `descriptionType` is manual or not included
|
|
||||||
// in the node's properties, e.g. when the node has neither `operation` or `resource`
|
|
||||||
return (node.parameters.toolDescription as string) ?? nodeType.description.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a node name to a valid tool name by replacing special characters with underscores
|
* Converts a node name to a valid tool name by replacing special characters with underscores
|
||||||
* and collapsing consecutive underscores into a single one.
|
* and collapsing consecutive underscores into a single one.
|
||||||
@@ -123,8 +103,9 @@ export function nodeNameToToolName(node: INode): string {
|
|||||||
*/
|
*/
|
||||||
function createTool(options: CreateNodeAsToolOptions) {
|
function createTool(options: CreateNodeAsToolOptions) {
|
||||||
const { node, nodeType, handleToolInvocation } = options;
|
const { node, nodeType, handleToolInvocation } = options;
|
||||||
|
|
||||||
const schema = getSchema(node);
|
const schema = getSchema(node);
|
||||||
const description = makeDescription(node, nodeType);
|
const description = NodeHelpers.getToolDescriptionForNode(node, nodeType);
|
||||||
const nodeName = nodeNameToToolName(node);
|
const nodeName = nodeNameToToolName(node);
|
||||||
const name = nodeName || nodeType.description.name;
|
const name = nodeName || nodeType.description.name;
|
||||||
|
|
||||||
|
|||||||
@@ -497,6 +497,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
|
|||||||
_node,
|
_node,
|
||||||
nodeType,
|
nodeType,
|
||||||
);
|
);
|
||||||
|
|
||||||
const oldNodeParameters = Object.assign({}, nodeParameters);
|
const oldNodeParameters = Object.assign({}, nodeParameters);
|
||||||
|
|
||||||
// Copy the data because it is the data of vuex so make sure that
|
// Copy the data because it is the data of vuex so make sure that
|
||||||
@@ -547,6 +548,18 @@ const valueChanged = (parameterData: IUpdateInformation) => {
|
|||||||
nodeType,
|
nodeType,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isToolNode.value) {
|
||||||
|
const updatedDescription = NodeHelpers.getUpdatedToolDescription(
|
||||||
|
props.nodeType,
|
||||||
|
nodeParameters,
|
||||||
|
node.value?.parameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updatedDescription && nodeParameters) {
|
||||||
|
nodeParameters.toolDescription = updatedDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(nodeParameters as object)) {
|
for (const key of Object.keys(nodeParameters as object)) {
|
||||||
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
||||||
setValue(`parameters.${key}`, nodeParameters[key] as string);
|
setValue(`parameters.${key}`, nodeParameters[key] as string);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import type {
|
|||||||
import { validateFilterParameter } from './NodeParameters/FilterParameter';
|
import { validateFilterParameter } from './NodeParameters/FilterParameter';
|
||||||
import {
|
import {
|
||||||
isFilterValue,
|
isFilterValue,
|
||||||
|
isINodePropertyOptionsList,
|
||||||
isResourceLocatorValue,
|
isResourceLocatorValue,
|
||||||
isResourceMapperValue,
|
isResourceMapperValue,
|
||||||
isValidResourceLocatorParameterValue,
|
isValidResourceLocatorParameterValue,
|
||||||
@@ -1569,6 +1570,91 @@ export function isNodeWithWorkflowSelector(node: INode) {
|
|||||||
return [EXECUTE_WORKFLOW_NODE_TYPE, WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE].includes(node.type);
|
return [EXECUTE_WORKFLOW_NODE_TYPE, WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE].includes(node.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a human-readable description for a node based on its parameters and type definition.
|
||||||
|
*
|
||||||
|
* This function creates a descriptive string that represents what the node does,
|
||||||
|
* based on its resource, operation, and node type information. The description is
|
||||||
|
* formatted in one of the following ways:
|
||||||
|
*
|
||||||
|
* 1. "{action} in {displayName}" if the operation has a defined action
|
||||||
|
* 2. "{operation} {resource} in {displayName}" if resource and operation exist
|
||||||
|
* 3. The node type's description field as a fallback
|
||||||
|
*/
|
||||||
|
export function makeDescription(
|
||||||
|
nodeParameters: INodeParameters,
|
||||||
|
nodeTypeDescription: INodeTypeDescription,
|
||||||
|
): string {
|
||||||
|
let description = '';
|
||||||
|
const resource = nodeParameters.resource as string;
|
||||||
|
const operation = nodeParameters.operation as string;
|
||||||
|
const nodeTypeOperation = nodeTypeDescription.properties.find(
|
||||||
|
(p) => p.name === 'operation' && p.displayOptions?.show?.resource?.includes(resource),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nodeTypeOperation?.options && isINodePropertyOptionsList(nodeTypeOperation.options)) {
|
||||||
|
const foundOperation = nodeTypeOperation.options.find((option) => option.value === operation);
|
||||||
|
if (foundOperation?.action) {
|
||||||
|
description = `${foundOperation.action} in ${nodeTypeDescription.defaults.name}`;
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!description && resource && operation) {
|
||||||
|
description = `${operation} ${resource} in ${nodeTypeDescription.defaults.name}`;
|
||||||
|
} else {
|
||||||
|
description = nodeTypeDescription.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a tool description should be updated and returns the new description if needed.
|
||||||
|
* Returns undefined if no update is needed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getUpdatedToolDescription = (
|
||||||
|
currentNodeType: INodeTypeDescription | null,
|
||||||
|
newParameters: INodeParameters | null,
|
||||||
|
currentParameters?: INodeParameters,
|
||||||
|
) => {
|
||||||
|
if (!currentNodeType) return;
|
||||||
|
|
||||||
|
if (newParameters?.descriptionType === 'manual' && currentParameters) {
|
||||||
|
const previousDescription = makeDescription(currentParameters, currentNodeType);
|
||||||
|
const newDescription = makeDescription(newParameters, currentNodeType);
|
||||||
|
|
||||||
|
if (
|
||||||
|
newParameters.toolDescription === previousDescription ||
|
||||||
|
!newParameters.toolDescription?.toString().trim() ||
|
||||||
|
newParameters.toolDescription === currentNodeType.description
|
||||||
|
) {
|
||||||
|
return newDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a tool description for a given node based on its parameters and type.
|
||||||
|
*/
|
||||||
|
export function getToolDescriptionForNode(node: INode, nodeType: INodeType): string {
|
||||||
|
let toolDescription;
|
||||||
|
if (
|
||||||
|
node.parameters.descriptionType === 'auto' ||
|
||||||
|
!node?.parameters.toolDescription?.toString().trim()
|
||||||
|
) {
|
||||||
|
toolDescription = makeDescription(node.parameters, nodeType.description);
|
||||||
|
} else if (node?.parameters.toolDescription) {
|
||||||
|
toolDescription = node.parameters.toolDescription;
|
||||||
|
} else {
|
||||||
|
toolDescription = nodeType.description.description;
|
||||||
|
}
|
||||||
|
return toolDescription as string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to retrieve the ID of a subworkflow from a execute workflow node.
|
* Attempts to retrieve the ID of a subworkflow from a execute workflow node.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { INodeType } from '@/index';
|
||||||
import {
|
import {
|
||||||
NodeConnectionTypes,
|
NodeConnectionTypes,
|
||||||
type NodeConnectionType,
|
type NodeConnectionType,
|
||||||
@@ -14,6 +15,9 @@ import {
|
|||||||
isTriggerNode,
|
isTriggerNode,
|
||||||
isExecutable,
|
isExecutable,
|
||||||
displayParameter,
|
displayParameter,
|
||||||
|
makeDescription,
|
||||||
|
getUpdatedToolDescription,
|
||||||
|
getToolDescriptionForNode,
|
||||||
} from '@/NodeHelpers';
|
} from '@/NodeHelpers';
|
||||||
import type { Workflow } from '@/Workflow';
|
import type { Workflow } from '@/Workflow';
|
||||||
|
|
||||||
@@ -4774,4 +4778,472 @@ describe('NodeHelpers', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('makeDescription', () => {
|
||||||
|
let mockNodeTypeDescription: INodeTypeDescription;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange a basic mock node type description
|
||||||
|
mockNodeTypeDescription = {
|
||||||
|
displayName: 'Test Node',
|
||||||
|
name: 'testNode',
|
||||||
|
icon: 'fa:test',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
description: 'This is a test node',
|
||||||
|
defaults: {
|
||||||
|
name: 'Test Node',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
properties: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return action-based description when action is available', () => {
|
||||||
|
// Arrange
|
||||||
|
const nodeParameters: INodeParameters = {
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockNodeTypeDescription.properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
action: 'Create a new user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = makeDescription(nodeParameters, mockNodeTypeDescription);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('Create a new user in Test Node');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return resource-operation-based description when action is not available', () => {
|
||||||
|
// Arrange
|
||||||
|
const nodeParameters: INodeParameters = {
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockNodeTypeDescription.properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
// No action property
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = makeDescription(nodeParameters, mockNodeTypeDescription);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('create user in Test Node');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return default description when resource or operation is missing', () => {
|
||||||
|
// Arrange
|
||||||
|
const nodeParameters: INodeParameters = {
|
||||||
|
// No resource or operation
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = makeDescription(nodeParameters, mockNodeTypeDescription);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('This is a test node');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle case where nodeTypeOperation is not found', () => {
|
||||||
|
// Arrange
|
||||||
|
const nodeParameters: INodeParameters = {
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockNodeTypeDescription.properties = [
|
||||||
|
// No matching operation property
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = makeDescription(nodeParameters, mockNodeTypeDescription);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('create user in Test Node');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle case where options are not a list of INodePropertyOptions', () => {
|
||||||
|
// Arrange
|
||||||
|
const nodeParameters: INodeParameters = {
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockNodeTypeDescription.properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Options are not INodePropertyOptions[]
|
||||||
|
options: [
|
||||||
|
//@ts-expect-error
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = makeDescription(nodeParameters, mockNodeTypeDescription);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('create user in Test Node');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUpdatedToolDescription', () => {
|
||||||
|
let mockNodeTypeDescription: INodeTypeDescription;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange a basic mock node type description
|
||||||
|
mockNodeTypeDescription = {
|
||||||
|
displayName: 'Test Node',
|
||||||
|
name: 'testNode',
|
||||||
|
icon: 'fa:test',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
description: 'This is a test node',
|
||||||
|
defaults: {
|
||||||
|
name: 'Test Node',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
properties: [],
|
||||||
|
usableAsTool: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return undefined when descriptionType is not manual', () => {
|
||||||
|
// Arrange
|
||||||
|
const newParameters: INodeParameters = {
|
||||||
|
descriptionType: 'automatic',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
const currentParameters: INodeParameters = {
|
||||||
|
descriptionType: 'automatic',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getUpdatedToolDescription(
|
||||||
|
mockNodeTypeDescription,
|
||||||
|
newParameters,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return new description when toolDescription matches previous description', () => {
|
||||||
|
// Arrange
|
||||||
|
mockNodeTypeDescription.properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
action: 'Create a new user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'update',
|
||||||
|
toolDescription: 'Create a new user in Test Node', // Matches the previous description
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getUpdatedToolDescription(
|
||||||
|
mockNodeTypeDescription,
|
||||||
|
newParameters,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('update user in Test Node');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return new description when toolDescription matches node type description', () => {
|
||||||
|
// Arrange
|
||||||
|
const currentParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'update',
|
||||||
|
toolDescription: 'This is a test node', // Matches the node type description
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getUpdatedToolDescription(
|
||||||
|
mockNodeTypeDescription,
|
||||||
|
newParameters,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('update user in Test Node');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return undefined when toolDescription is custom', () => {
|
||||||
|
// Arrange
|
||||||
|
const currentParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'update',
|
||||||
|
toolDescription: 'My custom description', // Custom description
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getUpdatedToolDescription(
|
||||||
|
mockNodeTypeDescription,
|
||||||
|
newParameters,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return undefined for null inputs', () => {
|
||||||
|
// Act
|
||||||
|
const result = getUpdatedToolDescription(null, null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return new description when toolDescription is empty or whitespace', () => {
|
||||||
|
// Arrange
|
||||||
|
const currentParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newParameters: INodeParameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'update',
|
||||||
|
toolDescription: ' ', // Empty/whitespace description
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getUpdatedToolDescription(
|
||||||
|
mockNodeTypeDescription,
|
||||||
|
newParameters,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('update user in Test Node');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getToolDescriptionForNode', () => {
|
||||||
|
let mockNode: INode;
|
||||||
|
let mockNodeType: INodeType;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange a basic mock node
|
||||||
|
mockNode = {
|
||||||
|
id: 'test-node-id',
|
||||||
|
name: 'Test Node',
|
||||||
|
typeVersion: 1,
|
||||||
|
type: 'test-node-type',
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Arrange a basic mock node type
|
||||||
|
mockNodeType = {
|
||||||
|
description: {
|
||||||
|
displayName: 'Test Node Type',
|
||||||
|
name: 'testNodeType',
|
||||||
|
icon: 'fa:test',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
description: 'This is the default node description',
|
||||||
|
defaults: {
|
||||||
|
name: 'Test Node Type',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
properties: [],
|
||||||
|
},
|
||||||
|
} as INodeType;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should use generated description when descriptionType is auto', () => {
|
||||||
|
// Arrange
|
||||||
|
mockNode.parameters = {
|
||||||
|
descriptionType: 'auto',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockNodeType.description.properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
action: 'Create a new user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getToolDescriptionForNode(mockNode, mockNodeType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('Create a new user in Test Node Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should use generated description when toolDescription is empty', () => {
|
||||||
|
// Arrange
|
||||||
|
mockNode.parameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
toolDescription: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getToolDescriptionForNode(mockNode, mockNodeType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('create user in Test Node Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should use generated description when toolDescription is only whitespace', () => {
|
||||||
|
// Arrange
|
||||||
|
mockNode.parameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
toolDescription: ' ',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getToolDescriptionForNode(mockNode, mockNodeType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('create user in Test Node Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should use custom toolDescription when it exists', () => {
|
||||||
|
// Arrange
|
||||||
|
mockNode.parameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
resource: 'user',
|
||||||
|
operation: 'create',
|
||||||
|
toolDescription: 'My custom description',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getToolDescriptionForNode(mockNode, mockNodeType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('My custom description');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fall back to node type description when toolDescription is undefined', () => {
|
||||||
|
// Arrange
|
||||||
|
mockNode.parameters = {
|
||||||
|
descriptionType: 'manual',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getToolDescriptionForNode(mockNode, mockNodeType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('This is the default node description');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user