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 f6087627d..5bbc2776c 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
@@ -322,15 +322,23 @@
}
- @case (WorkflowActionType.Notification) {
+ @case (WorkflowActionType.Email) {
+ }
+ @case (WorkflowActionType.Webhook) {
+
}
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 56520f9a0..f36e73433 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
@@ -97,8 +97,12 @@ export const WORKFLOW_ACTION_OPTIONS = [
name: $localize`Removal`,
},
{
- id: WorkflowActionType.Notification,
- name: $localize`Notification`,
+ id: WorkflowActionType.Email,
+ name: $localize`Email`,
+ },
+ {
+ id: WorkflowActionType.Webhook,
+ name: $localize`Webhook`,
},
]
@@ -406,19 +410,15 @@ export class WorkflowEditDialogComponent
remove_all_custom_fields: new FormControl(
action.remove_all_custom_fields
),
- notification_subject: new FormControl(action.notification_subject),
- notification_body: new FormControl(action.notification_body),
- notification_destination_emails: new FormControl(
- action.notification_destination_emails
- ),
- notification_destination_url: new FormControl(
- action.notification_destination_url
- ),
- notification_destination_url_headers: new FormControl(
- action.notification_destination_url_headers
- ),
- notification_include_document: new FormControl(
- action.notification_include_document
+ email_subject: new FormControl(action.email_subject),
+ email_body: new FormControl(action.email_body),
+ email_to: new FormControl(action.email_to),
+ email_include_document: new FormControl(action.email_include_document),
+ webhook_url: new FormControl(action.webhook_url),
+ webhook_params: new FormControl(action.webhook_params),
+ webhook_headers: new FormControl(action.webhook_headers),
+ webhook_include_document: new FormControl(
+ action.webhook_include_document
),
}),
{ emitEvent }
@@ -521,12 +521,14 @@ export class WorkflowEditDialogComponent
remove_all_permissions: false,
remove_custom_fields: [],
remove_all_custom_fields: false,
- notification_subject: null,
- notification_body: null,
- notification_destination_emails: null,
- notification_destination_url: null,
- notification_destination_url_headers: null,
- notification_include_document: null,
+ email_subject: null,
+ email_body: null,
+ email_to: null,
+ email_include_document: false,
+ webhook_url: null,
+ webhook_params: null,
+ webhook_headers: null,
+ webhook_include_document: false,
}
this.object.actions.push(action)
this.createActionField(action)
diff --git a/src-ui/src/app/data/workflow-action.ts b/src-ui/src/app/data/workflow-action.ts
index 096061cd0..4ddaaea8d 100644
--- a/src-ui/src/app/data/workflow-action.ts
+++ b/src-ui/src/app/data/workflow-action.ts
@@ -3,7 +3,8 @@ import { ObjectWithId } from './object-with-id'
export enum WorkflowActionType {
Assignment = 1,
Removal = 2,
- Notification = 3,
+ Email = 3,
+ Webhook = 4,
}
export interface WorkflowAction extends ObjectWithId {
type: WorkflowActionType
@@ -64,15 +65,19 @@ export interface WorkflowAction extends ObjectWithId {
remove_all_custom_fields?: boolean
- notification_subject?: string
+ email_subject?: string
- notification_body?: string
+ email_body?: string
- notification_destination_emails?: string
+ email_to?: string
- notification_destination_url?: string
+ email_include_document?: boolean
- notification_destination_url_headers?: string
+ webhook_url?: string
- notification_include_document?: boolean
+ webhook_params?: string
+
+ webhook_headers?: string
+
+ webhook_include_document?: boolean
}
diff --git a/src/documents/migrations/1057_workflowaction_notification_body_and_more.py b/src/documents/migrations/1057_workflowaction_email_body_and_more.py
similarity index 51%
rename from src/documents/migrations/1057_workflowaction_notification_body_and_more.py
rename to src/documents/migrations/1057_workflowaction_email_body_and_more.py
index 6dba8b88a..404c5e7e4 100644
--- a/src/documents/migrations/1057_workflowaction_notification_body_and_more.py
+++ b/src/documents/migrations/1057_workflowaction_email_body_and_more.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.2 on 2024-10-26 19:07
+# Generated by Django 5.1.1 on 2024-11-02 15:25
from django.db import migrations
from django.db import models
@@ -12,68 +12,91 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name="workflowaction",
- name="notification_body",
+ name="email_body",
field=models.TextField(
blank=True,
- help_text="The body (message) of the notification, can include some placeholders, see documentation.",
+ help_text="The body (message) of the email, can include some placeholders, see documentation.",
null=True,
- verbose_name="notification body",
+ verbose_name="email body",
),
),
migrations.AddField(
model_name="workflowaction",
- name="notification_destination_emails",
- field=models.TextField(
- blank=True,
- help_text="The destination email addresses for the notification, comma separated.",
- null=True,
- verbose_name="notification destination emails",
+ name="email_include_document",
+ field=models.BooleanField(
+ default=False,
+ verbose_name="include document in email",
),
),
migrations.AddField(
model_name="workflowaction",
- name="notification_destination_url",
+ name="email_subject",
+ field=models.CharField(
+ blank=True,
+ help_text="The subject of the email, can include some placeholders, see documentation.",
+ max_length=256,
+ null=True,
+ verbose_name="email subject",
+ ),
+ ),
+ migrations.AddField(
+ model_name="workflowaction",
+ name="email_to",
+ field=models.TextField(
+ blank=True,
+ help_text="The destination email addresses, comma separated.",
+ null=True,
+ verbose_name="emails to",
+ ),
+ ),
+ migrations.AddField(
+ model_name="workflowaction",
+ name="webhook_headers",
+ field=models.JSONField(
+ blank=True,
+ help_text="The headers to send with the webhook URL.",
+ null=True,
+ verbose_name="webhook headers",
+ ),
+ ),
+ migrations.AddField(
+ model_name="workflowaction",
+ name="webhook_include_document",
+ field=models.BooleanField(
+ default=False,
+ verbose_name="include document in webhook",
+ ),
+ ),
+ migrations.AddField(
+ model_name="workflowaction",
+ name="webhook_params",
+ field=models.JSONField(
+ blank=True,
+ help_text="The parameters to send with the webhook URL.",
+ null=True,
+ verbose_name="webhook parameters",
+ ),
+ ),
+ migrations.AddField(
+ model_name="workflowaction",
+ name="webhook_url",
field=models.URLField(
blank=True,
help_text="The destination URL for the notification.",
null=True,
- verbose_name="notification destination url",
- ),
- ),
- migrations.AddField(
- model_name="workflowaction",
- name="notification_destination_url_headers",
- field=models.JSONField(
- blank=True,
- help_text="The headers to send with the notification destination URL.",
- null=True,
- verbose_name="notification destination url headers",
- ),
- ),
- migrations.AddField(
- model_name="workflowaction",
- name="notification_include_document",
- field=models.BooleanField(
- default=False,
- verbose_name="include document in notification",
- ),
- ),
- migrations.AddField(
- model_name="workflowaction",
- name="notification_subject",
- field=models.CharField(
- blank=True,
- help_text="The subject of the notification, can include some placeholders, see documentation.",
- max_length=256,
- null=True,
- verbose_name="notification subject",
+ verbose_name="webhook url",
),
),
migrations.AlterField(
model_name="workflowaction",
name="type",
field=models.PositiveIntegerField(
- choices=[(1, "Assignment"), (2, "Removal"), (3, "Notification")],
+ choices=[
+ (1, "Assignment"),
+ (2, "Removal"),
+ (3, "Email"),
+ (4, "Webhook"),
+ ],
default=1,
verbose_name="Workflow Action Type",
),
diff --git a/src/documents/models.py b/src/documents/models.py
index 335fa168d..353b722b3 100644
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -1166,9 +1166,13 @@ class WorkflowAction(models.Model):
2,
_("Removal"),
)
- NOTIFICATION = (
+ EMAIL = (
3,
- _("Notification"),
+ _("Email"),
+ )
+ WEBHOOK = (
+ 4,
+ _("Webhook"),
)
type = models.PositiveIntegerField(
@@ -1371,53 +1375,65 @@ class WorkflowAction(models.Model):
verbose_name=_("remove all custom fields"),
)
- notification_subject = models.CharField(
- _("notification subject"),
+ email_subject = models.CharField(
+ _("email subject"),
max_length=256,
null=True,
blank=True,
help_text=_(
- "The subject of the notification, can include some placeholders, "
+ "The subject of the email, can include some placeholders, "
"see documentation.",
),
)
- notification_body = models.TextField(
- _("notification body"),
+ email_body = models.TextField(
+ _("email body"),
null=True,
blank=True,
help_text=_(
- "The body (message) of the notification, can include some placeholders, "
+ "The body (message) of the email, can include some placeholders, "
"see documentation.",
),
)
- notification_destination_emails = models.TextField(
- _("notification destination emails"),
+ email_to = models.TextField(
+ _("emails to"),
null=True,
blank=True,
help_text=_(
- "The destination email addresses for the notification, comma separated.",
+ "The destination email addresses, comma separated.",
),
)
- notification_destination_url = models.URLField(
- _("notification destination url"),
+ email_include_document = models.BooleanField(
+ default=False,
+ verbose_name=_("include document in email"),
+ )
+
+ webhook_url = models.URLField(
+ _("webhook url"),
null=True,
blank=True,
help_text=_("The destination URL for the notification."),
)
- notification_destination_url_headers = models.JSONField(
- _("notification destination url headers"),
+ webhook_params = models.JSONField(
+ _("webhook parameters"),
null=True,
blank=True,
- help_text=_("The headers to send with the notification destination URL."),
+ help_text=_("The parameters to send with the webhook URL."),
)
- notification_include_document = models.BooleanField(
+ webhook_headers = models.JSONField(
+ _("webhook headers"),
+ null=True,
+ blank=True,
+ help_text=_("The headers to send with the webhook URL."),
+ )
+
+ webhook_include_document = models.BooleanField(
default=False,
- verbose_name=_("include document in notification"),
+ verbose_name=_("include document in webhook"),
)
class Meta:
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index f9335e611..b1559a510 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -1847,12 +1847,13 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
"remove_view_groups",
"remove_change_users",
"remove_change_groups",
- "notification_subject",
- "notification_body",
- "notification_destination_emails",
- "notification_destination_url",
- "notification_destination_url_headers",
- "notification_include_document",
+ "email_to",
+ "email_subject",
+ "email_body",
+ "email_include_document",
+ "webhook_url",
+ "webhook_params",
+ "webhook_headers",
]
def validate(self, attrs):
@@ -1890,34 +1891,39 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
{"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
)
- if (
- "type" in attrs
- and attrs["type"] == WorkflowAction.WorkflowActionType.NOTIFICATION
- ):
+ if "type" in attrs and attrs["type"] == WorkflowAction.WorkflowActionType.EMAIL:
if (
- "notification_subject" not in attrs
- or attrs["notification_subject"] is None
- or len(attrs["notification_subject"]) == 0
- or "notification_body" not in attrs
- or attrs["notification_body"] is None
- or len(attrs["notification_body"]) == 0
+ "email_subject" not in attrs
+ or attrs["email_subject"] is None
+ or len(attrs["email_subject"]) == 0
+ or "email_body" not in attrs
+ or attrs["email_body"] is None
+ or len(attrs["email_body"]) == 0
):
raise serializers.ValidationError(
- "Notification subject and body required",
+ "Email subject and body required",
)
elif (
- "notification_destination_emails" not in attrs
- or attrs["notification_destination_emails"] is None
- or len(attrs["notification_destination_emails"]) == 0
- ) and (
- "notification_destination_url" not in attrs
- or attrs["notification_destination_url"] is None
- or len(attrs["notification_destination_url"]) == 0
+ "email_to" not in attrs
+ or attrs["email_to"] is None
+ or len(attrs["email_to"]) == 0
):
raise serializers.ValidationError(
- "Notification destination emails or URL required",
+ "Email recipient required",
)
+ if (
+ "type" in attrs
+ and attrs["type"] == WorkflowAction.WorkflowActionType.WEBHOOK
+ ) and (
+ "webhook_url" not in attrs
+ or attrs["webhook_url"] is None
+ or len(attrs["webhook_url"]) == 0
+ ):
+ raise serializers.ValidationError(
+ "Webhook URL required",
+ )
+
return attrs
diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py
index 8b4373282..5f206c7c3 100644
--- a/src/documents/signals/handlers.py
+++ b/src/documents/signals/handlers.py
@@ -869,7 +869,14 @@ def run_workflows(
):
overrides.custom_field_ids.remove(field.pk)
- def notification_action():
+ def email_action():
+ if not settings.EMAIL_ENABLED:
+ logger.error(
+ "Email backend has not been configured, cannot send email notifications",
+ extra={"group": logging_group},
+ )
+ return
+
title = (
document.title
if isinstance(document, Document)
@@ -878,9 +885,8 @@ def run_workflows(
doc_url = None
if isinstance(document, Document):
doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/"
-
subject = parse_w_workflow_placeholders(
- action.notification_subject,
+ action.email_subject,
document.correspondent.name if document.correspondent else "",
document.document_type.name if document.document_type else "",
document.owner.username if document.owner else "",
@@ -891,7 +897,7 @@ def run_workflows(
doc_url,
)
body = parse_w_workflow_placeholders(
- action.notification_body,
+ action.email_body,
document.correspondent.name if document.correspondent else "",
document.document_type.name if document.document_type else "",
document.owner.username if document.owner else "",
@@ -901,76 +907,83 @@ def run_workflows(
title,
doc_url,
)
+ try:
+ email = EmailMessage(
+ subject=subject,
+ body=body,
+ to=action.email_to.split(","),
+ )
+ if action.email_include_document:
+ email.attach_file(document.source_path)
+ n_messages = email.send()
+ logger.debug(
+ f"Sent {n_messages} notification email(s) to {action.email_to}",
+ extra={"group": logging_group},
+ )
+ except Exception as e:
+ logger.exception(
+ f"Error occurred sending notification email: {e}",
+ extra={"group": logging_group},
+ )
- if action.notification_destination_emails:
- if not settings.EMAIL_ENABLED:
- logger.error(
- "Email backend has not been configured, cannot send email notifications",
- extra={"group": logging_group},
+ def webhook_action():
+ title = (
+ document.title
+ if isinstance(document, Document)
+ else str(document.original_file)
+ )
+ doc_url = None
+ if isinstance(document, Document):
+ doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/"
+
+ try:
+ params = {}
+ params_json = json.loads(action.webhook_params)
+ for key, value in params_json.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,
)
- else:
+ headers = None
+ if action.webhook_headers:
try:
- email = EmailMessage(
- subject=subject,
- body=body,
- to=action.notification_destination_emails.split(","),
- )
- if action.notification_include_document:
- email.attach_file(document.source_path)
- n_messages = email.send()
- logger.debug(
- f"Sent {n_messages} notification email(s) to {action.notification_destination_emails}",
- extra={"group": logging_group},
+ # headers are a JSON object with key-value pairs, needs to be converted to a Mapping[str, str]
+ header_mapping = json.loads(
+ action.webhook_headers,
)
+ headers = {str(k): str(v) for k, v in header_mapping.items()}
except Exception as e:
- logger.exception(
- f"Error occurred sending notification email: {e}",
+ logger.error(
+ f"Error occurred parsing webhook headers: {e}",
extra={"group": logging_group},
)
- if action.notification_destination_url:
- try:
- data = {
- "title": subject,
- "message": body,
- }
- files = None
- headers = None
- if action.notification_destination_url_headers:
- try:
- # headers are a JSON object with key-value pairs, needs to be converted to a Mapping[str, str]
- header_mapping = json.loads(
- action.notification_destination_url_headers,
- )
- headers = {str(k): str(v) for k, v in header_mapping.items()}
- except Exception as e:
- logger.error(
- f"Error occurred parsing notification destination URL headers: {e}",
- extra={"group": logging_group},
- )
- if action.notification_include_document:
- with document.source_file as f:
- files = {"document": f}
- response = httpx.post(
- action.notification_destination_url,
- data=data,
- headers=headers,
- files=files,
- )
- logger.debug(
- f"Response from notification destination URL: {response}",
- extra={"group": logging_group},
- )
- else:
+ if action.webhook_include_document:
+ with open(document.source_path, "rb") as f:
+ files = {"file": (document.original_filename, f)}
httpx.post(
- action.notification_destination_url,
- data=data,
+ action.webhook_url,
+ data=params,
+ files=files,
headers=headers,
)
- except Exception as e:
- logger.exception(
- f"Error occurred sending notification to destination URL: {e}",
- extra={"group": logging_group},
+ else:
+ httpx.post(
+ action.webhook_url,
+ data=params,
+ headers=headers,
)
+ except Exception as e:
+ logger.exception(
+ f"Error occurred sending webhook: {e}",
+ extra={"group": logging_group},
+ )
use_overrides = overrides is not None
messages = []
@@ -1017,8 +1030,10 @@ def run_workflows(
assignment_action()
elif action.type == WorkflowAction.WorkflowActionType.REMOVAL:
removal_action()
- elif action.type == WorkflowAction.WorkflowActionType.NOTIFICATION:
- notification_action()
+ elif action.type == WorkflowAction.WorkflowActionType.EMAIL:
+ email_action()
+ elif action.type == WorkflowAction.WorkflowActionType.WEBHOOK:
+ webhook_action()
if not use_overrides:
# save first before setting tags
diff --git a/src/documents/tests/test_api_workflows.py b/src/documents/tests/test_api_workflows.py
index eae36d766..711f304f6 100644
--- a/src/documents/tests/test_api_workflows.py
+++ b/src/documents/tests/test_api_workflows.py
@@ -434,10 +434,10 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
self.assertEqual(WorkflowAction.objects.all().count(), 1)
self.assertNotEqual(workflow.actions.first().id, self.action.id)
- def test_notification_action_validation(self):
+ def test_email_action_validation(self):
"""
GIVEN:
- - API request to create a workflow with a notification action
+ - API request to create a workflow with an email action
WHEN:
- API is called
THEN:
@@ -458,14 +458,14 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
],
"actions": [
{
- "type": WorkflowAction.WorkflowActionType.NOTIFICATION,
+ "type": WorkflowAction.WorkflowActionType.EMAIL,
},
],
},
),
content_type="application/json",
)
- # Notification action requires subject and body
+ # Notification action requires to, subject and body
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = self.client.post(
@@ -483,9 +483,9 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
],
"actions": [
{
- "type": WorkflowAction.WorkflowActionType.NOTIFICATION,
- "notification_subject": "Subject",
- "notification_body": "Body",
+ "type": WorkflowAction.WorkflowActionType.EMAIL,
+ "email_subject": "Subject",
+ "email_body": "Body",
},
],
},
@@ -510,10 +510,69 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
],
"actions": [
{
- "type": WorkflowAction.WorkflowActionType.NOTIFICATION,
- "notification_subject": "Subject",
- "notification_body": "Body",
- "notification_destination_emails": "me@example.com",
+ "type": WorkflowAction.WorkflowActionType.EMAIL,
+ "email_subject": "Subject",
+ "email_body": "Body",
+ "email_to": "me@example.com",
+ },
+ ],
+ },
+ ),
+ content_type="application/json",
+ )
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ def test_webhook_action_validation(self):
+ """
+ GIVEN:
+ - API request to create a workflow with a notification action
+ WHEN:
+ - API is called
+ THEN:
+ - Correct HTTP response
+ """
+ response = self.client.post(
+ self.ENDPOINT,
+ json.dumps(
+ {
+ "name": "Workflow 2",
+ "order": 1,
+ "triggers": [
+ {
+ "type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
+ "sources": [DocumentSource.ApiUpload],
+ "filter_filename": "*",
+ },
+ ],
+ "actions": [
+ {
+ "type": WorkflowAction.WorkflowActionType.WEBHOOK,
+ },
+ ],
+ },
+ ),
+ content_type="application/json",
+ )
+ # Notification action requires url
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ response = self.client.post(
+ self.ENDPOINT,
+ json.dumps(
+ {
+ "name": "Workflow 2",
+ "order": 1,
+ "triggers": [
+ {
+ "type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
+ "sources": [DocumentSource.ApiUpload],
+ "filter_filename": "*",
+ },
+ ],
+ "actions": [
+ {
+ "type": WorkflowAction.WorkflowActionType.WEBHOOK,
+ "webhook_url": "https://example.com",
},
],
},
diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py
index dfc742f41..429cf4224 100644
--- a/src/documents/tests/test_workflows.py
+++ b/src/documents/tests/test_workflows.py
@@ -1,4 +1,3 @@
-import json
import shutil
from datetime import timedelta
from typing import TYPE_CHECKING
@@ -2092,14 +2091,14 @@ class TestWorkflows(
)
@mock.patch("httpx.post")
@mock.patch("django.core.mail.message.EmailMessage.send")
- def test_workflow_notifcation_action(self, mock_email_send, mock_post):
+ def test_workflow_email_action(self, mock_email_send, mock_post):
"""
GIVEN:
- - Document updated workflow with notification action
+ - Document updated workflow with email action
WHEN:
- Document that matches is updated
THEN:
- - Notification is sent
+ - email is sent
"""
mock_post.return_value = mock.Mock(
status_code=200,
@@ -2111,13 +2110,11 @@ class TestWorkflows(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
- type=WorkflowAction.WorkflowActionType.NOTIFICATION,
- notification_subject="Test Notification: {doc_title}",
- notification_body="Test message: {doc_url}",
- notification_destination_emails="user@example.com",
- notification_destination_url="http://paperless-ngx.com",
- notification_destination_url_headers=json.dumps({"x-api-key": "test"}),
- notification_include_document=False,
+ type=WorkflowAction.WorkflowActionType.EMAIL,
+ email_subject="Test Notification: {doc_title}",
+ email_body="Test message: {doc_url}",
+ email_to="user@example.com",
+ email_include_document=False,
)
w = Workflow.objects.create(
name="Workflow 1",
@@ -2136,14 +2133,6 @@ class TestWorkflows(
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
mock_email_send.assert_called_once()
- mock_post.assert_called_once_with(
- "http://paperless-ngx.com",
- data={
- "title": "Test Notification: sample test",
- "message": "Test message: http://localhost:8000/documents/1/",
- },
- headers={"x-api-key": "test"},
- )
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
@@ -2152,10 +2141,10 @@ class TestWorkflows(
)
@mock.patch("httpx.post")
@mock.patch("django.core.mail.message.EmailMessage.send")
- def test_workflow_notification_include_file(self, mock_email_send, mock_post):
+ def test_workflow_email_include_file(self, mock_email_send, mock_post):
"""
GIVEN:
- - Document updated workflow with notification action
+ - Document updated workflow with email action
- Include document is set to True
WHEN:
- Document that matches is updated
@@ -2173,12 +2162,11 @@ class TestWorkflows(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
- type=WorkflowAction.WorkflowActionType.NOTIFICATION,
- notification_subject="Test Notification: {doc_title}",
- notification_body="Test message: {doc_url}",
- notification_destination_emails="me@example.com",
- notification_destination_url="http://paperless-ngx.com",
- notification_include_document=True,
+ type=WorkflowAction.WorkflowActionType.EMAIL,
+ email_subject="Test Notification: {doc_title}",
+ email_body="Test message: {doc_url}",
+ email_to="me@example.com",
+ email_include_document=True,
)
w = Workflow.objects.create(
name="Workflow 1",
@@ -2198,69 +2186,13 @@ class TestWorkflows(
mock_email_send.assert_called_once()
- mock_post.assert_called_once_with(
- "http://paperless-ngx.com",
- data={
- "title": "Test Notification: sample test",
- "message": "Test message: http://localhost:8000/documents/1/",
- },
- headers=None,
- files={"document": mock.ANY},
- )
-
@override_settings(
- PAPERLESS_EMAIL_HOST="localhost",
- EMAIL_ENABLED=True,
- PAPERLESS_URL="http://localhost:8000",
+ EMAIL_ENABLED=False,
)
- def test_workflow_notification_action_fail(self):
+ def test_workflow_email_action_no_email_setup(self):
"""
GIVEN:
- - Document updated workflow with notification action
- WHEN:
- - Document that matches is updated
- - An error occurs during notification
- THEN:
- - Error is logged
- """
- trigger = WorkflowTrigger.objects.create(
- type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
- )
- action = WorkflowAction.objects.create(
- type=WorkflowAction.WorkflowActionType.NOTIFICATION,
- notification_subject="Test Notification: {doc_title}",
- notification_body="Test message: {doc_url}",
- notification_destination_emails="me@example.com",
- notification_destination_url="http://paperless-ngx.com",
- notification_include_document=True,
- )
- 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",
- )
-
- # fails because no file
- with self.assertLogs("paperless.handlers", level="ERROR") as cm:
- run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
-
- expected_str = "Error occurred sending notification email"
- self.assertIn(expected_str, cm.output[0])
- expected_str = "Error occurred sending notification to destination URL"
- self.assertIn(expected_str, cm.output[1])
-
- def test_workflow_notification_action_no_email_setup(self):
- """
- GIVEN:
- - Document updated workflow with notification action
+ - Document updated workflow with email action
- Email is not enabled
WHEN:
- Document that matches is updated
@@ -2271,10 +2203,10 @@ class TestWorkflows(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
- type=WorkflowAction.WorkflowActionType.NOTIFICATION,
- notification_subject="Test Notification: {doc_title}",
- notification_body="Test message: {doc_url}",
- notification_destination_emails="me@example.com",
+ type=WorkflowAction.WorkflowActionType.EMAIL,
+ email_subject="Test Notification: {doc_title}",
+ email_body="Test message: {doc_url}",
+ email_to="me@example.com",
)
w = Workflow.objects.create(
name="Workflow 1",
@@ -2296,10 +2228,56 @@ class TestWorkflows(
expected_str = "Email backend has not been configured"
self.assertIn(expected_str, cm.output[0])
- def test_workflow_notification_action_url_invalid_headers(self):
+ @override_settings(
+ PAPERLESS_EMAIL_HOST="localhost",
+ EMAIL_ENABLED=True,
+ PAPERLESS_URL="http://localhost:8000",
+ )
+ def test_workflow_webhook_action_fail(self):
"""
GIVEN:
- - Document updated workflow with notification action
+ - Document updated workflow with webhook action
+ WHEN:
+ - Document that matches is updated
+ - An error occurs during webhook
+ THEN:
+ - Error is logged
+ """
+ trigger = WorkflowTrigger.objects.create(
+ type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
+ )
+ action = WorkflowAction.objects.create(
+ type=WorkflowAction.WorkflowActionType.WEBHOOK,
+ webhook_params='{"title": "Test webhook: {doc_title}", "body": "Test message: {doc_url}"}',
+ webhook_url="http://paperless-ngx.com",
+ webhook_include_document=True,
+ )
+ 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",
+ )
+
+ # fails because no file
+ with self.assertLogs("paperless.handlers", level="ERROR") as cm:
+ run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
+
+ expected_str = "Error occurred sending webhook"
+ self.assertIn(expected_str, cm.output[0])
+
+ @mock.patch("httpx.post")
+ def test_workflow_notification_action_url_invalid_headers(self, mock_post):
+ """
+ GIVEN:
+ - Document updated workflow with webhook action
- Invalid headers JSON
WHEN:
- Document that matches is updated
@@ -2310,11 +2288,10 @@ class TestWorkflows(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
- type=WorkflowAction.WorkflowActionType.NOTIFICATION,
- notification_subject="Test Notification: {doc_title}",
- notification_body="Test message: {doc_url}",
- notification_destination_url="http://paperless-ngx.com",
- notification_destination_url_headers="invalid",
+ type=WorkflowAction.WorkflowActionType.WEBHOOK,
+ webhook_url="http://paperless-ngx.com",
+ webhook_params='{"title": "Test webhook: {doc_title}", "body": "Test message: {doc_url}"}',
+ webhook_headers="invalid",
)
w = Workflow.objects.create(
name="Workflow 1",
@@ -2333,5 +2310,5 @@ class TestWorkflows(
with self.assertLogs("paperless.handlers", level="ERROR") as cm:
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
- expected_str = "Error occurred parsing notification destination URL headers"
+ expected_str = "Error occurred parsing webhook headers"
self.assertIn(expected_str, cm.output[0])