Use discreet model for email / webhook

This commit is contained in:
shamoon 2024-11-25 21:09:13 -08:00
parent 69a4331d99
commit 6b36ee7819
No known key found for this signature in database
11 changed files with 501 additions and 323 deletions

View File

@ -323,27 +323,29 @@
</div> </div>
} }
@case (WorkflowActionType.Email) { @case (WorkflowActionType.Email) {
<div class="row"> <div class="row" [formGroup]="formGroup.get('email')">
<input type="hidden" formControlName="id" />
<div class="col"> <div class="col">
<pngx-input-text i18n-title title="Email subject" formControlName="email_subject" [error]="error?.actions?.[i]?.email_subject"></pngx-input-text> <pngx-input-text i18n-title title="Email subject" formControlName="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-textarea i18n-title title="Email body" formControlName="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-text i18n-title title="Email recipients" formControlName="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> <pngx-input-switch i18n-title title="Attach document" formControlName="include_document"></pngx-input-switch>
</div> </div>
</div> </div>
} }
@case (WorkflowActionType.Webhook) { @case (WorkflowActionType.Webhook) {
<div class="row"> <div class="row" [formGroup]="formGroup.get('webhook')">
<input type="hidden" formControlName="id" />
<div class="col"> <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 url" formControlName="url" [error]="error?.actions?.[i]?.url"></pngx-input-text>
<pngx-input-switch i18n-title title="Use parameters for webhook body" formControlName="webhook_use_params"></pngx-input-switch> <pngx-input-switch i18n-title title="Use parameters for webhook body" formControlName="use_params"></pngx-input-switch>
@if (formGroup.get('webhook_use_params').value) { @if (formGroup.get('webhook').value['use_params']) {
<pngx-input-entries i18n-title title="Webhook params" formControlName="webhook_params" [error]="error?.actions?.[i]?.webhook_params"></pngx-input-entries> <pngx-input-entries i18n-title title="Webhook params" formControlName="params" [error]="error?.actions?.[i]?.params"></pngx-input-entries>
} @else { } @else {
<pngx-input-textarea i18n-title title="Webhook body" formControlName="webhook_body" [error]="error?.actions?.[i]?.webhook_body"></pngx-input-textarea> <pngx-input-textarea i18n-title title="Webhook body" formControlName="body" [error]="error?.actions?.[i]?.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-entries i18n-title title="Webhook headers" formControlName="headers" [error]="error?.actions?.[i]?.headers"></pngx-input-entries>
<pngx-input-switch i18n-title title="Include document" formControlName="webhook_include_document"></pngx-input-switch> <pngx-input-switch i18n-title title="Include document" formControlName="include_document"></pngx-input-switch>
</div> </div>
</div> </div>
} }

View File

@ -347,4 +347,15 @@ describe('WorkflowEditDialogComponent', () => {
component.actionFields.at(0).get('remove_change_groups').disabled component.actionFields.at(0).get('remove_change_groups').disabled
).toBeFalsy() ).toBeFalsy()
}) })
it('should prune empty nested objects on save', () => {
component.object = workflow
component.addTrigger()
component.addAction()
expect(component.objectForm.get('actions').value[0].email).not.toBeNull()
expect(component.objectForm.get('actions').value[0].webhook).not.toBeNull()
component.save()
expect(component.objectForm.get('actions').value[0].email).toBeNull()
expect(component.objectForm.get('actions').value[0].webhook).toBeNull()
})
}) })

View File

@ -410,18 +410,22 @@ export class WorkflowEditDialogComponent
remove_all_custom_fields: new FormControl( remove_all_custom_fields: new FormControl(
action.remove_all_custom_fields action.remove_all_custom_fields
), ),
email_subject: new FormControl(action.email_subject), email: new FormGroup({
email_body: new FormControl(action.email_body), id: new FormControl(action.email?.id),
email_to: new FormControl(action.email_to), subject: new FormControl(action.email?.subject),
email_include_document: new FormControl(action.email_include_document), body: new FormControl(action.email?.body),
webhook_url: new FormControl(action.webhook_url), to: new FormControl(action.email?.to),
webhook_use_params: new FormControl(action.webhook_use_params), include_document: new FormControl(!!action.email?.include_document),
webhook_params: new FormControl(action.webhook_params), }),
webhook_body: new FormControl(action.webhook_body), webhook: new FormGroup({
webhook_headers: new FormControl(action.webhook_headers), id: new FormControl(action.webhook?.id),
webhook_include_document: new FormControl( url: new FormControl(action.webhook?.url),
action.webhook_include_document use_params: new FormControl(action.webhook?.use_params),
), params: new FormControl(action.webhook?.params),
body: new FormControl(action.webhook?.body),
headers: new FormControl(action.webhook?.headers),
include_document: new FormControl(!!action.webhook?.include_document),
}),
}), }),
{ emitEvent } { emitEvent }
) )
@ -523,16 +527,22 @@ export class WorkflowEditDialogComponent
remove_all_permissions: false, remove_all_permissions: false,
remove_custom_fields: [], remove_custom_fields: [],
remove_all_custom_fields: false, remove_all_custom_fields: false,
email_subject: null, email: {
email_body: null, id: null,
email_to: null, subject: null,
email_include_document: false, body: null,
webhook_url: null, to: null,
webhook_use_params: true, include_document: false,
webhook_params: null, },
webhook_body: null, webhook: {
webhook_headers: null, id: null,
webhook_include_document: false, url: null,
use_params: true,
params: null,
body: null,
headers: null,
include_document: false,
},
} }
this.object.actions.push(action) this.object.actions.push(action)
this.createActionField(action) this.createActionField(action)
@ -563,4 +573,18 @@ export class WorkflowEditDialogComponent
c.get('id').setValue(null, { emitEvent: false }) c.get('id').setValue(null, { emitEvent: false })
) )
} }
save(): void {
this.objectForm
.get('actions')
.value.forEach((action: WorkflowAction, i) => {
if (action.type !== WorkflowActionType.Webhook) {
action.webhook = null
}
if (action.type !== WorkflowActionType.Email) {
action.email = null
}
})
super.save()
}
} }

View File

@ -6,6 +6,31 @@ export enum WorkflowActionType {
Email = 3, Email = 3,
Webhook = 4, Webhook = 4,
} }
export interface WorkflowActionEmail extends ObjectWithId {
subject?: string
body?: string
to?: string
include_document?: boolean
}
export interface WorkflowActionWebhook extends ObjectWithId {
url?: string
use_params?: boolean
params?: object
body?: string
headers?: object
include_document?: boolean
}
export interface WorkflowAction extends ObjectWithId { export interface WorkflowAction extends ObjectWithId {
type: WorkflowActionType type: WorkflowActionType
@ -65,23 +90,7 @@ export interface WorkflowAction extends ObjectWithId {
remove_all_custom_fields?: boolean remove_all_custom_fields?: boolean
email_subject?: string email?: WorkflowActionEmail
email_body?: string webhook?: WorkflowActionWebhook
email_to?: string
email_include_document?: boolean
webhook_url?: string
webhook_use_params?: boolean
webhook_params?: object
webhook_body?: string
webhook_headers?: object
webhook_include_document?: boolean
} }

View File

@ -1,119 +0,0 @@
# Generated by Django 5.1.3 on 2024-11-24 18:30
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1058_workflowtrigger_schedule_date_custom_field_and_more"),
]
operations = [
migrations.AddField(
model_name="workflowaction",
name="email_body",
field=models.TextField(
blank=True,
help_text="The body (message) of the email, can include some placeholders, see documentation.",
null=True,
verbose_name="email body",
),
),
migrations.AddField(
model_name="workflowaction",
name="email_include_document",
field=models.BooleanField(
default=False,
verbose_name="include document in email",
),
),
migrations.AddField(
model_name="workflowaction",
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_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",
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 if body not used.",
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="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",
field=models.PositiveIntegerField(
choices=[
(1, "Assignment"),
(2, "Removal"),
(3, "Email"),
(4, "Webhook"),
],
default=1,
verbose_name="Workflow Action Type",
),
),
]

View File

@ -0,0 +1,154 @@
# Generated by Django 5.1.3 on 2024-11-26 04:07
import django.db.models.deletion
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1058_workflowtrigger_schedule_date_custom_field_and_more"),
]
operations = [
migrations.CreateModel(
name="WorkflowActionEmail",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"subject",
models.CharField(
help_text="The subject of the email, can include some placeholders, see documentation.",
max_length=256,
verbose_name="email subject",
),
),
(
"body",
models.TextField(
help_text="The body (message) of the email, can include some placeholders, see documentation.",
verbose_name="email body",
),
),
(
"to",
models.TextField(
help_text="The destination email addresses, comma separated.",
verbose_name="emails to",
),
),
(
"include_document",
models.BooleanField(
default=False,
verbose_name="include document in email",
),
),
],
),
migrations.CreateModel(
name="WorkflowActionWebhook",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"url",
models.URLField(
help_text="The destination URL for the notification.",
verbose_name="webhook url",
),
),
(
"use_params",
models.BooleanField(default=True, verbose_name="use parameters"),
),
(
"params",
models.JSONField(
blank=True,
help_text="The parameters to send with the webhook URL if body not used.",
null=True,
verbose_name="webhook parameters",
),
),
(
"body",
models.TextField(
blank=True,
help_text="The body to send with the webhook URL if parameters not used.",
null=True,
verbose_name="webhook body",
),
),
(
"headers",
models.JSONField(
blank=True,
help_text="The headers to send with the webhook URL.",
null=True,
verbose_name="webhook headers",
),
),
(
"include_document",
models.BooleanField(
default=False,
verbose_name="include document in webhook",
),
),
],
),
migrations.AlterField(
model_name="workflowaction",
name="type",
field=models.PositiveIntegerField(
choices=[
(1, "Assignment"),
(2, "Removal"),
(3, "Email"),
(4, "Webhook"),
],
default=1,
verbose_name="Workflow Action Type",
),
),
migrations.AddField(
model_name="workflowaction",
name="email",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="action",
to="documents.workflowactionemail",
verbose_name="email",
),
),
migrations.AddField(
model_name="workflowaction",
name="webhook",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="action",
to="documents.workflowactionwebhook",
verbose_name="webhook",
),
),
]

View File

@ -1156,6 +1156,85 @@ class WorkflowTrigger(models.Model):
return f"WorkflowTrigger {self.pk}" return f"WorkflowTrigger {self.pk}"
class WorkflowActionEmail(models.Model):
subject = models.CharField(
_("email subject"),
max_length=256,
null=False,
help_text=_(
"The subject of the email, can include some placeholders, "
"see documentation.",
),
)
body = models.TextField(
_("email body"),
null=False,
help_text=_(
"The body (message) of the email, can include some placeholders, "
"see documentation.",
),
)
to = models.TextField(
_("emails to"),
null=False,
help_text=_(
"The destination email addresses, comma separated.",
),
)
include_document = models.BooleanField(
default=False,
verbose_name=_("include document in email"),
)
def __str__(self):
return f"Workflow Email Action {self.pk}"
class WorkflowActionWebhook(models.Model):
url = models.URLField(
_("webhook url"),
null=False,
help_text=_("The destination URL for the notification."),
)
use_params = models.BooleanField(
default=True,
verbose_name=_("use parameters"),
)
params = models.JSONField(
_("webhook parameters"),
null=True,
blank=True,
help_text=_("The parameters to send with the webhook URL if body not used."),
)
body = models.TextField(
_("webhook body"),
null=True,
blank=True,
help_text=_("The body to send with the webhook URL if parameters not used."),
)
headers = models.JSONField(
_("webhook headers"),
null=True,
blank=True,
help_text=_("The headers to send with the webhook URL."),
)
include_document = models.BooleanField(
default=False,
verbose_name=_("include document in webhook"),
)
def __str__(self):
return f"Workflow Webhook Action {self.pk}"
class WorkflowAction(models.Model): class WorkflowAction(models.Model):
class WorkflowActionType(models.IntegerChoices): class WorkflowActionType(models.IntegerChoices):
ASSIGNMENT = ( ASSIGNMENT = (
@ -1375,77 +1454,22 @@ class WorkflowAction(models.Model):
verbose_name=_("remove all custom fields"), verbose_name=_("remove all custom fields"),
) )
email_subject = models.CharField( email = models.ForeignKey(
_("email subject"), WorkflowActionEmail,
max_length=256,
null=True, null=True,
blank=True, blank=True,
help_text=_( on_delete=models.SET_NULL,
"The subject of the email, can include some placeholders, " related_name="action",
"see documentation.", verbose_name=_("email"),
),
) )
email_body = models.TextField( webhook = models.ForeignKey(
_("email body"), WorkflowActionWebhook,
null=True, null=True,
blank=True, blank=True,
help_text=_( on_delete=models.SET_NULL,
"The body (message) of the email, can include some placeholders, " related_name="action",
"see documentation.", verbose_name=_("webhook"),
),
)
email_to = models.TextField(
_("emails to"),
null=True,
blank=True,
help_text=_(
"The destination email addresses, comma separated.",
),
)
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."),
)
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 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(
_("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 webhook"),
) )
class Meta: class Meta:

View File

@ -49,6 +49,8 @@ from documents.models import Tag
from documents.models import UiSettings from documents.models import UiSettings
from documents.models import Workflow from documents.models import Workflow
from documents.models import WorkflowAction from documents.models import WorkflowAction
from documents.models import WorkflowActionEmail
from documents.models import WorkflowActionWebhook
from documents.models import WorkflowTrigger from documents.models import WorkflowTrigger
from documents.parsers import is_mime_type_supported from documents.parsers import is_mime_type_supported
from documents.permissions import get_groups_with_only_permission from documents.permissions import get_groups_with_only_permission
@ -1807,12 +1809,44 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer):
return attrs return attrs
class WorkflowActionEmailSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(allow_null=True, required=False)
class Meta:
model = WorkflowActionEmail
fields = [
"id",
"subject",
"body",
"to",
"include_document",
]
class WorkflowActionWebhookSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(allow_null=True, required=False)
class Meta:
model = WorkflowActionWebhook
fields = [
"id",
"url",
"use_params",
"params",
"body",
"headers",
"include_document",
]
class WorkflowActionSerializer(serializers.ModelSerializer): class WorkflowActionSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, allow_null=True) id = serializers.IntegerField(required=False, allow_null=True)
assign_correspondent = CorrespondentField(allow_null=True, required=False) assign_correspondent = CorrespondentField(allow_null=True, required=False)
assign_tags = TagsField(many=True, allow_null=True, required=False) assign_tags = TagsField(many=True, allow_null=True, required=False)
assign_document_type = DocumentTypeField(allow_null=True, required=False) assign_document_type = DocumentTypeField(allow_null=True, required=False)
assign_storage_path = StoragePathField(allow_null=True, required=False) assign_storage_path = StoragePathField(allow_null=True, required=False)
email = WorkflowActionEmailSerializer(allow_null=True, required=False)
webhook = WorkflowActionWebhookSerializer(allow_null=True, required=False)
class Meta: class Meta:
model = WorkflowAction model = WorkflowAction
@ -1847,15 +1881,8 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
"remove_view_groups", "remove_view_groups",
"remove_change_users", "remove_change_users",
"remove_change_groups", "remove_change_groups",
"email_to", "email",
"email_subject", "webhook",
"email_body",
"email_include_document",
"webhook_url",
"webhook_use_params",
"webhook_params",
"webhook_body",
"webhook_headers",
] ]
def validate(self, attrs): def validate(self, attrs):
@ -1893,37 +1920,22 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
{"assign_title": f'Invalid f-string detected: "{e.args[0]}"'}, {"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
) )
if "type" in attrs and attrs["type"] == WorkflowAction.WorkflowActionType.EMAIL: if (
if ( "type" in attrs
"email_subject" not in attrs and attrs["type"] == WorkflowAction.WorkflowActionType.EMAIL
or attrs["email_subject"] is None and "email" not in attrs
or len(attrs["email_subject"]) == 0 ):
or "email_body" not in attrs raise serializers.ValidationError(
or attrs["email_body"] is None "Email data is required for email actions",
or len(attrs["email_body"]) == 0 )
):
raise serializers.ValidationError(
"Email subject and body required",
)
elif (
"email_to" not in attrs
or attrs["email_to"] is None
or len(attrs["email_to"]) == 0
):
raise serializers.ValidationError(
"Email recipient required",
)
if ( if (
"type" in attrs "type" in attrs
and attrs["type"] == WorkflowAction.WorkflowActionType.WEBHOOK and attrs["type"] == WorkflowAction.WorkflowActionType.WEBHOOK
) and ( and "webhook" not in attrs
"webhook_url" not in attrs
or attrs["webhook_url"] is None
or len(attrs["webhook_url"]) == 0
): ):
raise serializers.ValidationError( raise serializers.ValidationError(
"Webhook URL required", "Webhook data is required for webhook actions",
) )
return attrs return attrs
@ -1980,11 +1992,34 @@ class WorkflowSerializer(serializers.ModelSerializer):
remove_change_users = action.pop("remove_change_users", None) remove_change_users = action.pop("remove_change_users", None)
remove_change_groups = action.pop("remove_change_groups", None) remove_change_groups = action.pop("remove_change_groups", None)
email_data = action.pop("email", None)
webhook_data = action.pop("webhook", None)
action_instance, _ = WorkflowAction.objects.update_or_create( action_instance, _ = WorkflowAction.objects.update_or_create(
id=action.get("id"), id=action.get("id"),
defaults=action, defaults=action,
) )
if email_data is not None:
serializer = WorkflowActionEmailSerializer(data=email_data)
serializer.is_valid(raise_exception=True)
email, _ = WorkflowActionEmail.objects.update_or_create(
id=email_data.get("id"),
defaults=serializer.validated_data,
)
action_instance.email = email
action_instance.save()
if webhook_data is not None:
serializer = WorkflowActionWebhookSerializer(data=webhook_data)
serializer.is_valid(raise_exception=True)
webhook, _ = WorkflowActionWebhook.objects.update_or_create(
id=webhook_data.get("id"),
defaults=serializer.validated_data,
)
action_instance.webhook = webhook
action_instance.save()
if assign_tags is not None: if assign_tags is not None:
action_instance.assign_tags.set(assign_tags) action_instance.assign_tags.set(assign_tags)
if assign_view_users is not None: if assign_view_users is not None:
@ -2037,6 +2072,9 @@ class WorkflowSerializer(serializers.ModelSerializer):
if action.workflows.all().count() == 0: if action.workflows.all().count() == 0:
action.delete() action.delete()
WorkflowActionEmail.objects.filter(action=None).delete()
WorkflowActionWebhook.objects.filter(action=None).delete()
def create(self, validated_data) -> Workflow: def create(self, validated_data) -> Workflow:
if "triggers" in validated_data: if "triggers" in validated_data:
triggers = validated_data.pop("triggers") triggers = validated_data.pop("triggers")

View File

@ -891,7 +891,7 @@ def run_workflows(
added = timezone.localtime(document.added) added = timezone.localtime(document.added)
created = timezone.localtime(document.created) created = timezone.localtime(document.created)
subject = parse_w_workflow_placeholders( subject = parse_w_workflow_placeholders(
action.email_subject, action.email.subject,
correspondent, correspondent,
document_type, document_type,
owner_username, owner_username,
@ -902,7 +902,7 @@ def run_workflows(
doc_url, doc_url,
) )
body = parse_w_workflow_placeholders( body = parse_w_workflow_placeholders(
action.email_body, action.email.body,
correspondent, correspondent,
document_type, document_type,
owner_username, owner_username,
@ -916,13 +916,13 @@ def run_workflows(
email = EmailMessage( email = EmailMessage(
subject=subject, subject=subject,
body=body, body=body,
to=action.email_to.split(","), to=action.email.to.split(","),
) )
if action.email_include_document: if action.email.include_document:
email.attach_file(document.source_path) email.attach_file(document.source_path)
n_messages = email.send() n_messages = email.send()
logger.debug( logger.debug(
f"Sent {n_messages} notification email(s) to {action.email_to}", f"Sent {n_messages} notification email(s) to {action.email.to}",
extra={"group": logging_group}, extra={"group": logging_group},
) )
except Exception as e: except Exception as e:
@ -949,9 +949,9 @@ def run_workflows(
try: try:
data = {} data = {}
if action.webhook_use_params: if action.webhook.use_params:
try: try:
for key, value in action.webhook_params.items(): for key, value in action.webhook.params.items():
data[key] = parse_w_workflow_placeholders( data[key] = parse_w_workflow_placeholders(
value, value,
correspondent, correspondent,
@ -970,7 +970,7 @@ def run_workflows(
) )
else: else:
data = parse_w_workflow_placeholders( data = parse_w_workflow_placeholders(
action.webhook_body, action.webhook.body,
correspondent, correspondent,
document_type, document_type,
owner_username, owner_username,
@ -981,30 +981,30 @@ def run_workflows(
doc_url, doc_url,
) )
headers = {} headers = {}
if action.webhook_headers: if action.webhook.headers:
try: try:
headers = { headers = {
str(k): str(v) for k, v in action.webhook_headers.items() str(k): str(v) for k, v in action.webhook.headers.items()
} }
except Exception as e: except Exception as e:
logger.error( logger.error(
f"Error occurred parsing webhook headers: {e}", f"Error occurred parsing webhook headers: {e}",
extra={"group": logging_group}, extra={"group": logging_group},
) )
if action.webhook_include_document: if action.webhook.include_document:
with open(document.source_path, "rb") as f: with open(document.source_path, "rb") as f:
files = { files = {
"file": (document.original_filename, f, document.mime_type), "file": (document.original_filename, f, document.mime_type),
} }
httpx.post( httpx.post(
action.webhook_url, action.webhook.url,
data=data, data=data,
files=files, files=files,
headers=headers, headers=headers,
) )
else: else:
httpx.post( httpx.post(
action.webhook_url, action.webhook.url,
data=data, data=data,
headers=headers, headers=headers,
) )

View File

@ -484,8 +484,10 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
"actions": [ "actions": [
{ {
"type": WorkflowAction.WorkflowActionType.EMAIL, "type": WorkflowAction.WorkflowActionType.EMAIL,
"email_subject": "Subject", "email": {
"email_body": "Body", "subject": "Subject",
"body": "Body",
},
}, },
], ],
}, },
@ -511,9 +513,12 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
"actions": [ "actions": [
{ {
"type": WorkflowAction.WorkflowActionType.EMAIL, "type": WorkflowAction.WorkflowActionType.EMAIL,
"email_subject": "Subject", "email": {
"email_body": "Body", "subject": "Subject",
"email_to": "me@example.com", "body": "Body",
"to": "me@example.com",
"include_document": False,
},
}, },
], ],
}, },
@ -572,7 +577,10 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
"actions": [ "actions": [
{ {
"type": WorkflowAction.WorkflowActionType.WEBHOOK, "type": WorkflowAction.WorkflowActionType.WEBHOOK,
"webhook_url": "https://example.com", "webhook": {
"url": "https://example.com",
"include_document": False,
},
}, },
], ],
}, },

View File

@ -31,6 +31,8 @@ from documents.models import StoragePath
from documents.models import Tag from documents.models import Tag
from documents.models import Workflow from documents.models import Workflow
from documents.models import WorkflowAction from documents.models import WorkflowAction
from documents.models import WorkflowActionEmail
from documents.models import WorkflowActionWebhook
from documents.models import WorkflowRun from documents.models import WorkflowRun
from documents.models import WorkflowTrigger from documents.models import WorkflowTrigger
from documents.signals import document_consumption_finished from documents.signals import document_consumption_finished
@ -2109,12 +2111,15 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
email_action = WorkflowActionEmail.objects.create(
subject="Test Notification: {doc_title}",
body="Test message: {doc_url}",
to="user@example.com",
include_document=False,
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL, type=WorkflowAction.WorkflowActionType.EMAIL,
email_subject="Test Notification: {doc_title}", email=email_action,
email_body="Test message: {doc_url}",
email_to="user@example.com",
email_include_document=False,
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2161,12 +2166,15 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
email_action = WorkflowActionEmail.objects.create(
subject="Test Notification: {doc_title}",
body="Test message: {doc_url}",
to="me@example.com",
include_document=True,
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL, type=WorkflowAction.WorkflowActionType.EMAIL,
email_subject="Test Notification: {doc_title}", email=email_action,
email_body="Test message: {doc_url}",
email_to="me@example.com",
email_include_document=True,
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2202,11 +2210,14 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
email_action = WorkflowActionEmail.objects.create(
subject="Test Notification: {doc_title}",
body="Test message: {doc_url}",
to="me@example.com",
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL, type=WorkflowAction.WorkflowActionType.EMAIL,
email_subject="Test Notification: {doc_title}", email=email_action,
email_body="Test message: {doc_url}",
email_to="me@example.com",
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2247,11 +2258,14 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
email_action = WorkflowActionEmail.objects.create(
subject="Test Notification: {doc_title}",
body="Test message: {doc_url}",
to="me@example.com",
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL, type=WorkflowAction.WorkflowActionType.EMAIL,
email_subject="Test Notification: {doc_title}", email=email_action,
email_body="Test message: {doc_url}",
email_to="me@example.com",
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2296,12 +2310,15 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
webhook_action = WorkflowActionWebhook.objects.create(
use_params=False,
body="Test message: {doc_url}",
url="http://paperless-ngx.com",
include_document=False,
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK, type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_use_params=False, webhook=webhook_action,
webhook_body="Test message: {doc_url}",
webhook_url="http://paperless-ngx.com",
webhook_include_document=False,
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2348,12 +2365,15 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
webhook_action = WorkflowActionWebhook.objects.create(
use_params=False,
body="Test message: {doc_url}",
url="http://paperless-ngx.com",
include_document=True,
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK, type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_use_params=False, webhook=webhook_action,
webhook_body="Test message: {doc_url}",
webhook_url="http://paperless-ngx.com",
webhook_include_document=True,
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2373,6 +2393,7 @@ class TestWorkflows(
correspondent=self.c, correspondent=self.c,
original_filename="simple.pdf", original_filename="simple.pdf",
filename=test_file, filename=test_file,
mime_type="application/pdf",
) )
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc) run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
@ -2380,7 +2401,7 @@ class TestWorkflows(
mock_post.assert_called_once_with( mock_post.assert_called_once_with(
"http://paperless-ngx.com", "http://paperless-ngx.com",
data=f"Test message: http://localhost:8000/documents/{doc.id}/", data=f"Test message: http://localhost:8000/documents/{doc.id}/",
files={"file": ("simple.pdf", mock.ANY)}, files={"file": ("simple.pdf", mock.ANY, "application/pdf")},
headers={}, headers={},
) )
@ -2402,15 +2423,18 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
action = WorkflowAction.objects.create( webhook_action = WorkflowActionWebhook.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK, use_params=True,
webhook_use_params=True, params={
webhook_params={
"title": "Test webhook: {doc_title}", "title": "Test webhook: {doc_title}",
"body": "Test message: {doc_url}", "body": "Test message: {doc_url}",
}, },
webhook_url="http://paperless-ngx.com", url="http://paperless-ngx.com",
webhook_include_document=True, include_document=True,
)
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook=webhook_action,
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -2447,12 +2471,15 @@ class TestWorkflows(
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
) )
webhook_action = WorkflowActionWebhook.objects.create(
url="http://paperless-ngx.com",
use_params=True,
params="invalid",
headers="invalid",
)
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK, type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook_url="http://paperless-ngx.com", webhook=webhook_action,
webhook_use_params=True,
webhook_params="invalid",
webhook_headers="invalid",
) )
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",