mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
✨(template) allow to insert recipient name in reply
This commit is contained in:
@@ -2689,6 +2689,19 @@
|
||||
"operationId": "mailboxes_message_templates_render_retrieve",
|
||||
"description": "Render a template with the provided context variables.",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "*",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Any other parameter will be available in the template context",
|
||||
"examples": {
|
||||
"Example": {
|
||||
"value": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.db.models import Q
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from drf_spectacular.utils import (
|
||||
OpenApiExample,
|
||||
OpenApiParameter,
|
||||
OpenApiResponse,
|
||||
OpenApiTypes,
|
||||
@@ -75,6 +76,16 @@ class MailboxMessageTemplateViewSet(
|
||||
return context
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="*",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Any other parameter will be available in the template context",
|
||||
required=False,
|
||||
examples=[OpenApiExample("Example", value="value")],
|
||||
)
|
||||
],
|
||||
responses={
|
||||
200: OpenApiResponse(
|
||||
description="Template rendered with provided context",
|
||||
@@ -95,7 +106,11 @@ class MailboxMessageTemplateViewSet(
|
||||
"""Render a template."""
|
||||
template = self.get_object()
|
||||
try:
|
||||
rendered = template.render_template(mailbox=self.mailbox, user=request.user)
|
||||
rendered = template.render_template(
|
||||
mailbox=self.mailbox,
|
||||
user=request.user,
|
||||
context=request.query_params.dict(),
|
||||
)
|
||||
return Response(rendered)
|
||||
except (KeyError, ValueError, TypeError) as e:
|
||||
return Response(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Declare and configure the models for the messages core application
|
||||
"""
|
||||
# pylint: disable=too-many-lines,too-many-instance-attributes
|
||||
# pylint: disable=too-many-lines,too-many-instance-attributes,dangerous-default-value
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
@@ -1822,7 +1822,10 @@ class MessageTemplate(BaseModel):
|
||||
return None
|
||||
|
||||
def render_template(
|
||||
self, mailbox: Mailbox = None, user: User = None
|
||||
self,
|
||||
mailbox: Mailbox = None,
|
||||
user: User = None,
|
||||
context: Dict[str, Any] = {}, # noqa: B006
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Render the template with the given context.
|
||||
@@ -1834,12 +1837,12 @@ class MessageTemplate(BaseModel):
|
||||
Returns:
|
||||
Dictionary with 'html_body' and 'text_body' keys containing rendered content
|
||||
"""
|
||||
name = (
|
||||
context = context.copy()
|
||||
context["name"] = (
|
||||
mailbox.contact.name
|
||||
if mailbox and mailbox.contact
|
||||
else (getattr(user, "full_name", None) if user else "")
|
||||
)
|
||||
context = {"name": name}
|
||||
schema = settings.SCHEMA_CUSTOM_ATTRIBUTES_USER
|
||||
schema_properties = schema.get("properties", {})
|
||||
|
||||
|
||||
@@ -164,6 +164,44 @@ class TestMessageTemplateRender:
|
||||
assert "Cordialement, John Doe - Adjointe" in response.data["html_body"]
|
||||
assert "Cordialement, John Doe - Adjointe" in response.data["text_body"]
|
||||
|
||||
@patch(
|
||||
"django.conf.settings.SCHEMA_CUSTOM_ATTRIBUTES_USER",
|
||||
{"properties": {"job_title": {"type": "string"}}},
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"role",
|
||||
[
|
||||
models.MailboxRoleChoices.EDITOR,
|
||||
models.MailboxRoleChoices.SENDER,
|
||||
models.MailboxRoleChoices.VIEWER,
|
||||
models.MailboxRoleChoices.ADMIN,
|
||||
],
|
||||
)
|
||||
def test_success_with_context(self, user, mailbox, role):
|
||||
"""Test successful template rendering with context."""
|
||||
factories.MailboxAccessFactory(
|
||||
mailbox=mailbox,
|
||||
user=user,
|
||||
role=role,
|
||||
)
|
||||
template_reply = factories.MessageTemplateFactory(
|
||||
html_body="<p>Hello {recipient_name}!</p><p> My name is {name} and I'm {job_title}.</p>",
|
||||
text_body="Hello {recipient_name}! My name is {name} and I'm {job_title}.",
|
||||
mailbox=mailbox,
|
||||
)
|
||||
client = APIClient()
|
||||
client.force_authenticate(user=user)
|
||||
url = reverse(
|
||||
"mailbox-message-templates-render-template",
|
||||
kwargs={"mailbox_id": mailbox.id, "pk": template_reply.id},
|
||||
)
|
||||
response = client.get(f"{url}?recipient_name=Jane Smith")
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert "Hello Jane Smith!" in response.data["html_body"]
|
||||
assert "Hello Jane Smith!" in response.data["text_body"]
|
||||
assert "My name is John Doe and I'm Adjointe." in response.data["html_body"]
|
||||
assert "My name is John Doe and I'm Adjointe." in response.data["text_body"]
|
||||
|
||||
def test_render_template_no_access_mailbox(self, user, mailbox):
|
||||
"""Test rendering a template from a mailbox that doesn't exist."""
|
||||
client = APIClient()
|
||||
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
MailboxesMessageTemplatesAvailableListParams,
|
||||
MailboxesMessageTemplatesListParams,
|
||||
MailboxesMessageTemplatesRenderRetrieve200,
|
||||
MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
MailboxesSearchListParams,
|
||||
MessageTemplate,
|
||||
MessageTemplateRequest,
|
||||
@@ -1070,17 +1071,31 @@ export type mailboxesMessageTemplatesRenderRetrieveResponse =
|
||||
export const getMailboxesMessageTemplatesRenderRetrieveUrl = (
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
) => {
|
||||
return `/api/v1.0/mailboxes/${mailboxId}/message-templates/${id}/render/`;
|
||||
const normalizedParams = new URLSearchParams();
|
||||
|
||||
Object.entries(params || {}).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const stringifiedParams = normalizedParams.toString();
|
||||
|
||||
return stringifiedParams.length > 0
|
||||
? `/api/v1.0/mailboxes/${mailboxId}/message-templates/${id}/render/?${stringifiedParams}`
|
||||
: `/api/v1.0/mailboxes/${mailboxId}/message-templates/${id}/render/`;
|
||||
};
|
||||
|
||||
export const mailboxesMessageTemplatesRenderRetrieve = async (
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
options?: RequestInit,
|
||||
): Promise<mailboxesMessageTemplatesRenderRetrieveResponse> => {
|
||||
return fetchAPI<mailboxesMessageTemplatesRenderRetrieveResponse>(
|
||||
getMailboxesMessageTemplatesRenderRetrieveUrl(mailboxId, id),
|
||||
getMailboxesMessageTemplatesRenderRetrieveUrl(mailboxId, id, params),
|
||||
{
|
||||
...options,
|
||||
method: "GET",
|
||||
@@ -1091,9 +1106,11 @@ export const mailboxesMessageTemplatesRenderRetrieve = async (
|
||||
export const getMailboxesMessageTemplatesRenderRetrieveQueryKey = (
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
) => {
|
||||
return [
|
||||
`/api/v1.0/mailboxes/${mailboxId}/message-templates/${id}/render/`,
|
||||
...(params ? [params] : []),
|
||||
] as const;
|
||||
};
|
||||
|
||||
@@ -1103,6 +1120,7 @@ export const getMailboxesMessageTemplatesRenderRetrieveQueryOptions = <
|
||||
>(
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
@@ -1118,12 +1136,12 @@ export const getMailboxesMessageTemplatesRenderRetrieveQueryOptions = <
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ??
|
||||
getMailboxesMessageTemplatesRenderRetrieveQueryKey(mailboxId, id);
|
||||
getMailboxesMessageTemplatesRenderRetrieveQueryKey(mailboxId, id, params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof mailboxesMessageTemplatesRenderRetrieve>>
|
||||
> = ({ signal }) =>
|
||||
mailboxesMessageTemplatesRenderRetrieve(mailboxId, id, {
|
||||
mailboxesMessageTemplatesRenderRetrieve(mailboxId, id, params, {
|
||||
signal,
|
||||
...requestOptions,
|
||||
});
|
||||
@@ -1151,6 +1169,7 @@ export function useMailboxesMessageTemplatesRenderRetrieve<
|
||||
>(
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params: undefined | MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<
|
||||
@@ -1179,6 +1198,7 @@ export function useMailboxesMessageTemplatesRenderRetrieve<
|
||||
>(
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
@@ -1207,6 +1227,7 @@ export function useMailboxesMessageTemplatesRenderRetrieve<
|
||||
>(
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
@@ -1228,6 +1249,7 @@ export function useMailboxesMessageTemplatesRenderRetrieve<
|
||||
>(
|
||||
mailboxId: string,
|
||||
id: string,
|
||||
params?: MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
@@ -1245,6 +1267,7 @@ export function useMailboxesMessageTemplatesRenderRetrieve<
|
||||
const queryOptions = getMailboxesMessageTemplatesRenderRetrieveQueryOptions(
|
||||
mailboxId,
|
||||
id,
|
||||
params,
|
||||
options,
|
||||
);
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ export * from "./mailboxes_message_templates_available_list_type";
|
||||
export * from "./mailboxes_message_templates_list_params";
|
||||
export * from "./mailboxes_message_templates_list_type_item";
|
||||
export * from "./mailboxes_message_templates_render_retrieve200";
|
||||
export * from "./mailboxes_message_templates_render_retrieve_params";
|
||||
export * from "./mailboxes_search_list_params";
|
||||
export * from "./maildomain_access_read";
|
||||
export * from "./maildomain_access_write";
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* messages API
|
||||
* This is the messages API schema.
|
||||
* OpenAPI spec version: 1.0.0 (v1.0)
|
||||
*/
|
||||
|
||||
export type MailboxesMessageTemplatesRenderRetrieveParams = {
|
||||
/**
|
||||
* Any other parameter will be available in the template context
|
||||
*/
|
||||
"*"?: string;
|
||||
};
|
||||
@@ -2,19 +2,20 @@ import { useBlockNoteEditor, useComponentsContext } from "@blocknote/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Icon, IconSize, Spinner } from "@gouvfr-lasuite/ui-kit";
|
||||
import { Modal, ModalSize } from "@openfun/cunningham-react";
|
||||
import { MessageTemplateTypeChoices, ReadOnlyMessageTemplate, useMailboxesMessageTemplatesAvailableList, mailboxesMessageTemplatesRenderRetrieve } from "@/features/api/gen";
|
||||
import { MessageTemplateTypeChoices, ReadOnlyMessageTemplate, useMailboxesMessageTemplatesAvailableList, mailboxesMessageTemplatesRenderRetrieve, MailboxesMessageTemplatesRenderRetrieveParams } from "@/features/api/gen";
|
||||
import { MessageComposerBlockSchema, MessageComposerInlineContentSchema, MessageComposerStyleSchema, PartialMessageComposerBlockSchema } from "@/features/forms/components/message-composer";
|
||||
import { useModal } from "@openfun/cunningham-react";
|
||||
|
||||
type MessageTemplateSelectorProps = {
|
||||
mailboxId: string;
|
||||
context?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A BlockNote toolbar selector which allows the user to select a message template
|
||||
* from all active templates for a given mailbox.
|
||||
*/
|
||||
export const MessageTemplateSelector = ({ mailboxId }: MessageTemplateSelectorProps) => {
|
||||
export const MessageTemplateSelector = ({ mailboxId, context = {} }: MessageTemplateSelectorProps) => {
|
||||
const { t } = useTranslation();
|
||||
const editor = useBlockNoteEditor<MessageComposerBlockSchema, MessageComposerInlineContentSchema, MessageComposerStyleSchema>();
|
||||
const Components = useComponentsContext()!;
|
||||
@@ -35,7 +36,8 @@ export const MessageTemplateSelector = ({ mailboxId }: MessageTemplateSelectorPr
|
||||
// Get rendered template content (allows to use placeholders)
|
||||
const { data: renderedTemplate } = await mailboxesMessageTemplatesRenderRetrieve(
|
||||
mailboxId,
|
||||
template.id
|
||||
template.id,
|
||||
context as MailboxesMessageTemplatesRenderRetrieveParams,
|
||||
);
|
||||
if (!renderedTemplate?.html_body) {
|
||||
console.error("Failed to render template");
|
||||
|
||||
@@ -154,6 +154,7 @@ export const BlockSignature = createReactBlockSpec(
|
||||
const { data: { data: preview = null } = {}, isLoading } = useMailboxesMessageTemplatesRenderRetrieve(
|
||||
props.mailboxId,
|
||||
props.templateId,
|
||||
{},
|
||||
{
|
||||
query: {
|
||||
enabled: !!props.mailboxId && !!props.templateId,
|
||||
|
||||
@@ -212,6 +212,11 @@ export const MessageComposer = ({ mailboxId, blockNoteOptions, defaultValue, quo
|
||||
<Toolbar>
|
||||
<MessageTemplateSelector
|
||||
mailboxId={mailboxId}
|
||||
context={{
|
||||
recipient_name: draft
|
||||
? draft.to.map(to => to.contact.name).join(", ")
|
||||
: quotedMessage?.sender?.name || ""
|
||||
}}
|
||||
/>
|
||||
<SignatureTemplateSelector
|
||||
templates={activeSignatures}
|
||||
|
||||
Reference in New Issue
Block a user