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", )