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="col">
<pngx-input-text i18n-title title="Webhook url" formControlName="webhook_url" [error]="error?.actions?.[i]?.webhook_url"></pngx-input-text>
<pngx-input-entries i18n-title title="Webhook params" formControlName="webhook_params" [error]="error?.actions?.[i]?.webhook_params"></pngx-input-entries>
<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>
} @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-switch i18n-title title="Include document" formControlName="webhook_include_document"></pngx-input-switch>
</div>

View File

@ -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,
}

View File

@ -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

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

View File

@ -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(

View File

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

View File

@ -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:

View File

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