Skip to content

Commit 00f7990

Browse files
Merge branch 'master' into ADO-2220-agent-assistant-poc
* master: fix(editor): Fix type errors for various utils files (no-changelog) (#9480) fix(editor): Fix design system typecheck errors (no-changelog) (#9447) fix(core): Fix 431 for large dynamic node parameters (#9384) fix(editor): Fix project settings layout (#9475) refactor: Fix type issues for parameter input components (#9449) fix(editor): Fix rundata type errors (no-changelog) (#9443) refactor(editor): Refactor code completions mixins to composables (no-changelog) (#9459) fix(Telegram Node): Revert typo introduced in #8437 (no-changelog) (#9472) refactor(editor): Fix type errors in ResourcesListLayout.vue (no-changelog) (#9461) feat(Telegram Node): Add support for local bot api server (#8437) fix: Init license properly with multi main (no-changelog) (#9467)
2 parents b363cc2 + 0cb977b commit 00f7990

File tree

118 files changed

+2906
-1849
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+2906
-1849
lines changed

cypress/e2e/5-ndv.cy.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,11 @@ describe('NDV', () => {
395395
});
396396

397397
it('should not retrieve remote options when a parameter value changes', () => {
398-
cy.intercept('/rest/dynamic-node-parameters/options?**', cy.spy().as('fetchParameterOptions'));
398+
cy.intercept(
399+
'POST',
400+
'/rest/dynamic-node-parameters/options',
401+
cy.spy().as('fetchParameterOptions'),
402+
);
399403
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
400404
// Type something into the field
401405
ndv.actions.typeIntoParameterInput('otherField', 'test');

cypress/fixtures/Multiple_trigger_node_rerun.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
{
1616
"parameters": {
17-
"url": "https://random-data-api.com/api/v2/users?size=5",
17+
"url": "https://internal.users.n8n.cloud/webhook/random-data-api",
1818
"options": {}
1919
},
2020
"id": "22511d75-ab54-49e1-b8af-08b8b3372373",
@@ -28,7 +28,7 @@
2828
},
2929
{
3030
"parameters": {
31-
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.first_name_reversed = item.json = {\n firstName: item.json.first_name,\n firstnNameReversed: item.json.first_name_BUG.split(\"\").reverse().join(\"\")\n };\n}\n\nreturn $input.all();"
31+
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.first_name_reversed = item.json = {\n firstName: item.json.firstname,\n firstnNameReversed: item.json.firstname.split(\"\").reverse().join(\"\")\n };\n}\n\nreturn $input.all();"
3232
},
3333
"id": "4b66b15a-1685-46c1-a5e3-ebf8cdb11d21",
3434
"name": "do something with them",
@@ -130,4 +130,4 @@
130130
},
131131
"id": "PymcwIrbqgNh3O0K",
132132
"tags": []
133-
}
133+
}

packages/cli/src/License.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ export class License {
6161
return autoRenewEnabled;
6262
}
6363

64-
async init(instanceType: N8nInstanceType = 'main') {
65-
if (this.manager) {
64+
async init(instanceType: N8nInstanceType = 'main', forceRecreate = false) {
65+
if (this.manager && !forceRecreate) {
6666
this.logger.warn('License manager already initialized or shutting down');
6767
return;
6868
}
@@ -375,6 +375,6 @@ export class License {
375375

376376
async reinit() {
377377
this.manager?.reset();
378-
await this.init();
378+
await this.init('main', true);
379379
}
380380
}

packages/cli/src/controllers/dynamicNodeParameters.controller.ts

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,27 @@
1-
import type { RequestHandler } from 'express';
2-
import { NextFunction, Response } from 'express';
3-
import type {
4-
INodeListSearchResult,
5-
INodePropertyOptions,
6-
ResourceMapperFields,
7-
} from 'n8n-workflow';
1+
import type { INodePropertyOptions } from 'n8n-workflow';
82
import { jsonParse } from 'n8n-workflow';
93

10-
import { Get, Middleware, RestController } from '@/decorators';
4+
import { Post, RestController } from '@/decorators';
115
import { getBase } from '@/WorkflowExecuteAdditionalData';
126
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
137
import { DynamicNodeParametersRequest } from '@/requests';
148
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
159

16-
const assertMethodName: RequestHandler = (req, res, next) => {
17-
const { methodName } = req.query as DynamicNodeParametersRequest.BaseRequest['query'];
18-
if (!methodName) {
19-
throw new BadRequestError('Parameter methodName is required.');
20-
}
21-
next();
22-
};
23-
2410
@RestController('/dynamic-node-parameters')
2511
export class DynamicNodeParametersController {
2612
constructor(private readonly service: DynamicNodeParametersService) {}
2713

28-
@Middleware()
29-
parseQueryParams(req: DynamicNodeParametersRequest.BaseRequest, _: Response, next: NextFunction) {
30-
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.query;
31-
if (!nodeTypeAndVersion) {
32-
throw new BadRequestError('Parameter nodeTypeAndVersion is required.');
33-
}
34-
if (!currentNodeParameters) {
35-
throw new BadRequestError('Parameter currentNodeParameters is required.');
36-
}
37-
38-
req.params = {
39-
nodeTypeAndVersion: jsonParse(nodeTypeAndVersion),
40-
currentNodeParameters: jsonParse(currentNodeParameters),
41-
credentials: credentials ? jsonParse(credentials) : undefined,
42-
};
43-
44-
next();
45-
}
46-
47-
/** Returns parameter values which normally get loaded from an external API or get generated dynamically */
48-
@Get('/options')
14+
@Post('/options')
4915
async getOptions(req: DynamicNodeParametersRequest.Options): Promise<INodePropertyOptions[]> {
50-
const { path, methodName, loadOptions } = req.query;
51-
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params;
16+
const {
17+
credentials,
18+
currentNodeParameters,
19+
nodeTypeAndVersion,
20+
path,
21+
methodName,
22+
loadOptions,
23+
} = req.body;
24+
5225
const additionalData = await getBase(req.user.id, currentNodeParameters);
5326

5427
if (methodName) {
@@ -75,13 +48,22 @@ export class DynamicNodeParametersController {
7548
return [];
7649
}
7750

78-
@Get('/resource-locator-results', { middlewares: [assertMethodName] })
79-
async getResourceLocatorResults(
80-
req: DynamicNodeParametersRequest.ResourceLocatorResults,
81-
): Promise<INodeListSearchResult | undefined> {
82-
const { path, methodName, filter, paginationToken } = req.query;
83-
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params;
51+
@Post('/resource-locator-results')
52+
async getResourceLocatorResults(req: DynamicNodeParametersRequest.ResourceLocatorResults) {
53+
const {
54+
path,
55+
methodName,
56+
filter,
57+
paginationToken,
58+
credentials,
59+
currentNodeParameters,
60+
nodeTypeAndVersion,
61+
} = req.body;
62+
63+
if (!methodName) throw new BadRequestError('Missing `methodName` in request body');
64+
8465
const additionalData = await getBase(req.user.id, currentNodeParameters);
66+
8567
return await this.service.getResourceLocatorResults(
8668
methodName,
8769
path,
@@ -94,13 +76,14 @@ export class DynamicNodeParametersController {
9476
);
9577
}
9678

97-
@Get('/resource-mapper-fields', { middlewares: [assertMethodName] })
98-
async getResourceMappingFields(
99-
req: DynamicNodeParametersRequest.ResourceMapperFields,
100-
): Promise<ResourceMapperFields | undefined> {
101-
const { path, methodName } = req.query;
102-
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params;
79+
@Post('/resource-mapper-fields')
80+
async getResourceMappingFields(req: DynamicNodeParametersRequest.ResourceMapperFields) {
81+
const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion } = req.body;
82+
83+
if (!methodName) throw new BadRequestError('Missing `methodName` in request body');
84+
10385
const additionalData = await getBase(req.user.id, currentNodeParameters);
86+
10487
return await this.service.getResourceMappingFields(
10588
methodName,
10689
path,

packages/cli/src/requests.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -434,21 +434,17 @@ export declare namespace OAuthRequest {
434434
// /dynamic-node-parameters
435435
// ----------------------------------
436436
export declare namespace DynamicNodeParametersRequest {
437-
type BaseRequest<QueryParams = {}> = AuthenticatedRequest<
438-
{
439-
nodeTypeAndVersion: INodeTypeNameVersion;
440-
currentNodeParameters: INodeParameters;
441-
credentials?: INodeCredentials;
442-
},
437+
type BaseRequest<RequestBody = {}> = AuthenticatedRequest<
443438
{},
444439
{},
445440
{
446441
path: string;
447-
nodeTypeAndVersion: string;
448-
currentNodeParameters: string;
442+
nodeTypeAndVersion: INodeTypeNameVersion;
443+
currentNodeParameters: INodeParameters;
449444
methodName?: string;
450-
credentials?: string;
451-
} & QueryParams
445+
credentials?: INodeCredentials;
446+
} & RequestBody,
447+
{}
452448
>;
453449

454450
/** GET /dynamic-node-parameters/options */
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import type { SuperTest, Test } from 'supertest';
2+
import { createOwner } from '../shared/db/users';
3+
import { setupTestServer } from '../shared/utils';
4+
import * as AdditionalData from '@/WorkflowExecuteAdditionalData';
5+
import type {
6+
INodeListSearchResult,
7+
IWorkflowExecuteAdditionalData,
8+
ResourceMapperFields,
9+
} from 'n8n-workflow';
10+
import { mock } from 'jest-mock-extended';
11+
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
12+
13+
describe('DynamicNodeParametersController', () => {
14+
const testServer = setupTestServer({ endpointGroups: ['dynamic-node-parameters'] });
15+
let ownerAgent: SuperTest<Test>;
16+
17+
beforeAll(async () => {
18+
const owner = await createOwner();
19+
ownerAgent = testServer.authAgentFor(owner);
20+
});
21+
22+
const commonRequestParams = {
23+
credentials: {},
24+
currentNodeParameters: {},
25+
nodeTypeAndVersion: {},
26+
path: 'path',
27+
methodName: 'methodName',
28+
};
29+
30+
describe('POST /dynamic-node-parameters/options', () => {
31+
jest.spyOn(AdditionalData, 'getBase').mockResolvedValue(mock<IWorkflowExecuteAdditionalData>());
32+
33+
it('should take params via body', async () => {
34+
jest
35+
.spyOn(DynamicNodeParametersService.prototype, 'getOptionsViaMethodName')
36+
.mockResolvedValue([]);
37+
38+
await ownerAgent
39+
.post('/dynamic-node-parameters/options')
40+
.send({
41+
...commonRequestParams,
42+
loadOptions: 'loadOptions',
43+
})
44+
.expect(200);
45+
});
46+
});
47+
48+
describe('POST /dynamic-node-parameters/resource-locator-results', () => {
49+
it('should take params via body', async () => {
50+
jest
51+
.spyOn(DynamicNodeParametersService.prototype, 'getResourceLocatorResults')
52+
.mockResolvedValue(mock<INodeListSearchResult>());
53+
54+
await ownerAgent
55+
.post('/dynamic-node-parameters/resource-locator-results')
56+
.send({
57+
...commonRequestParams,
58+
filter: 'filter',
59+
paginationToken: 'paginationToken',
60+
})
61+
.expect(200);
62+
});
63+
});
64+
65+
describe('POST /dynamic-node-parameters/resource-mapper-fields', () => {
66+
it('should take params via body', async () => {
67+
jest
68+
.spyOn(DynamicNodeParametersService.prototype, 'getResourceMappingFields')
69+
.mockResolvedValue(mock<ResourceMapperFields>());
70+
71+
await ownerAgent
72+
.post('/dynamic-node-parameters/resource-mapper-fields')
73+
.send({
74+
...commonRequestParams,
75+
loadOptions: 'loadOptions',
76+
})
77+
.expect(200);
78+
});
79+
});
80+
});

packages/cli/test/integration/shared/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ type EndpointGroup =
3535
| 'invitations'
3636
| 'debug'
3737
| 'project'
38-
| 'role';
38+
| 'role'
39+
| 'dynamic-node-parameters';
3940

4041
export interface SetupProps {
4142
endpointGroups?: EndpointGroup[];

packages/cli/test/integration/shared/utils/testServer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,13 @@ export const setupTestServer = ({
267267
const { RoleController } = await import('@/controllers/role.controller');
268268
registerController(app, RoleController);
269269
break;
270+
271+
case 'dynamic-node-parameters':
272+
const { DynamicNodeParametersController } = await import(
273+
'@/controllers/dynamicNodeParameters.controller'
274+
);
275+
registerController(app, DynamicNodeParametersController);
276+
break;
270277
}
271278
}
272279
}

packages/core/src/Interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ export namespace n8n {
3737
}
3838
}
3939

40-
export type ExtendedValidationResult = Partial<ValidationResult> & { fieldName?: string };
40+
export type ExtendedValidationResult = ValidationResult & { fieldName?: string };

packages/core/src/NodeExecuteFunctions.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ import type {
9999
WorkflowActivateMode,
100100
WorkflowExecuteMode,
101101
CallbackManager,
102+
INodeParameters,
102103
} from 'n8n-workflow';
103104
import {
104105
ExpressionError,
@@ -2121,13 +2122,12 @@ export function cleanupParameterData(inputData: NodeParameterValueType): void {
21212122
}
21222123

21232124
if (typeof inputData === 'object') {
2124-
type Key = keyof typeof inputData;
2125-
(Object.keys(inputData) as Key[]).forEach((key) => {
2126-
const value = inputData[key];
2125+
Object.keys(inputData).forEach((key) => {
2126+
const value = (inputData as INodeParameters)[key];
21272127
if (typeof value === 'object') {
21282128
if (DateTime.isDateTime(value)) {
21292129
// Is a special luxon date so convert to string
2130-
inputData[key] = value.toString();
2130+
(inputData as INodeParameters)[key] = value.toString();
21312131
} else {
21322132
cleanupParameterData(value);
21332133
}
@@ -2230,28 +2230,30 @@ const validateCollection = (
22302230
return validationResult;
22312231
}
22322232

2233-
for (const value of Array.isArray(validationResult.newValue)
2234-
? (validationResult.newValue as IDataObject[])
2235-
: [validationResult.newValue as IDataObject]) {
2236-
for (const key of Object.keys(value)) {
2237-
if (!validationMap[key]) continue;
2233+
if (validationResult.valid) {
2234+
for (const value of Array.isArray(validationResult.newValue)
2235+
? (validationResult.newValue as IDataObject[])
2236+
: [validationResult.newValue as IDataObject]) {
2237+
for (const key of Object.keys(value)) {
2238+
if (!validationMap[key]) continue;
22382239

2239-
const fieldValidationResult = validateFieldType(key, value[key], validationMap[key].type, {
2240-
valueOptions: validationMap[key].options,
2241-
});
2240+
const fieldValidationResult = validateFieldType(key, value[key], validationMap[key].type, {
2241+
valueOptions: validationMap[key].options,
2242+
});
22422243

2243-
if (!fieldValidationResult.valid) {
2244-
throw new ExpressionError(
2245-
`Invalid input for field '${validationMap[key].displayName}' inside '${propertyDescription.displayName}' in [item ${itemIndex}]`,
2246-
{
2247-
description: fieldValidationResult.errorMessage,
2248-
runIndex,
2249-
itemIndex,
2250-
nodeCause: node.name,
2251-
},
2252-
);
2244+
if (!fieldValidationResult.valid) {
2245+
throw new ExpressionError(
2246+
`Invalid input for field '${validationMap[key].displayName}' inside '${propertyDescription.displayName}' in [item ${itemIndex}]`,
2247+
{
2248+
description: fieldValidationResult.errorMessage,
2249+
runIndex,
2250+
itemIndex,
2251+
nodeCause: node.name,
2252+
},
2253+
);
2254+
}
2255+
value[key] = fieldValidationResult.newValue;
22532256
}
2254-
value[key] = fieldValidationResult.newValue;
22552257
}
22562258
}
22572259

0 commit comments

Comments
 (0)