Allow webhook body

This commit is contained in:
shamoon 2024-11-06 13:07:26 -08:00
parent 4f7cbab912
commit 02ff7bd55e
No known key found for this signature in database
8 changed files with 137 additions and 24 deletions

View File

@ -336,7 +336,12 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<pngx-input-text i18n-title title="Webhook url" formControlName="webhook_url" [error]="error?.actions?.[i]?.webhook_url"></pngx-input-text> <pngx-input-text i18n-title title="Webhook url" formControlName="webhook_url" [error]="error?.actions?.[i]?.webhook_url"></pngx-input-text>
<pngx-input-switch i18n-title title="Use parameters for webhook body" formControlName="webhook_use_params"></pngx-input-switch>
@if (formGroup.get('webhook_use_params').value) {
<pngx-input-entries i18n-title title="Webhook params" formControlName="webhook_params" [error]="error?.actions?.[i]?.webhook_params"></pngx-input-entries> <pngx-input-entries i18n-title title="Webhook params" formControlName="webhook_params" [error]="error?.actions?.[i]?.webhook_params"></pngx-input-entries>
} @else {
<pngx-input-textarea i18n-title title="Webhook body" formControlName="webhook_body" [error]="error?.actions?.[i]?.webhook_body"></pngx-input-textarea>
}
<pngx-input-entries i18n-title title="Webhook headers" formControlName="webhook_headers" [error]="error?.actions?.[i]?.webhook_headers"></pngx-input-entries> <pngx-input-entries i18n-title title="Webhook headers" formControlName="webhook_headers" [error]="error?.actions?.[i]?.webhook_headers"></pngx-input-entries>
<pngx-input-switch i18n-title title="Include document" formControlName="webhook_include_document"></pngx-input-switch> <pngx-input-switch i18n-title title="Include document" formControlName="webhook_include_document"></pngx-input-switch>
</div> </div>

View File

@ -415,7 +415,9 @@ export class WorkflowEditDialogComponent
email_to: new FormControl(action.email_to), email_to: new FormControl(action.email_to),
email_include_document: new FormControl(action.email_include_document), email_include_document: new FormControl(action.email_include_document),
webhook_url: new FormControl(action.webhook_url), webhook_url: new FormControl(action.webhook_url),
webhook_use_params: new FormControl(action.webhook_use_params),
webhook_params: new FormControl(action.webhook_params), webhook_params: new FormControl(action.webhook_params),
webhook_body: new FormControl(action.webhook_body),
webhook_headers: new FormControl(action.webhook_headers), webhook_headers: new FormControl(action.webhook_headers),
webhook_include_document: new FormControl( webhook_include_document: new FormControl(
action.webhook_include_document action.webhook_include_document
@ -526,7 +528,9 @@ export class WorkflowEditDialogComponent
email_to: null, email_to: null,
email_include_document: false, email_include_document: false,
webhook_url: null, webhook_url: null,
webhook_use_params: true,
webhook_params: null, webhook_params: null,
webhook_body: null,
webhook_headers: null, webhook_headers: null,
webhook_include_document: false, webhook_include_document: false,
} }

View File

@ -75,8 +75,12 @@ export interface WorkflowAction extends ObjectWithId {
webhook_url?: string webhook_url?: string
webhook_use_params?: boolean
webhook_params?: object webhook_params?: object
webhook_body?: string
webhook_headers?: object webhook_headers?: object
webhook_include_document?: boolean webhook_include_document?: boolean

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.1 on 2024-11-02 15:25 # Generated by Django 5.1.1 on 2024-11-06 20:45
from django.db import migrations from django.db import migrations
from django.db import models from django.db import models
@ -49,6 +49,16 @@ class Migration(migrations.Migration):
verbose_name="emails to", verbose_name="emails to",
), ),
), ),
migrations.AddField(
model_name="workflowaction",
name="webhook_body",
field=models.TextField(
blank=True,
help_text="The body to send with the webhook URL if parameters not used.",
null=True,
verbose_name="webhook body",
),
),
migrations.AddField( migrations.AddField(
model_name="workflowaction", model_name="workflowaction",
name="webhook_headers", name="webhook_headers",
@ -72,7 +82,7 @@ class Migration(migrations.Migration):
name="webhook_params", name="webhook_params",
field=models.JSONField( field=models.JSONField(
blank=True, blank=True,
help_text="The parameters to send with the webhook URL.", help_text="The parameters to send with the webhook URL if body not used.",
null=True, null=True,
verbose_name="webhook parameters", verbose_name="webhook parameters",
), ),
@ -87,6 +97,11 @@ class Migration(migrations.Migration):
verbose_name="webhook url", verbose_name="webhook url",
), ),
), ),
migrations.AddField(
model_name="workflowaction",
name="webhook_use_params",
field=models.BooleanField(default=True, verbose_name="use parameters"),
),
migrations.AlterField( migrations.AlterField(
model_name="workflowaction", model_name="workflowaction",
name="type", name="type",

View File

@ -1417,11 +1417,23 @@ class WorkflowAction(models.Model):
help_text=_("The destination URL for the notification."), help_text=_("The destination URL for the notification."),
) )
webhook_use_params = models.BooleanField(
default=True,
verbose_name=_("use parameters"),
)
webhook_params = models.JSONField( webhook_params = models.JSONField(
_("webhook parameters"), _("webhook parameters"),
null=True, null=True,
blank=True, blank=True,
help_text=_("The parameters to send with the webhook URL."), help_text=_("The parameters to send with the webhook URL if body not used."),
)
webhook_body = models.TextField(
_("webhook body"),
null=True,
blank=True,
help_text=_("The body to send with the webhook URL if parameters not used."),
) )
webhook_headers = models.JSONField( webhook_headers = models.JSONField(

View File

@ -1852,7 +1852,9 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
"email_body", "email_body",
"email_include_document", "email_include_document",
"webhook_url", "webhook_url",
"webhook_use_params",
"webhook_params", "webhook_params",
"webhook_body",
"webhook_headers", "webhook_headers",
] ]

View File

@ -936,13 +936,18 @@ def run_workflows(
doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/" doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/"
try: try:
params = {} data = {}
if action.webhook_use_params:
try: try:
for key, value in action.webhook_params.items(): for key, value in action.webhook_params.items():
params[key] = parse_w_workflow_placeholders( data[key] = parse_w_workflow_placeholders(
value, value,
document.correspondent.name if document.correspondent else "", document.correspondent.name
document.document_type.name if document.document_type else "", if document.correspondent
else "",
document.document_type.name
if document.document_type
else "",
document.owner.username if document.owner else "", document.owner.username if document.owner else "",
timezone.localtime(document.added), timezone.localtime(document.added),
document.original_filename or "", document.original_filename or "",
@ -955,6 +960,18 @@ def run_workflows(
f"Error occurred parsing webhook params: {e}", f"Error occurred parsing webhook params: {e}",
extra={"group": logging_group}, extra={"group": logging_group},
) )
else:
data = parse_w_workflow_placeholders(
action.webhook_body,
document.correspondent.name if document.correspondent else "",
document.document_type.name if document.document_type else "",
document.owner.username if document.owner else "",
timezone.localtime(document.added),
document.original_filename or "",
timezone.localtime(document.created),
title,
doc_url,
)
headers = {} headers = {}
if action.webhook_headers: if action.webhook_headers:
try: try:
@ -971,14 +988,14 @@ def run_workflows(
files = {"file": (document.original_filename, f)} files = {"file": (document.original_filename, f)}
httpx.post( httpx.post(
action.webhook_url, action.webhook_url,
data=params, data=data,
files=files, files=files,
headers=headers, headers=headers,
) )
else: else:
httpx.post( httpx.post(
action.webhook_url, action.webhook_url,
data=params, data=data,
headers=headers, headers=headers,
) )
except Exception as e: except Exception as e:

View File

@ -2228,6 +2228,58 @@ class TestWorkflows(
expected_str = "Email backend has not been configured" expected_str = "Email backend has not been configured"
self.assertIn(expected_str, cm.output[0]) self.assertIn(expected_str, cm.output[0])
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,
PAPERLESS_URL="http://localhost:8000",
)
@mock.patch("httpx.post")
def test_workflow_webhook_action_body(self, mock_post):
"""
GIVEN:
- Document updated workflow with webhook action which uses body
WHEN:
- Document that matches is updated
THEN:
- Webhook is sent with body
"""
mock_post.return_value = mock.Mock(
status_code=200,
json=mock.Mock(return_value={"status": "ok"}),
)
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_use_params=False,
webhook_body="Test message: {doc_url}",
webhook_url="http://paperless-ngx.com",
webhook_include_document=False,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
)
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
mock_post.assert_called_once_with(
"http://paperless-ngx.com",
data=f"Test message: http://localhost:8000/documents/{doc.id}/",
headers={},
)
@override_settings( @override_settings(
PAPERLESS_EMAIL_HOST="localhost", PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True, EMAIL_ENABLED=True,
@ -2248,6 +2300,7 @@ class TestWorkflows(
) )
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK, type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_use_params=True,
webhook_params={ webhook_params={
"title": "Test webhook: {doc_title}", "title": "Test webhook: {doc_title}",
"body": "Test message: {doc_url}", "body": "Test message: {doc_url}",
@ -2277,7 +2330,7 @@ class TestWorkflows(
self.assertIn(expected_str, cm.output[0]) self.assertIn(expected_str, cm.output[0])
@mock.patch("httpx.post") @mock.patch("httpx.post")
def test_workflow_notification_action_url_invalid_params_headers(self, mock_post): def test_workflow_webhook_action_url_invalid_params_headers(self, mock_post):
""" """
GIVEN: GIVEN:
- Document updated workflow with webhook action - Document updated workflow with webhook action
@ -2293,6 +2346,7 @@ class TestWorkflows(
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK, type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_url="http://paperless-ngx.com", webhook_url="http://paperless-ngx.com",
webhook_use_params=True,
webhook_params="invalid", webhook_params="invalid",
webhook_headers="invalid", webhook_headers="invalid",
) )