From 4ec3b92df6cb68072447b1931e89b7f4b92124f5 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Fri, 20 Jun 2025 10:36:46 -0300 Subject: [PATCH] feat: Export messages downloading as PDF (#36259) --- .changeset/tidy-colts-think.md | 6 + .../server/constant/permissions.ts | 1 + .../ExportMessages/ExportMessages.tsx | 59 ++++-- .../useExportMessagesAsPDFMutation.tsx | 84 ++++++++ .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v319.ts | 9 + apps/meteor/tests/e2e/export-messages.spec.ts | 2 + package.json | 13 ++ packages/i18n/src/locales/en.i18n.json | 1 + yarn.lock | 197 ++++++++---------- 10 files changed, 244 insertions(+), 129 deletions(-) create mode 100644 .changeset/tidy-colts-think.md create mode 100644 apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx create mode 100644 apps/meteor/server/startup/migrations/v319.ts diff --git a/.changeset/tidy-colts-think.md b/.changeset/tidy-colts-think.md new file mode 100644 index 00000000000..05131ea732a --- /dev/null +++ b/.changeset/tidy-colts-think.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/i18n": patch +--- + +Introduces PDF file as an export type for room messages diff --git a/apps/meteor/app/authorization/server/constant/permissions.ts b/apps/meteor/app/authorization/server/constant/permissions.ts index bc0bc45f1b1..7e01dcb92f9 100644 --- a/apps/meteor/app/authorization/server/constant/permissions.ts +++ b/apps/meteor/app/authorization/server/constant/permissions.ts @@ -250,4 +250,5 @@ export const permissions = [ { _id: 'view-moderation-console', roles: ['admin'] }, { _id: 'manage-moderation-actions', roles: ['admin'] }, { _id: 'bypass-time-limit-edit-and-delete', roles: ['bot', 'app'] }, + { _id: 'export-messages-as-pdf', roles: ['admin', 'user'] }, ]; diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx index 9457c97a2be..037bf3db5bb 100644 --- a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx @@ -15,11 +15,13 @@ import { Callout, } from '@rocket.chat/fuselage'; import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; +import { usePermission } from '@rocket.chat/ui-contexts'; import { useContext, useEffect, useId, useMemo } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useDownloadExportMutation } from './useDownloadExportMutation'; +import { useExportMessagesAsPDFMutation } from './useExportMessagesAsPDFMutation'; import { useRoomExportMutation } from './useRoomExportMutation'; import { validateEmail } from '../../../../../lib/emailValidator'; import { @@ -41,7 +43,7 @@ export type ExportMessagesFormValues = { type: 'email' | 'file' | 'download'; dateFrom: string; dateTo: string; - format: 'html' | 'json'; + format: 'html' | 'json' | 'pdf'; toUsers: string[]; additionalEmails: string; messagesCount: number; @@ -51,6 +53,7 @@ export type ExportMessagesFormValues = { const ExportMessages = () => { const { t } = useTranslation(); const { closeTab } = useRoomToolbox(); + const pfdExportPermission = usePermission('export-messages-as-pdf'); const formFocus = useAutoFocus(); const room = useRoom(); const isE2ERoom = room.encrypted; @@ -92,13 +95,21 @@ const ExportMessages = () => { [t], ); - const outputOptions = useMemo( - () => [ + const outputOptions = useMemo(() => { + const options: SelectOption[] = [ ['html', t('HTML')], ['json', t('JSON')], - ], - [t], - ); + ]; + + if (pfdExportPermission) { + options.push(['pdf', t('PDF')]); + } + + return options; + }, [t, pfdExportPermission]); + + // Remove HTML from download options + const downloadOutputOptions = outputOptions.slice(1); const roomExportMutation = useRoomExportMutation(); const downloadExportMutation = useDownloadExportMutation(); @@ -108,6 +119,12 @@ const ExportMessages = () => { const { type, toUsers } = watch(); + useEffect(() => { + if (type === 'email') { + setValue('format', 'html'); + } + }, [type, setValue]); + useEffect(() => { if (type !== 'file') { selectedMessageStore.setIsSelecting(true); @@ -119,24 +136,24 @@ const ExportMessages = () => { }, [type, selectedMessageStore]); useEffect(() => { - if (type === 'email') { - setValue('format', 'html'); - } - - if (type === 'download') { - setValue('format', 'json'); - } - setValue('messagesCount', messageCount, { shouldDirty: true }); - }, [type, setValue, messageCount]); + }, [messageCount, setValue]); + + const { mutate: exportAsPDF } = useExportMessagesAsPDFMutation(); const handleExport = async ({ type, toUsers, dateFrom, dateTo, format, subject, additionalEmails }: ExportMessagesFormValues) => { const messages = selectedMessageStore.getSelectedMessages(); if (type === 'download') { - return downloadExportMutation.mutateAsync({ - mids: messages, - }); + if (format === 'pdf') { + return exportAsPDF(messages); + } + + if (format === 'json') { + return downloadExportMutation.mutateAsync({ + mids: messages, + }); + } } if (type === 'file') { @@ -145,7 +162,7 @@ const ExportMessages = () => { type: 'file', ...(dateFrom && { dateFrom }), ...(dateTo && { dateTo }), - format, + format: format as 'html' | 'json', }); } @@ -207,9 +224,9 @@ const ExportMessages = () => {