fix: Remove Request Options from sub nodes (no-changelog) (#9853)

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Michael Kret 2024-06-25 14:53:31 +03:00 committed by GitHub
parent 2c0df8d467
commit 19213efc30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 118 additions and 31 deletions

View file

@ -25,18 +25,6 @@ describe('useNodeType()', () => {
}); });
}); });
describe('isSubNodeType', () => {
it('identifies sub node type correctly', () => {
const nodeTypeOption = {
name: 'testNodeType',
outputs: ['Main', 'Other'],
} as unknown as SimplifiedNodeType;
const { isSubNodeType } = useNodeType({ nodeType: nodeTypeOption });
expect(isSubNodeType.value).toBe(true);
});
});
describe('isMultipleOutputsNodeType', () => { describe('isMultipleOutputsNodeType', () => {
it('identifies multiple outputs node type correctly', () => { it('identifies multiple outputs node type correctly', () => {
const nodeTypeOption = { const nodeTypeOption = {

View file

@ -3,7 +3,7 @@ import { computed, unref } from 'vue';
import type { INodeTypeDescription } from 'n8n-workflow'; import type { INodeTypeDescription } from 'n8n-workflow';
import type { INodeUi, SimplifiedNodeType } from '@/Interface'; import type { INodeUi, SimplifiedNodeType } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow'; import { NodeHelpers } from 'n8n-workflow';
export function useNodeType( export function useNodeType(
options: { options: {
@ -26,15 +26,7 @@ export function useNodeType(
return null; return null;
}); });
const isSubNodeType = computed(() => { const isSubNodeType = computed(() => NodeHelpers.isSubNodeType(nodeType.value));
if (!nodeType.value?.outputs || typeof nodeType.value?.outputs === 'string') {
return false;
}
const outputTypes = NodeHelpers.getConnectionTypes(nodeType.value?.outputs);
return outputTypes
? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0
: false;
});
const isMultipleOutputsNodeType = computed(() => { const isMultipleOutputsNodeType = computed(() => {
const outputs = nodeType.value?.outputs; const outputs = nodeType.value?.outputs;

View file

@ -530,13 +530,13 @@ describe('mapCanvasConnectionToLegacyConnection', () => {
describe('mapLegacyEndpointsToCanvasConnectionPort', () => { describe('mapLegacyEndpointsToCanvasConnectionPort', () => {
it('should return an empty array and log a warning when inputs is a string', () => { it('should return an empty array and log a warning when inputs is a string', () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const endpoints: INodeTypeDescription['inputs'] = 'some code'; const endpoints: INodeTypeDescription['inputs'] = '={{some code}}';
const result = mapLegacyEndpointsToCanvasConnectionPort(endpoints); const result = mapLegacyEndpointsToCanvasConnectionPort(endpoints);
expect(result).toEqual([]); expect(result).toEqual([]);
expect(consoleWarnSpy).toHaveBeenCalledWith( expect(consoleWarnSpy).toHaveBeenCalledWith(
'Node endpoints have not been evaluated', 'Node endpoints have not been evaluated',
'some code', '={{some code}}',
); );
consoleWarnSpy.mockRestore(); consoleWarnSpy.mockRestore();

View file

@ -1766,21 +1766,24 @@ export interface INodeInputConfiguration {
} }
export interface INodeOutputConfiguration { export interface INodeOutputConfiguration {
category?: string; category?: 'error';
displayName?: string; displayName?: string;
maxConnections?: number;
required?: boolean; required?: boolean;
type: ConnectionTypes; type: ConnectionTypes;
} }
export type ExpressionString = `={{${string}}}`;
export interface INodeTypeDescription extends INodeTypeBaseDescription { export interface INodeTypeDescription extends INodeTypeBaseDescription {
version: number | number[]; version: number | number[];
defaults: INodeParameters; defaults: INodeParameters;
eventTriggerDescription?: string; eventTriggerDescription?: string;
activationMessage?: string; activationMessage?: string;
inputs: Array<ConnectionTypes | INodeInputConfiguration> | string; inputs: Array<ConnectionTypes | INodeInputConfiguration> | ExpressionString;
requiredInputs?: string | number[] | number; // Ony available with executionOrder => "v1" requiredInputs?: string | number[] | number; // Ony available with executionOrder => "v1"
inputNames?: string[]; inputNames?: string[];
outputs: Array<ConnectionTypes | INodeInputConfiguration> | string; outputs: Array<ConnectionTypes | INodeOutputConfiguration> | ExpressionString;
outputNames?: string[]; outputNames?: string[];
properties: INodeProperties[]; properties: INodeProperties[];
credentials?: INodeCredentialDescription[]; credentials?: INodeCredentialDescription[];

View file

@ -10,6 +10,7 @@ import get from 'lodash/get';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import { NodeConnectionType } from './Interfaces';
import type { import type {
FieldType, FieldType,
IContextObject, IContextObject,
@ -351,8 +352,31 @@ const declarativeNodeOptionParameters: INodeProperties = {
], ],
}; };
/**
* Determines if the provided node type has any output types other than the main connection type.
* @param typeDescription The node's type description to check.
*/
export function isSubNodeType(
typeDescription: Pick<INodeTypeDescription, 'outputs'> | null,
): boolean {
if (!typeDescription || !typeDescription.outputs || typeof typeDescription.outputs === 'string') {
return false;
}
const outputTypes = getConnectionTypes(typeDescription.outputs);
return outputTypes
? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0
: false;
}
/** Augments additional `Request Options` property on declarative node-type */
export function applyDeclarativeNodeOptionParameters(nodeType: INodeType): void { export function applyDeclarativeNodeOptionParameters(nodeType: INodeType): void {
if (nodeType.execute || nodeType.trigger || nodeType.webhook || nodeType.description.polling) { if (
nodeType.execute ||
nodeType.trigger ||
nodeType.webhook ||
nodeType.description.polling ||
isSubNodeType(nodeType.description)
) {
return; return;
} }

View file

@ -1,7 +1,18 @@
import type { INode, INodeParameters, INodeProperties, INodeTypeDescription } from '@/Interfaces'; import type {
INode,
INodeParameters,
INodeProperties,
INodeType,
INodeTypeDescription,
} from '@/Interfaces';
import type { Workflow } from '@/Workflow'; import type { Workflow } from '@/Workflow';
import {
import { getNodeParameters, getNodeHints, isSingleExecution } from '@/NodeHelpers'; getNodeParameters,
getNodeHints,
isSingleExecution,
isSubNodeType,
applyDeclarativeNodeOptionParameters,
} from '@/NodeHelpers';
describe('NodeHelpers', () => { describe('NodeHelpers', () => {
describe('getNodeParameters', () => { describe('getNodeParameters', () => {
@ -3528,6 +3539,7 @@ describe('NodeHelpers', () => {
expect(hints).toHaveLength(1); expect(hints).toHaveLength(1);
}); });
}); });
describe('isSingleExecution', () => { describe('isSingleExecution', () => {
test('should determine based on node parameters if it would be executed once', () => { test('should determine based on node parameters if it would be executed once', () => {
expect(isSingleExecution('n8n-nodes-base.code', {})).toEqual(true); expect(isSingleExecution('n8n-nodes-base.code', {})).toEqual(true);
@ -3555,4 +3567,72 @@ describe('NodeHelpers', () => {
expect(isSingleExecution('n8n-nodes-base.redis', {})).toEqual(true); expect(isSingleExecution('n8n-nodes-base.redis', {})).toEqual(true);
}); });
}); });
describe('isSubNodeType', () => {
const tests: Array<[boolean, Pick<INodeTypeDescription, 'outputs'> | null]> = [
[false, null],
[false, { outputs: '={{random_expression}}' }],
[false, { outputs: [] }],
[false, { outputs: ['main'] }],
[true, { outputs: ['ai_agent'] }],
[true, { outputs: ['main', 'ai_agent'] }],
];
test.each(tests)('should return %p for %o', (expected, nodeType) => {
expect(isSubNodeType(nodeType)).toBe(expected);
});
});
describe('applyDeclarativeNodeOptionParameters', () => {
test.each([
[
'node with execute method',
{
execute: jest.fn(),
description: {
properties: [],
},
},
],
[
'node with trigger method',
{
trigger: jest.fn(),
description: {
properties: [],
},
},
],
[
'node with webhook method',
{
webhook: jest.fn(),
description: {
properties: [],
},
},
],
[
'a polling node-type',
{
description: {
polling: true,
properties: [],
},
},
],
[
'a node-type with a non-main output',
{
description: {
outputs: ['main', 'ai_agent'],
properties: [],
},
},
],
])('should not modify properties on node with %s method', (_, nodeTypeName) => {
const nodeType = nodeTypeName as unknown as INodeType;
applyDeclarativeNodeOptionParameters(nodeType);
expect(nodeType.description.properties).toEqual([]);
});
});
}); });