Skip to content

Commit 2bf0a39

Browse files
feat(Webhook Node): Setting to enable multiple outputs/methods (#9086)
Co-authored-by: Giulio Andreini <[email protected]>
1 parent f6142ff commit 2bf0a39

File tree

7 files changed

+153
-29
lines changed

7 files changed

+153
-29
lines changed

packages/cli/src/TestWebhooks.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,20 @@ export class TestWebhooks implements IWebhookManager {
238238

239239
for (const webhook of webhooks) {
240240
const key = this.registrations.toKey(webhook);
241-
const isAlreadyRegistered = await this.registrations.get(key);
241+
const registrationByKey = await this.registrations.get(key);
242242

243243
if (runData && webhook.node in runData) {
244244
return false;
245245
}
246246

247-
if (isAlreadyRegistered && !webhook.webhookId) {
247+
// if registration already exists and is not a test webhook created by this user in this workflow throw an error
248+
if (
249+
registrationByKey &&
250+
!webhook.webhookId &&
251+
!registrationByKey.webhook.isTest &&
252+
registrationByKey.webhook.userId !== userId &&
253+
registrationByKey.webhook.workflowId !== workflow.id
254+
) {
248255
throw new WebhookPathTakenError(webhook.node);
249256
}
250257

packages/editor-ui/src/components/NodeWebhooks.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
>
2929
<div v-if="isWebhookMethodVisible(webhook)" class="webhook-wrapper">
3030
<div class="http-field">
31-
<div class="http-method">
32-
{{ workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod') }}<br />
33-
</div>
31+
<div class="http-method">{{ getWebhookHttpMethod(webhook) }}<br /></div>
3432
</div>
3533
<div class="url-field">
3634
<div class="webhook-url left-ellipsis clickable" @click="copyWebhookUrl(webhook)">
@@ -195,12 +193,27 @@ export default defineComponent({
195193
return '';
196194
},
197195
isWebhookMethodVisible(webhook: IWebhookDescription): boolean {
196+
try {
197+
const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false);
198+
if (Array.isArray(method) && method.length !== 1) {
199+
return false;
200+
}
201+
} catch (error) {}
202+
198203
if (typeof webhook.ndvHideMethod === 'string') {
199204
return !this.workflowHelpers.getWebhookExpressionValue(webhook, 'ndvHideMethod');
200205
}
201206
202207
return !webhook.ndvHideMethod;
203208
},
209+
210+
getWebhookHttpMethod(webhook: IWebhookDescription): string {
211+
const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false);
212+
if (Array.isArray(method)) {
213+
return method[0];
214+
}
215+
return method;
216+
},
204217
},
205218
});
206219
</script>

packages/editor-ui/src/components/TriggerPanel.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,17 @@ export default defineComponent({
225225
return undefined;
226226
}
227227
228-
return this.workflowHelpers.getWebhookExpressionValue(
228+
const httpMethod = this.workflowHelpers.getWebhookExpressionValue(
229229
this.nodeType.webhooks[0],
230230
'httpMethod',
231+
false,
231232
);
233+
234+
if (Array.isArray(httpMethod)) {
235+
return httpMethod.join(', ');
236+
}
237+
238+
return httpMethod;
232239
},
233240
webhookTestUrl(): string | undefined {
234241
if (!this.node || !this.nodeType?.webhooks?.length) {

packages/editor-ui/src/composables/useWorkflowHelpers.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -738,12 +738,21 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
738738
return nodeData;
739739
}
740740

741-
function getWebhookExpressionValue(webhookData: IWebhookDescription, key: string): string {
741+
function getWebhookExpressionValue(
742+
webhookData: IWebhookDescription,
743+
key: string,
744+
stringify = true,
745+
): string {
742746
if (webhookData[key] === undefined) {
743747
return 'empty';
744748
}
745749
try {
746-
return resolveExpression(webhookData[key] as string) as string;
750+
return resolveExpression(
751+
webhookData[key] as string,
752+
undefined,
753+
undefined,
754+
stringify,
755+
) as string;
747756
} catch (e) {
748757
return i18n.baseText('nodeWebhooks.invalidExpression');
749758
}
@@ -785,6 +794,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
785794
c?: number;
786795
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
787796
} = {},
797+
stringifyObject = true,
788798
) {
789799
const parameters = {
790800
__xxxxxxx__: expression,
@@ -796,7 +806,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
796806
}
797807

798808
const obj = returnData.__xxxxxxx__;
799-
if (typeof obj === 'object') {
809+
if (typeof obj === 'object' && stringifyObject) {
800810
const proxy = obj as { isProxy: boolean; toJSON?: () => unknown } | null;
801811
if (proxy?.isProxy && proxy.toJSON) return JSON.stringify(proxy.toJSON());
802812
const workflow = getCurrentWorkflow();

packages/nodes-base/nodes/Webhook/Webhook.node.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,60 @@ export class Webhook extends Node {
7474
credentials: credentialsProperty(this.authPropertyName),
7575
webhooks: [defaultWebhookDescription],
7676
properties: [
77-
httpMethodsProperty,
77+
{
78+
displayName: 'Allow Multiple HTTP Methods',
79+
name: 'multipleMethods',
80+
type: 'boolean',
81+
default: false,
82+
isNodeSetting: true,
83+
description: 'Whether to allow the webhook to listen for multiple HTTP methods',
84+
},
85+
{
86+
...httpMethodsProperty,
87+
displayOptions: {
88+
show: {
89+
multipleMethods: [false],
90+
},
91+
},
92+
},
93+
{
94+
displayName: 'HTTP Methods',
95+
name: 'httpMethod',
96+
type: 'multiOptions',
97+
options: [
98+
{
99+
name: 'DELETE',
100+
value: 'DELETE',
101+
},
102+
{
103+
name: 'GET',
104+
value: 'GET',
105+
},
106+
{
107+
name: 'HEAD',
108+
value: 'HEAD',
109+
},
110+
{
111+
name: 'PATCH',
112+
value: 'PATCH',
113+
},
114+
{
115+
name: 'POST',
116+
value: 'POST',
117+
},
118+
{
119+
name: 'PUT',
120+
value: 'PUT',
121+
},
122+
],
123+
default: ['GET', 'POST'],
124+
description: 'The HTTP methods to listen to',
125+
displayOptions: {
126+
show: {
127+
multipleMethods: [true],
128+
},
129+
},
130+
},
78131
{
79132
displayName: 'Path',
80133
name: 'path',
@@ -144,6 +197,7 @@ export class Webhook extends Node {
144197
};
145198
const req = context.getRequestObject();
146199
const resp = context.getResponseObject();
200+
const requestMethod = context.getRequestObject().method;
147201

148202
if (!isIpWhitelisted(options.ipWhitelist, req.ips, req.ip)) {
149203
resp.writeHead(403);
@@ -165,7 +219,7 @@ export class Webhook extends Node {
165219
throw error;
166220
}
167221

168-
const prepareOutput = setupOutputConnection(context, {
222+
const prepareOutput = setupOutputConnection(context, requestMethod, {
169223
jwtPayload: validationData,
170224
});
171225

packages/nodes-base/nodes/Webhook/utils.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,36 +50,64 @@ export const getResponseData = (parameters: WebhookParameters) => {
5050
};
5151

5252
export const configuredOutputs = (parameters: WebhookParameters) => {
53-
const httpMethod = parameters.httpMethod;
53+
const httpMethod = parameters.httpMethod as string | string[];
5454

55-
return [
56-
{
55+
if (!Array.isArray(httpMethod))
56+
return [
57+
{
58+
type: `${NodeConnectionType.Main}`,
59+
displayName: httpMethod,
60+
},
61+
];
62+
63+
const outputs = httpMethod.map((method) => {
64+
return {
5765
type: `${NodeConnectionType.Main}`,
58-
displayName: httpMethod,
59-
},
60-
];
66+
displayName: method,
67+
};
68+
});
69+
70+
return outputs;
6171
};
6272

6373
export const setupOutputConnection = (
6474
ctx: IWebhookFunctions,
75+
method: string,
6576
additionalData: {
6677
jwtPayload?: IDataObject;
6778
},
6879
) => {
80+
const httpMethod = ctx.getNodeParameter('httpMethod', []) as string[] | string;
6981
let webhookUrl = ctx.getNodeWebhookUrl('default') as string;
7082
const executionMode = ctx.getMode() === 'manual' ? 'test' : 'production';
7183

7284
if (executionMode === 'test') {
7385
webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/');
7486
}
7587

88+
// multi methods could be set in settings of node, so we need to check if it's an array
89+
if (!Array.isArray(httpMethod)) {
90+
return (outputData: INodeExecutionData): INodeExecutionData[][] => {
91+
outputData.json.webhookUrl = webhookUrl;
92+
outputData.json.executionMode = executionMode;
93+
if (additionalData?.jwtPayload) {
94+
outputData.json.jwtPayload = additionalData.jwtPayload;
95+
}
96+
return [[outputData]];
97+
};
98+
}
99+
100+
const outputIndex = httpMethod.indexOf(method.toUpperCase());
101+
const outputs: INodeExecutionData[][] = httpMethod.map(() => []);
102+
76103
return (outputData: INodeExecutionData): INodeExecutionData[][] => {
77104
outputData.json.webhookUrl = webhookUrl;
78105
outputData.json.executionMode = executionMode;
79106
if (additionalData?.jwtPayload) {
80107
outputData.json.jwtPayload = additionalData.jwtPayload;
81108
}
82-
return [[outputData]];
109+
outputs[outputIndex] = [outputData];
110+
return outputs;
83111
};
84112
};
85113

packages/workflow/src/NodeHelpers.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ export function getNodeWebhooks(
996996
) as boolean;
997997
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook);
998998

999-
const httpMethod = workflow.expression.getSimpleParameterValue(
999+
const webhookMethods = workflow.expression.getSimpleParameterValue(
10001000
node,
10011001
webhookDescription.httpMethod,
10021002
mode,
@@ -1005,7 +1005,7 @@ export function getNodeWebhooks(
10051005
'GET',
10061006
);
10071007

1008-
if (httpMethod === undefined) {
1008+
if (webhookMethods === undefined) {
10091009
// TODO: Use a proper logger
10101010
console.error(
10111011
`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`,
@@ -1018,15 +1018,20 @@ export function getNodeWebhooks(
10181018
webhookId = node.webhookId;
10191019
}
10201020

1021-
returnData.push({
1022-
httpMethod: httpMethod.toString() as IHttpRequestMethods,
1023-
node: node.name,
1024-
path,
1025-
webhookDescription,
1026-
workflowId,
1027-
workflowExecuteAdditionalData: additionalData,
1028-
webhookId,
1029-
});
1021+
String(webhookMethods)
1022+
.split(',')
1023+
.forEach((httpMethod) => {
1024+
if (!httpMethod) return;
1025+
returnData.push({
1026+
httpMethod: httpMethod.trim() as IHttpRequestMethods,
1027+
node: node.name,
1028+
path,
1029+
webhookDescription,
1030+
workflowId,
1031+
workflowExecuteAdditionalData: additionalData,
1032+
webhookId,
1033+
});
1034+
});
10301035
}
10311036

10321037
return returnData;

0 commit comments

Comments
 (0)