feat(Respond to Webhook Node): Additional output branch - Response (#13734)

This commit is contained in:
Michael Kret
2025-05-13 09:51:45 +03:00
committed by GitHub
parent 6be129c08b
commit 40fb4a2efc
5 changed files with 103 additions and 4 deletions

View File

@@ -216,6 +216,7 @@ export const GITHUB_NODE_TYPE = 'n8n-nodes-base.github';
export const SLACK_TRIGGER_NODE_TYPE = 'n8n-nodes-base.slackTrigger';
export const TELEGRAM_TRIGGER_NODE_TYPE = 'n8n-nodes-base.telegramTrigger';
export const FACEBOOK_LEAD_ADS_TRIGGER_NODE_TYPE = 'n8n-nodes-base.facebookLeadAdsTrigger';
export const RESPOND_TO_WEBHOOK_NODE_TYPE = 'n8n-nodes-base.respondToWebhook';
export const CREDENTIAL_ONLY_NODE_PREFIX = 'n8n-creds-base';
export const CREDENTIAL_ONLY_HTTP_NODE_VERSION = 4.1;
@@ -795,6 +796,7 @@ export const NODE_TYPES_EXCLUDED_FROM_OUTPUT_NAME_APPEND = [
FILTER_NODE_TYPE,
SWITCH_NODE_TYPE,
REMOVE_DUPLICATES_NODE_TYPE,
RESPOND_TO_WEBHOOK_NODE_TYPE,
];
type ClearOutgoingConnectonsEvents = {

View File

@@ -22,6 +22,7 @@ import {
} from 'n8n-workflow';
import type { Readable } from 'stream';
import { configuredOutputs } from './utils';
import { formatPrivateKey, generatePairedItemData } from '../../utils/utilities';
const respondWithProperty: INodeProperties = {
@@ -80,13 +81,13 @@ export class RespondToWebhook implements INodeType {
icon: { light: 'file:webhook.svg', dark: 'file:webhook.dark.svg' },
name: 'respondToWebhook',
group: ['transform'],
version: [1, 1.1, 1.2],
version: [1, 1.1, 1.2, 1.3],
description: 'Returns data for Webhook',
defaults: {
name: 'Respond to Webhook',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
outputs: `={{(${configuredOutputs})($nodeVersion)}}`,
credentials: [
{
name: 'jwtAuth',
@@ -318,6 +319,8 @@ export class RespondToWebhook implements INodeType {
WAIT_NODE_TYPE,
];
let response: IN8nHttpFullResponse;
try {
if (nodeVersion >= 1.1) {
const connectedNodes = this.getParentNodes(this.getNode().name);
@@ -445,7 +448,7 @@ export class RespondToWebhook implements INodeType {
);
}
const response: IN8nHttpFullResponse = {
response = {
body: responseBody,
headers,
statusCode,
@@ -465,6 +468,10 @@ export class RespondToWebhook implements INodeType {
throw error;
}
if (nodeVersion >= 1.3) {
return [items, [{ json: { response } }]];
}
return [items];
}
}

View File

@@ -202,12 +202,15 @@ describe('RespondToWebhook Node', () => {
});
mockExecuteFunctions.sendResponse.mockReturnValue();
await expect(respondToWebhook.execute.call(mockExecuteFunctions)).resolves.not.toThrow();
const result = await respondToWebhook.execute.call(mockExecuteFunctions);
expect(mockExecuteFunctions.sendResponse).toHaveBeenCalledWith({
body: inputItems.map((item) => item.json),
headers: {},
statusCode: 200,
});
expect(result).toHaveLength(1);
expect(result[0]).toHaveLength(2);
expect(result[0]).toEqual(inputItems);
});
it('should correctly return binary', async () => {
@@ -259,5 +262,52 @@ describe('RespondToWebhook Node', () => {
]);
expect(mockExecuteFunctions.sendResponse).not.toHaveBeenCalled();
});
it('should have two outputs in version 1.3', async () => {
const inputItems = [{ json: { index: 0, input: true } }, { json: { index: 1, input: true } }];
mockExecuteFunctions.getInputData.mockReturnValue(inputItems);
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 1.3 }));
mockExecuteFunctions.getParentNodes.mockReturnValue([
mock<NodeTypeAndVersion>({ type: WAIT_NODE_TYPE }),
]);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName) => {
if (paramName === 'respondWith') return 'redirect';
if (paramName === 'redirectURL') return 'n8n.io';
if (paramName === 'options') return {};
});
mockExecuteFunctions.sendResponse.mockReturnValue();
const result = await respondToWebhook.execute.call(mockExecuteFunctions);
expect(result).toHaveLength(2);
expect(result).toEqual([
[
{
json: {
index: 0,
input: true,
},
},
{
json: {
index: 1,
input: true,
},
},
],
[
{
json: {
response: {
headers: {
location: 'n8n.io',
},
statusCode: 307,
},
},
},
],
]);
});
});
});

View File

@@ -0,0 +1,24 @@
import { configuredOutputs } from '../utils';
describe('configuredOutputs', () => {
it('returns array of objects when version >= 1.3', () => {
const result = configuredOutputs(1.3);
expect(result).toEqual([
{ type: 'main', displayName: 'Input Data' },
{ type: 'main', displayName: 'Response' },
]);
});
it('returns array of objects when version > 1.3', () => {
const result = configuredOutputs(2);
expect(result).toEqual([
{ type: 'main', displayName: 'Input Data' },
{ type: 'main', displayName: 'Response' },
]);
});
it('returns ["main"] when version < 1.3', () => {
const result = configuredOutputs(1.2);
expect(result).toEqual(['main']);
});
});

View File

@@ -0,0 +1,16 @@
export const configuredOutputs = (version: number) => {
if (version >= 1.3) {
return [
{
type: 'main',
displayName: 'Input Data',
},
{
type: 'main',
displayName: 'Response',
},
];
}
return ['main'];
};