feat(Webhook Node): Setting to enable multiple outputs/methods (#9086)

Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
Michael Kret 2024-04-24 08:46:16 +03:00 committed by GitHub
parent f6142ff275
commit 2bf0a3933e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 153 additions and 29 deletions

View file

@ -238,13 +238,20 @@ export class TestWebhooks implements IWebhookManager {
for (const webhook of webhooks) {
const key = this.registrations.toKey(webhook);
const isAlreadyRegistered = await this.registrations.get(key);
const registrationByKey = await this.registrations.get(key);
if (runData && webhook.node in runData) {
return false;
}
if (isAlreadyRegistered && !webhook.webhookId) {
// if registration already exists and is not a test webhook created by this user in this workflow throw an error
if (
registrationByKey &&
!webhook.webhookId &&
!registrationByKey.webhook.isTest &&
registrationByKey.webhook.userId !== userId &&
registrationByKey.webhook.workflowId !== workflow.id
) {
throw new WebhookPathTakenError(webhook.node);
}

View file

@ -28,9 +28,7 @@
>
<div v-if="isWebhookMethodVisible(webhook)" class="webhook-wrapper">
<div class="http-field">
<div class="http-method">
{{ workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod') }}<br />
</div>
<div class="http-method">{{ getWebhookHttpMethod(webhook) }}<br /></div>
</div>
<div class="url-field">
<div class="webhook-url left-ellipsis clickable" @click="copyWebhookUrl(webhook)">
@ -195,12 +193,27 @@ export default defineComponent({
return '';
},
isWebhookMethodVisible(webhook: IWebhookDescription): boolean {
try {
const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false);
if (Array.isArray(method) && method.length !== 1) {
return false;
}
} catch (error) {}
if (typeof webhook.ndvHideMethod === 'string') {
return !this.workflowHelpers.getWebhookExpressionValue(webhook, 'ndvHideMethod');
}
return !webhook.ndvHideMethod;
},
getWebhookHttpMethod(webhook: IWebhookDescription): string {
const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false);
if (Array.isArray(method)) {
return method[0];
}
return method;
},
},
});
</script>

View file

@ -225,10 +225,17 @@ export default defineComponent({
return undefined;
}
return this.workflowHelpers.getWebhookExpressionValue(
const httpMethod = this.workflowHelpers.getWebhookExpressionValue(
this.nodeType.webhooks[0],
'httpMethod',
false,
);
if (Array.isArray(httpMethod)) {
return httpMethod.join(', ');
}
return httpMethod;
},
webhookTestUrl(): string | undefined {
if (!this.node || !this.nodeType?.webhooks?.length) {

View file

@ -738,12 +738,21 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
return nodeData;
}
function getWebhookExpressionValue(webhookData: IWebhookDescription, key: string): string {
function getWebhookExpressionValue(
webhookData: IWebhookDescription,
key: string,
stringify = true,
): string {
if (webhookData[key] === undefined) {
return 'empty';
}
try {
return resolveExpression(webhookData[key] as string) as string;
return resolveExpression(
webhookData[key] as string,
undefined,
undefined,
stringify,
) as string;
} catch (e) {
return i18n.baseText('nodeWebhooks.invalidExpression');
}
@ -785,6 +794,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
c?: number;
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
} = {},
stringifyObject = true,
) {
const parameters = {
__xxxxxxx__: expression,
@ -796,7 +806,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
}
const obj = returnData.__xxxxxxx__;
if (typeof obj === 'object') {
if (typeof obj === 'object' && stringifyObject) {
const proxy = obj as { isProxy: boolean; toJSON?: () => unknown } | null;
if (proxy?.isProxy && proxy.toJSON) return JSON.stringify(proxy.toJSON());
const workflow = getCurrentWorkflow();

View file

@ -74,7 +74,60 @@ export class Webhook extends Node {
credentials: credentialsProperty(this.authPropertyName),
webhooks: [defaultWebhookDescription],
properties: [
httpMethodsProperty,
{
displayName: 'Allow Multiple HTTP Methods',
name: 'multipleMethods',
type: 'boolean',
default: false,
isNodeSetting: true,
description: 'Whether to allow the webhook to listen for multiple HTTP methods',
},
{
...httpMethodsProperty,
displayOptions: {
show: {
multipleMethods: [false],
},
},
},
{
displayName: 'HTTP Methods',
name: 'httpMethod',
type: 'multiOptions',
options: [
{
name: 'DELETE',
value: 'DELETE',
},
{
name: 'GET',
value: 'GET',
},
{
name: 'HEAD',
value: 'HEAD',
},
{
name: 'PATCH',
value: 'PATCH',
},
{
name: 'POST',
value: 'POST',
},
{
name: 'PUT',
value: 'PUT',
},
],
default: ['GET', 'POST'],
description: 'The HTTP methods to listen to',
displayOptions: {
show: {
multipleMethods: [true],
},
},
},
{
displayName: 'Path',
name: 'path',
@ -144,6 +197,7 @@ export class Webhook extends Node {
};
const req = context.getRequestObject();
const resp = context.getResponseObject();
const requestMethod = context.getRequestObject().method;
if (!isIpWhitelisted(options.ipWhitelist, req.ips, req.ip)) {
resp.writeHead(403);
@ -165,7 +219,7 @@ export class Webhook extends Node {
throw error;
}
const prepareOutput = setupOutputConnection(context, {
const prepareOutput = setupOutputConnection(context, requestMethod, {
jwtPayload: validationData,
});

View file

@ -50,22 +50,34 @@ export const getResponseData = (parameters: WebhookParameters) => {
};
export const configuredOutputs = (parameters: WebhookParameters) => {
const httpMethod = parameters.httpMethod;
const httpMethod = parameters.httpMethod as string | string[];
return [
{
if (!Array.isArray(httpMethod))
return [
{
type: `${NodeConnectionType.Main}`,
displayName: httpMethod,
},
];
const outputs = httpMethod.map((method) => {
return {
type: `${NodeConnectionType.Main}`,
displayName: httpMethod,
},
];
displayName: method,
};
});
return outputs;
};
export const setupOutputConnection = (
ctx: IWebhookFunctions,
method: string,
additionalData: {
jwtPayload?: IDataObject;
},
) => {
const httpMethod = ctx.getNodeParameter('httpMethod', []) as string[] | string;
let webhookUrl = ctx.getNodeWebhookUrl('default') as string;
const executionMode = ctx.getMode() === 'manual' ? 'test' : 'production';
@ -73,13 +85,29 @@ export const setupOutputConnection = (
webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/');
}
// multi methods could be set in settings of node, so we need to check if it's an array
if (!Array.isArray(httpMethod)) {
return (outputData: INodeExecutionData): INodeExecutionData[][] => {
outputData.json.webhookUrl = webhookUrl;
outputData.json.executionMode = executionMode;
if (additionalData?.jwtPayload) {
outputData.json.jwtPayload = additionalData.jwtPayload;
}
return [[outputData]];
};
}
const outputIndex = httpMethod.indexOf(method.toUpperCase());
const outputs: INodeExecutionData[][] = httpMethod.map(() => []);
return (outputData: INodeExecutionData): INodeExecutionData[][] => {
outputData.json.webhookUrl = webhookUrl;
outputData.json.executionMode = executionMode;
if (additionalData?.jwtPayload) {
outputData.json.jwtPayload = additionalData.jwtPayload;
}
return [[outputData]];
outputs[outputIndex] = [outputData];
return outputs;
};
};

View file

@ -996,7 +996,7 @@ export function getNodeWebhooks(
) as boolean;
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook);
const httpMethod = workflow.expression.getSimpleParameterValue(
const webhookMethods = workflow.expression.getSimpleParameterValue(
node,
webhookDescription.httpMethod,
mode,
@ -1005,7 +1005,7 @@ export function getNodeWebhooks(
'GET',
);
if (httpMethod === undefined) {
if (webhookMethods === undefined) {
// TODO: Use a proper logger
console.error(
`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(
webhookId = node.webhookId;
}
returnData.push({
httpMethod: httpMethod.toString() as IHttpRequestMethods,
node: node.name,
path,
webhookDescription,
workflowId,
workflowExecuteAdditionalData: additionalData,
webhookId,
});
String(webhookMethods)
.split(',')
.forEach((httpMethod) => {
if (!httpMethod) return;
returnData.push({
httpMethod: httpMethod.trim() as IHttpRequestMethods,
node: node.name,
path,
webhookDescription,
workflowId,
workflowExecuteAdditionalData: additionalData,
webhookId,
});
});
}
return returnData;