mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-05-01 11:19:32 -05:00
Split actions into email + webhook
This commit is contained in:
parent
93a58da426
commit
76672b0760
@ -322,15 +322,23 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@case (WorkflowActionType.Notification) {
|
||||
@case (WorkflowActionType.Email) {
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<pngx-input-text i18n-title title="Notification subject" formControlName="notification_subject" [error]="error?.actions?.[i]?.notification_subject"></pngx-input-text>
|
||||
<pngx-input-textarea i18n-title title="Notification body" formControlName="notification_body" [error]="error?.actions?.[i]?.notification_body"></pngx-input-textarea>
|
||||
<pngx-input-text i18n-title title="Notification emails" formControlName="notification_destination_emails" [error]="error?.actions?.[i]?.notification_destination_emails"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Notification url" formControlName="notification_destination_url" [error]="error?.actions?.[i]?.notification_destination_url"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Notification headers" formControlName="notification_destination_url_headers" [error]="error?.actions?.[i]?.notification_destination_url_headers"></pngx-input-text>
|
||||
<pngx-input-switch i18n-title title="Notification include document" formControlName="notification_include_document"></pngx-input-switch>
|
||||
<pngx-input-text i18n-title title="Email subject" formControlName="email_subject" [error]="error?.actions?.[i]?.email_subject"></pngx-input-text>
|
||||
<pngx-input-textarea i18n-title title="Email body" formControlName="email_body" [error]="error?.actions?.[i]?.email_body"></pngx-input-textarea>
|
||||
<pngx-input-text i18n-title title="Email recipients" formControlName="email_to" [error]="error?.actions?.[i]?.email_to"></pngx-input-text>
|
||||
<pngx-input-switch i18n-title title="Attach document" formControlName="email_include_document"></pngx-input-switch>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@case (WorkflowActionType.Webhook) {
|
||||
<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-text i18n-title title="Webhook params" formControlName="webhook_params" [error]="error?.actions?.[i]?.webhook_params"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Webhook headers" formControlName="webhook_headers" [error]="error?.actions?.[i]?.webhook_headers"></pngx-input-text>
|
||||
<pngx-input-switch i18n-title title="Include document" formControlName="webhook_include_document"></pngx-input-switch>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
),
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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])
|
||||
|
Loading…
x
Reference in New Issue
Block a user