Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/cli/src/TestWebhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
19 changes: 16 additions & 3 deletions packages/editor-ui/src/components/NodeWebhooks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)">
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a catch here? asking because you are catching the same call on line 201

You also might be able to extract some common logic between isWebhookMethodVisible and getWebhookHttpMethod

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, as method could be string or array

if (Array.isArray(method)) {
return method[0];
}
return method;
},
},
});
</script>
Expand Down
9 changes: 8 additions & 1 deletion packages/editor-ui/src/components/TriggerPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 13 additions & 3 deletions packages/editor-ui/src/composables/useWorkflowHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -785,6 +794,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
c?: number;
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
} = {},
stringifyObject = true,
) {
const parameters = {
__xxxxxxx__: expression,
Expand All @@ -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();
Expand Down
58 changes: 56 additions & 2 deletions packages/nodes-base/nodes/Webhook/Webhook.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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);
Expand All @@ -165,7 +219,7 @@ export class Webhook extends Node {
throw error;
}

const prepareOutput = setupOutputConnection(context, {
const prepareOutput = setupOutputConnection(context, requestMethod, {
jwtPayload: validationData,
});

Expand Down
42 changes: 35 additions & 7 deletions packages/nodes-base/nodes/Webhook/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,36 +50,64 @@ 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';

if (executionMode === 'test') {
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;
};
};

Expand Down
27 changes: 16 additions & 11 deletions packages/workflow/src/NodeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.`,
Expand All @@ -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;
Expand Down
Loading