Skip to content

Commit d21ad15

Browse files
authored
fix(core): Fix 431 for large dynamic node parameters (#9384)
1 parent 96cf41f commit d21ad15

File tree

7 files changed

+137
-66
lines changed

7 files changed

+137
-66
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');

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
@@ -393,21 +393,17 @@ export declare namespace OAuthRequest {
393393
// /dynamic-node-parameters
394394
// ----------------------------------
395395
export declare namespace DynamicNodeParametersRequest {
396-
type BaseRequest<QueryParams = {}> = AuthenticatedRequest<
397-
{
398-
nodeTypeAndVersion: INodeTypeNameVersion;
399-
currentNodeParameters: INodeParameters;
400-
credentials?: INodeCredentials;
401-
},
396+
type BaseRequest<RequestBody = {}> = AuthenticatedRequest<
402397
{},
403398
{},
404399
{
405400
path: string;
406-
nodeTypeAndVersion: string;
407-
currentNodeParameters: string;
401+
nodeTypeAndVersion: INodeTypeNameVersion;
402+
currentNodeParameters: INodeParameters;
408403
methodName?: string;
409-
credentials?: string;
410-
} & QueryParams
404+
credentials?: INodeCredentials;
405+
} & RequestBody,
406+
{}
411407
>;
412408

413409
/** 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/editor-ui/src/api/nodeTypes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export async function getNodeParameterOptions(
3131
context: IRestApiContext,
3232
sendData: DynamicNodeParameters.OptionsRequest,
3333
): Promise<INodePropertyOptions[]> {
34-
return await makeRestApiRequest(context, 'GET', '/dynamic-node-parameters/options', sendData);
34+
return await makeRestApiRequest(context, 'POST', '/dynamic-node-parameters/options', sendData);
3535
}
3636

3737
export async function getResourceLocatorResults(
@@ -40,7 +40,7 @@ export async function getResourceLocatorResults(
4040
): Promise<INodeListSearchResult> {
4141
return await makeRestApiRequest(
4242
context,
43-
'GET',
43+
'POST',
4444
'/dynamic-node-parameters/resource-locator-results',
4545
sendData,
4646
);
@@ -52,7 +52,7 @@ export async function getResourceMapperFields(
5252
): Promise<ResourceMapperFields> {
5353
return await makeRestApiRequest(
5454
context,
55-
'GET',
55+
'POST',
5656
'/dynamic-node-parameters/resource-mapper-fields',
5757
sendData,
5858
);

0 commit comments

Comments
 (0)