mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
fix(core): Fix 431 for large dynamic node parameters (#9384)
This commit is contained in:
parent
96cf41f851
commit
d21ad15c1f
|
@ -395,7 +395,11 @@ describe('NDV', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not retrieve remote options when a parameter value changes', () => {
|
it('should not retrieve remote options when a parameter value changes', () => {
|
||||||
cy.intercept('/rest/dynamic-node-parameters/options?**', cy.spy().as('fetchParameterOptions'));
|
cy.intercept(
|
||||||
|
'POST',
|
||||||
|
'/rest/dynamic-node-parameters/options',
|
||||||
|
cy.spy().as('fetchParameterOptions'),
|
||||||
|
);
|
||||||
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
|
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
|
||||||
// Type something into the field
|
// Type something into the field
|
||||||
ndv.actions.typeIntoParameterInput('otherField', 'test');
|
ndv.actions.typeIntoParameterInput('otherField', 'test');
|
||||||
|
|
|
@ -1,54 +1,27 @@
|
||||||
import type { RequestHandler } from 'express';
|
import type { INodePropertyOptions } from 'n8n-workflow';
|
||||||
import { NextFunction, Response } from 'express';
|
|
||||||
import type {
|
|
||||||
INodeListSearchResult,
|
|
||||||
INodePropertyOptions,
|
|
||||||
ResourceMapperFields,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse } from 'n8n-workflow';
|
||||||
|
|
||||||
import { Get, Middleware, RestController } from '@/decorators';
|
import { Post, RestController } from '@/decorators';
|
||||||
import { getBase } from '@/WorkflowExecuteAdditionalData';
|
import { getBase } from '@/WorkflowExecuteAdditionalData';
|
||||||
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
|
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
|
||||||
import { DynamicNodeParametersRequest } from '@/requests';
|
import { DynamicNodeParametersRequest } from '@/requests';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
|
|
||||||
const assertMethodName: RequestHandler = (req, res, next) => {
|
|
||||||
const { methodName } = req.query as DynamicNodeParametersRequest.BaseRequest['query'];
|
|
||||||
if (!methodName) {
|
|
||||||
throw new BadRequestError('Parameter methodName is required.');
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
@RestController('/dynamic-node-parameters')
|
@RestController('/dynamic-node-parameters')
|
||||||
export class DynamicNodeParametersController {
|
export class DynamicNodeParametersController {
|
||||||
constructor(private readonly service: DynamicNodeParametersService) {}
|
constructor(private readonly service: DynamicNodeParametersService) {}
|
||||||
|
|
||||||
@Middleware()
|
@Post('/options')
|
||||||
parseQueryParams(req: DynamicNodeParametersRequest.BaseRequest, _: Response, next: NextFunction) {
|
|
||||||
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.query;
|
|
||||||
if (!nodeTypeAndVersion) {
|
|
||||||
throw new BadRequestError('Parameter nodeTypeAndVersion is required.');
|
|
||||||
}
|
|
||||||
if (!currentNodeParameters) {
|
|
||||||
throw new BadRequestError('Parameter currentNodeParameters is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
req.params = {
|
|
||||||
nodeTypeAndVersion: jsonParse(nodeTypeAndVersion),
|
|
||||||
currentNodeParameters: jsonParse(currentNodeParameters),
|
|
||||||
credentials: credentials ? jsonParse(credentials) : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns parameter values which normally get loaded from an external API or get generated dynamically */
|
|
||||||
@Get('/options')
|
|
||||||
async getOptions(req: DynamicNodeParametersRequest.Options): Promise<INodePropertyOptions[]> {
|
async getOptions(req: DynamicNodeParametersRequest.Options): Promise<INodePropertyOptions[]> {
|
||||||
const { path, methodName, loadOptions } = req.query;
|
const {
|
||||||
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params;
|
credentials,
|
||||||
|
currentNodeParameters,
|
||||||
|
nodeTypeAndVersion,
|
||||||
|
path,
|
||||||
|
methodName,
|
||||||
|
loadOptions,
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
||||||
|
|
||||||
if (methodName) {
|
if (methodName) {
|
||||||
|
@ -75,13 +48,22 @@ export class DynamicNodeParametersController {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/resource-locator-results', { middlewares: [assertMethodName] })
|
@Post('/resource-locator-results')
|
||||||
async getResourceLocatorResults(
|
async getResourceLocatorResults(req: DynamicNodeParametersRequest.ResourceLocatorResults) {
|
||||||
req: DynamicNodeParametersRequest.ResourceLocatorResults,
|
const {
|
||||||
): Promise<INodeListSearchResult | undefined> {
|
path,
|
||||||
const { path, methodName, filter, paginationToken } = req.query;
|
methodName,
|
||||||
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params;
|
filter,
|
||||||
|
paginationToken,
|
||||||
|
credentials,
|
||||||
|
currentNodeParameters,
|
||||||
|
nodeTypeAndVersion,
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
if (!methodName) throw new BadRequestError('Missing `methodName` in request body');
|
||||||
|
|
||||||
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
||||||
|
|
||||||
return await this.service.getResourceLocatorResults(
|
return await this.service.getResourceLocatorResults(
|
||||||
methodName,
|
methodName,
|
||||||
path,
|
path,
|
||||||
|
@ -94,13 +76,14 @@ export class DynamicNodeParametersController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/resource-mapper-fields', { middlewares: [assertMethodName] })
|
@Post('/resource-mapper-fields')
|
||||||
async getResourceMappingFields(
|
async getResourceMappingFields(req: DynamicNodeParametersRequest.ResourceMapperFields) {
|
||||||
req: DynamicNodeParametersRequest.ResourceMapperFields,
|
const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion } = req.body;
|
||||||
): Promise<ResourceMapperFields | undefined> {
|
|
||||||
const { path, methodName } = req.query;
|
if (!methodName) throw new BadRequestError('Missing `methodName` in request body');
|
||||||
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params;
|
|
||||||
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
||||||
|
|
||||||
return await this.service.getResourceMappingFields(
|
return await this.service.getResourceMappingFields(
|
||||||
methodName,
|
methodName,
|
||||||
path,
|
path,
|
||||||
|
|
|
@ -393,21 +393,17 @@ export declare namespace OAuthRequest {
|
||||||
// /dynamic-node-parameters
|
// /dynamic-node-parameters
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
export declare namespace DynamicNodeParametersRequest {
|
export declare namespace DynamicNodeParametersRequest {
|
||||||
type BaseRequest<QueryParams = {}> = AuthenticatedRequest<
|
type BaseRequest<RequestBody = {}> = AuthenticatedRequest<
|
||||||
{
|
|
||||||
nodeTypeAndVersion: INodeTypeNameVersion;
|
|
||||||
currentNodeParameters: INodeParameters;
|
|
||||||
credentials?: INodeCredentials;
|
|
||||||
},
|
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
path: string;
|
path: string;
|
||||||
nodeTypeAndVersion: string;
|
nodeTypeAndVersion: INodeTypeNameVersion;
|
||||||
currentNodeParameters: string;
|
currentNodeParameters: INodeParameters;
|
||||||
methodName?: string;
|
methodName?: string;
|
||||||
credentials?: string;
|
credentials?: INodeCredentials;
|
||||||
} & QueryParams
|
} & RequestBody,
|
||||||
|
{}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/** GET /dynamic-node-parameters/options */
|
/** GET /dynamic-node-parameters/options */
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
import type { SuperTest, Test } from 'supertest';
|
||||||
|
import { createOwner } from '../shared/db/users';
|
||||||
|
import { setupTestServer } from '../shared/utils';
|
||||||
|
import * as AdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
|
import type {
|
||||||
|
INodeListSearchResult,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
ResourceMapperFields,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
|
||||||
|
|
||||||
|
describe('DynamicNodeParametersController', () => {
|
||||||
|
const testServer = setupTestServer({ endpointGroups: ['dynamic-node-parameters'] });
|
||||||
|
let ownerAgent: SuperTest<Test>;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const owner = await createOwner();
|
||||||
|
ownerAgent = testServer.authAgentFor(owner);
|
||||||
|
});
|
||||||
|
|
||||||
|
const commonRequestParams = {
|
||||||
|
credentials: {},
|
||||||
|
currentNodeParameters: {},
|
||||||
|
nodeTypeAndVersion: {},
|
||||||
|
path: 'path',
|
||||||
|
methodName: 'methodName',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('POST /dynamic-node-parameters/options', () => {
|
||||||
|
jest.spyOn(AdditionalData, 'getBase').mockResolvedValue(mock<IWorkflowExecuteAdditionalData>());
|
||||||
|
|
||||||
|
it('should take params via body', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(DynamicNodeParametersService.prototype, 'getOptionsViaMethodName')
|
||||||
|
.mockResolvedValue([]);
|
||||||
|
|
||||||
|
await ownerAgent
|
||||||
|
.post('/dynamic-node-parameters/options')
|
||||||
|
.send({
|
||||||
|
...commonRequestParams,
|
||||||
|
loadOptions: 'loadOptions',
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /dynamic-node-parameters/resource-locator-results', () => {
|
||||||
|
it('should take params via body', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(DynamicNodeParametersService.prototype, 'getResourceLocatorResults')
|
||||||
|
.mockResolvedValue(mock<INodeListSearchResult>());
|
||||||
|
|
||||||
|
await ownerAgent
|
||||||
|
.post('/dynamic-node-parameters/resource-locator-results')
|
||||||
|
.send({
|
||||||
|
...commonRequestParams,
|
||||||
|
filter: 'filter',
|
||||||
|
paginationToken: 'paginationToken',
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /dynamic-node-parameters/resource-mapper-fields', () => {
|
||||||
|
it('should take params via body', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(DynamicNodeParametersService.prototype, 'getResourceMappingFields')
|
||||||
|
.mockResolvedValue(mock<ResourceMapperFields>());
|
||||||
|
|
||||||
|
await ownerAgent
|
||||||
|
.post('/dynamic-node-parameters/resource-mapper-fields')
|
||||||
|
.send({
|
||||||
|
...commonRequestParams,
|
||||||
|
loadOptions: 'loadOptions',
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -35,7 +35,8 @@ type EndpointGroup =
|
||||||
| 'invitations'
|
| 'invitations'
|
||||||
| 'debug'
|
| 'debug'
|
||||||
| 'project'
|
| 'project'
|
||||||
| 'role';
|
| 'role'
|
||||||
|
| 'dynamic-node-parameters';
|
||||||
|
|
||||||
export interface SetupProps {
|
export interface SetupProps {
|
||||||
endpointGroups?: EndpointGroup[];
|
endpointGroups?: EndpointGroup[];
|
||||||
|
|
|
@ -267,6 +267,13 @@ export const setupTestServer = ({
|
||||||
const { RoleController } = await import('@/controllers/role.controller');
|
const { RoleController } = await import('@/controllers/role.controller');
|
||||||
registerController(app, RoleController);
|
registerController(app, RoleController);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'dynamic-node-parameters':
|
||||||
|
const { DynamicNodeParametersController } = await import(
|
||||||
|
'@/controllers/dynamicNodeParameters.controller'
|
||||||
|
);
|
||||||
|
registerController(app, DynamicNodeParametersController);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export async function getNodeParameterOptions(
|
||||||
context: IRestApiContext,
|
context: IRestApiContext,
|
||||||
sendData: DynamicNodeParameters.OptionsRequest,
|
sendData: DynamicNodeParameters.OptionsRequest,
|
||||||
): Promise<INodePropertyOptions[]> {
|
): Promise<INodePropertyOptions[]> {
|
||||||
return await makeRestApiRequest(context, 'GET', '/dynamic-node-parameters/options', sendData);
|
return await makeRestApiRequest(context, 'POST', '/dynamic-node-parameters/options', sendData);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getResourceLocatorResults(
|
export async function getResourceLocatorResults(
|
||||||
|
@ -40,7 +40,7 @@ export async function getResourceLocatorResults(
|
||||||
): Promise<INodeListSearchResult> {
|
): Promise<INodeListSearchResult> {
|
||||||
return await makeRestApiRequest(
|
return await makeRestApiRequest(
|
||||||
context,
|
context,
|
||||||
'GET',
|
'POST',
|
||||||
'/dynamic-node-parameters/resource-locator-results',
|
'/dynamic-node-parameters/resource-locator-results',
|
||||||
sendData,
|
sendData,
|
||||||
);
|
);
|
||||||
|
@ -52,7 +52,7 @@ export async function getResourceMapperFields(
|
||||||
): Promise<ResourceMapperFields> {
|
): Promise<ResourceMapperFields> {
|
||||||
return await makeRestApiRequest(
|
return await makeRestApiRequest(
|
||||||
context,
|
context,
|
||||||
'GET',
|
'POST',
|
||||||
'/dynamic-node-parameters/resource-mapper-fields',
|
'/dynamic-node-parameters/resource-mapper-fields',
|
||||||
sendData,
|
sendData,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue