From 02ff7bd55ee22c531af247e3bc8c6762eb9def75 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Wed, 6 Nov 2024 13:07:26 -0800
Subject: [PATCH] Allow webhook body
---
.../workflow-edit-dialog.component.html | 7 ++-
.../workflow-edit-dialog.component.ts | 4 ++
src-ui/src/app/data/workflow-action.ts | 4 ++
...1057_workflowaction_email_body_and_more.py | 19 ++++++-
src/documents/models.py | 14 ++++-
src/documents/serialisers.py | 2 +
src/documents/signals/handlers.py | 55 +++++++++++-------
src/documents/tests/test_workflows.py | 56 ++++++++++++++++++-
8 files changed, 137 insertions(+), 24 deletions(-)
diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
index c5c486fac..0f415b8d7 100644
--- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
+++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
@@ -336,7 +336,12 @@
-
+
+ @if (formGroup.get('webhook_use_params').value) {
+
+ } @else {
+
+ }
diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts
index f36e73433..f2b2b7752 100644
--- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts
@@ -415,7 +415,9 @@ export class WorkflowEditDialogComponent
email_to: new FormControl(action.email_to),
email_include_document: new FormControl(action.email_include_document),
webhook_url: new FormControl(action.webhook_url),
+ webhook_use_params: new FormControl(action.webhook_use_params),
webhook_params: new FormControl(action.webhook_params),
+ webhook_body: new FormControl(action.webhook_body),
webhook_headers: new FormControl(action.webhook_headers),
webhook_include_document: new FormControl(
action.webhook_include_document
@@ -526,7 +528,9 @@ export class WorkflowEditDialogComponent
email_to: null,
email_include_document: false,
webhook_url: null,
+ webhook_use_params: true,
webhook_params: null,
+ webhook_body: null,
webhook_headers: null,
webhook_include_document: false,
}
diff --git a/src-ui/src/app/data/workflow-action.ts b/src-ui/src/app/data/workflow-action.ts
index ac043b5f7..fdb8914e4 100644
--- a/src-ui/src/app/data/workflow-action.ts
+++ b/src-ui/src/app/data/workflow-action.ts
@@ -75,8 +75,12 @@ export interface WorkflowAction extends ObjectWithId {
webhook_url?: string
+ webhook_use_params?: boolean
+
webhook_params?: object
+ webhook_body?: string
+
webhook_headers?: object
webhook_include_document?: boolean
diff --git a/src/documents/migrations/1057_workflowaction_email_body_and_more.py b/src/documents/migrations/1057_workflowaction_email_body_and_more.py
index 404c5e7e4..a874bd9ab 100644
--- a/src/documents/migrations/1057_workflowaction_email_body_and_more.py
+++ b/src/documents/migrations/1057_workflowaction_email_body_and_more.py
@@ -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 models
@@ -49,6 +49,16 @@ class Migration(migrations.Migration):
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(
model_name="workflowaction",
name="webhook_headers",
@@ -72,7 +82,7 @@ class Migration(migrations.Migration):
name="webhook_params",
field=models.JSONField(
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,
verbose_name="webhook parameters",
),
@@ -87,6 +97,11 @@ class Migration(migrations.Migration):
verbose_name="webhook url",
),
),
+ migrations.AddField(
+ model_name="workflowaction",
+ name="webhook_use_params",
+ field=models.BooleanField(default=True, verbose_name="use parameters"),
+ ),
migrations.AlterField(
model_name="workflowaction",
name="type",
diff --git a/src/documents/models.py b/src/documents/models.py
index 353b722b3..2befcdb9c 100644
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -1417,11 +1417,23 @@ class WorkflowAction(models.Model):
help_text=_("The destination URL for the notification."),
)
+ webhook_use_params = models.BooleanField(
+ default=True,
+ verbose_name=_("use parameters"),
+ )
+
webhook_params = models.JSONField(
_("webhook parameters"),
null=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(
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index b1559a510..bee6ee044 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -1852,7 +1852,9 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
"email_body",
"email_include_document",
"webhook_url",
+ "webhook_use_params",
"webhook_params",
+ "webhook_body",
"webhook_headers",
]
diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py
index c49010308..f33fc2f07 100644
--- a/src/documents/signals/handlers.py
+++ b/src/documents/signals/handlers.py
@@ -936,24 +936,41 @@ def run_workflows(
doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/"
try:
- params = {}
- try:
- for key, value in action.webhook_params.items():
- params[key] = parse_w_workflow_placeholders(
- value,
- 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,
+ data = {}
+ if action.webhook_use_params:
+ try:
+ for key, value in action.webhook_params.items():
+ data[key] = parse_w_workflow_placeholders(
+ value,
+ 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,
+ )
+ except Exception as e:
+ logger.error(
+ f"Error occurred parsing webhook params: {e}",
+ extra={"group": logging_group},
)
- except Exception as e:
- logger.error(
- f"Error occurred parsing webhook params: {e}",
- 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 = {}
if action.webhook_headers:
@@ -971,14 +988,14 @@ def run_workflows(
files = {"file": (document.original_filename, f)}
httpx.post(
action.webhook_url,
- data=params,
+ data=data,
files=files,
headers=headers,
)
else:
httpx.post(
action.webhook_url,
- data=params,
+ data=data,
headers=headers,
)
except Exception as e:
diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py
index bcdeb1869..40db979b1 100644
--- a/src/documents/tests/test_workflows.py
+++ b/src/documents/tests/test_workflows.py
@@ -2228,6 +2228,58 @@ class TestWorkflows(
expected_str = "Email backend has not been configured"
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(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,
@@ -2248,6 +2300,7 @@ class TestWorkflows(
)
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK,
+ webhook_use_params=True,
webhook_params={
"title": "Test webhook: {doc_title}",
"body": "Test message: {doc_url}",
@@ -2277,7 +2330,7 @@ class TestWorkflows(
self.assertIn(expected_str, cm.output[0])
@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:
- Document updated workflow with webhook action
@@ -2293,6 +2346,7 @@ class TestWorkflows(
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_url="http://paperless-ngx.com",
+ webhook_use_params=True,
webhook_params="invalid",
webhook_headers="invalid",
)