From 1ff23d7c4088f68770391bd46948a04868a6c0e4 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 1 Oct 2024 13:44:57 +0300 Subject: [PATCH] add I18n Audit log --- config/locales/i18n.yml | 174 ++++++++++++++++++ lib/submission_events.rb | 16 -- lib/submissions/generate_audit_trail.rb | 110 ++++++----- .../generate_combined_attachment.rb | 4 +- 4 files changed, 237 insertions(+), 67 deletions(-) diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 5bbacbd0..fc09f656 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -577,6 +577,35 @@ en: &en opened: Opened sent: Sent awaiting: Awaiting + document_id: Document ID + envelope_id: Envelope ID + event_log: Event Log + verify: Verify + testing_log_not_for_production_use: Testing Log - Not for Production Use + original_sha256: Original SHA256 + result_sha256: Result SHA256 + generated_at: Generated at + email_verification: Email verification + phone_verification: Phone verification + session_id: Session ID + type_field: '%{type} Field' + paid_price: 'Paid %{price}' + verified: Verified + unverified: Unverified + submission_event_names: + send_email_to_html: 'Email sent to %{submitter_name}' + send_reminder_email_to_html: 'Reminder email sent to %{submitter_name}' + send_sms_to_html: 'SMS sent to %{submitter_name}' + send_2fa_sms_to_html: 'Verification SMS sent to %{submitter_name}' + open_email_by_html: 'Email opened by %{submitter_name}' + click_email_by_html: 'Email link clicked by %{submitter_name}' + click_sms_by_html: 'SMS link clicked by %{submitter_name}' + phone_verified_by_html: 'Phone verified by %{submitter_name}' + start_form_by_html: 'Submission started by %{submitter_name}' + view_form_by_html: 'Form viewed by %{submitter_name}' + invite_party_by_html: 'Invited %{invited_submitter_name} by %{submitter_name}' + complete_form_by_html: 'Submission completed by %{submitter_name}' + api_complete_form_by_html: 'Submission completed via API by %{submitter_name}' import_list: select_worksheet: Select Worksheet open: Open @@ -1158,6 +1187,35 @@ es: &es opened: Abierto sent: Enviado awaiting: Pendiente + document_id: ID del Documento + envelope_id: ID del Sobre + event_log: Registro de Eventos + verify: Verificar + testing_log_not_for_production_use: Registro de Pruebas - No para Uso en Producción + original_sha256: SHA256 Original + result_sha256: SHA256 del Resultado + generated_at: Generado el + email_verification: Verificación de correo electrónico + phone_verification: Verificación telefónica + session_id: ID de la Sesión + type_field: 'Campo %{type}' + paid_price: 'Pagado %{price}' + verified: Verificado + unverified: No verificado + submission_event_names: + send_email_to_html: 'Correo electrónico enviado a %{submitter_name}' + send_reminder_email_to_html: 'Correo de recordatorio enviado a %{submitter_name}' + send_sms_to_html: 'SMS enviado a %{submitter_name}' + send_2fa_sms_to_html: 'SMS de verificación enviado a %{submitter_name}' + open_email_by_html: 'Correo electrónico abierto por %{submitter_name}' + click_email_by_html: 'Enlace del correo electrónico clicado por %{submitter_name}' + click_sms_by_html: 'Enlace del SMS clicado por %{submitter_name}' + phone_verified_by_html: 'Teléfono verificado por %{submitter_name}' + start_form_by_html: 'Envío iniciado por %{submitter_name}' + view_form_by_html: 'Formulario visto por %{submitter_name}' + invite_party_by_html: 'Invitado %{invited_submitter_name} por %{submitter_name}' + complete_form_by_html: 'Envío completado por %{submitter_name}' + api_complete_form_by_html: 'Envío completado vía API por %{submitter_name}' import_list: select_worksheet: Seleccionar hoja de cálculo open: Abrir @@ -1739,6 +1797,35 @@ it: &it opened: Aperto sent: Inviato awaiting: In attesa + document_id: ID del Documento + envelope_id: ID della Busta + event_log: Registro degli Eventi + verify: Verifica + testing_log_not_for_production_use: Registro di Test - Non per Uso in Produzione + original_sha256: SHA256 Originale + result_sha256: SHA256 del Risultato + generated_at: Generato il + email_verification: Verifica e-mail + phone_verification: Verifica telefono + session_id: ID della Sessione + type_field: 'Campo %{type}' + paid_price: 'Pagato %{price}' + verified: Verificato + unverified: Non verificato + submission_event_names: + send_email_to_html: 'E-mail inviato a %{submitter_name}' + send_reminder_email_to_html: 'E-mail di promemoria inviato a %{submitter_name}' + send_sms_to_html: 'SMS inviato a %{submitter_name}' + send_2fa_sms_to_html: 'SMS di verifica inviato a %{submitter_name}' + open_email_by_html: 'E-mail aperta da %{submitter_name}' + click_email_by_html: "Link dell'e-mail cliccato da %{submitter_name}" + click_sms_by_html: "Link dell'SMS cliccato da %{submitter_name}" + phone_verified_by_html: 'Telefono verificato da %{submitter_name}' + start_form_by_html: 'Invio iniziato da %{submitter_name}' + view_form_by_html: 'Modulo visualizzato da %{submitter_name}' + invite_party_by_html: 'Invitato %{invited_submitter_name} da %{submitter_name}' + complete_form_by_html: 'Invio completato da %{submitter_name}' + api_complete_form_by_html: 'Invio completato tramite API da %{submitter_name}' import_list: select_worksheet: Seleziona il foglio di lavoro open: Apri @@ -2321,6 +2408,35 @@ fr: &fr opened: Ouvert sent: Envoyé awaiting: En attente + document_id: ID du document + envelope_id: ID de l'enveloppe + event_log: Journal d'événements + verify: Vérifier + testing_log_not_for_production_use: Journal de test - Ne pas utiliser en production + original_sha256: SHA256 original + result_sha256: SHA256 du résultat + generated_at: Généré le + email_verification: Vérification de l'e-mail + phone_verification: Vérification du téléphone + session_id: ID de session + type_field: 'Champ %{type}' + paid_price: 'Payé %{price}' + verified: Vérifié + unverified: Non vérifié + submission_event_names: + send_email_to_html: 'E-mail envoyé à %{submitter_name}' + send_reminder_email_to_html: 'E-mail de rappel envoyé à %{submitter_name}' + send_sms_to_html: 'SMS envoyé à %{submitter_name}' + send_2fa_sms_to_html: 'SMS de vérification envoyé à %{submitter_name}' + open_email_by_html: 'E-mail ouvert par %{submitter_name}' + click_email_by_html: "Lien de l'e-mail cliqué par %{submitter_name}" + click_sms_by_html: 'Lien du SMS cliqué par %{submitter_name}' + phone_verified_by_html: 'Téléphone vérifié par %{submitter_name}' + start_form_by_html: 'Soumission commencée par %{submitter_name}' + view_form_by_html: 'Formulaire consulté par %{submitter_name}' + invite_party_by_html: 'Invité %{invited_submitter_name} par %{submitter_name}' + complete_form_by_html: 'Soumission terminée par %{submitter_name}' + api_complete_form_by_html: "Soumission terminée via l'API par %{submitter_name}" import_list: select_worksheet: Sélectionner la feuille de calcul open: Ouvrir @@ -2902,6 +3018,35 @@ pt: &pt opened: Aberto sent: Enviado awaiting: Aguardando + document_id: ID do Documento + envelope_id: ID do Envelope + event_log: Log de Eventos + verify: Verificar + testing_log_not_for_production_use: Log de Teste - Não para Uso em Produção + original_sha256: SHA256 Original + result_sha256: SHA256 do Resultado + generated_at: Gerado em + email_verification: Verificação de E-mail + phone_verification: Verificação de Telefone + session_id: ID da Sessão + type_field: 'Campo %{type}' + paid_price: 'Pago %{price}' + verified: Verificado + unverified: Não verificado + submission_event_names: + send_email_to_html: 'E-mail enviado para %{submitter_name}' + send_reminder_email_to_html: 'E-mail de lembrete enviado para %{submitter_name}' + send_sms_to_html: 'SMS enviado para %{submitter_name}' + send_2fa_sms_to_html: 'SMS de verificação enviado para %{submitter_name}' + open_email_by_html: 'E-mail aberto por %{submitter_name}' + click_email_by_html: 'Link do e-mail clicado por %{submitter_name}' + click_sms_by_html: 'Link do SMS clicado por %{submitter_name}' + phone_verified_by_html: 'Telefone verificado por %{submitter_name}' + start_form_by_html: 'Submissão iniciada por %{submitter_name}' + view_form_by_html: 'Formulário visualizado por %{submitter_name}' + invite_party_by_html: 'Convidado %{invited_submitter_name} por %{submitter_name}' + complete_form_by_html: 'Submissão concluída por %{submitter_name}' + api_complete_form_by_html: 'Submissão concluída via API por %{submitter_name}' import_list: select_worksheet: Selecionar planilha open: Abrir @@ -3483,6 +3628,35 @@ de: &de opened: Geöffnet sent: Gesendet awaiting: Ausstehend + document_id: Dokumenten-ID + envelope_id: Umschlag-ID + event_log: Ereignisprotokoll + verify: Überprüfen + testing_log_not_for_production_use: Testprotokoll - Nicht für den Produktionseinsatz + original_sha256: Original SHA256 + result_sha256: Ergebnis SHA256 + generated_at: Generiert am + email_verification: E-Mail-Verifizierung + phone_verification: Telefonverifizierung + session_id: Sitzungs-ID + type_field: '%{type} Feld' + paid_price: 'Bezahlt %{price}' + verified: Verifiziert + unverified: Nicht verifiziert + submission_event_names: + send_email_to_html: 'E-Mail gesendet an %{submitter_name}' + send_reminder_email_to_html: 'Erinnerungs-E-Mail gesendet an %{submitter_name}' + send_sms_to_html: 'SMS gesendet an %{submitter_name}' + send_2fa_sms_to_html: 'Verifizierungs-SMS gesendet an %{submitter_name}' + open_email_by_html: 'E-Mail geöffnet von %{submitter_name}' + click_email_by_html: 'E-Mail-Link angeklickt von %{submitter_name}' + click_sms_by_html: 'SMS-Link angeklickt von %{submitter_name}' + phone_verified_by_html: 'Telefon verifiziert von %{submitter_name}' + start_form_by_html: 'Einreichung gestartet von %{submitter_name}' + view_form_by_html: 'Formular angesehen von %{submitter_name}' + invite_party_by_html: 'Eingeladen %{invited_submitter_name} von %{submitter_name}' + complete_form_by_html: 'Einreichung abgeschlossen von %{submitter_name}' + api_complete_form_by_html: 'Einreichung über API abgeschlossen von %{submitter_name}' import_list: select_worksheet: Arbeitsblatt auswählen open: Öffnen diff --git a/lib/submission_events.rb b/lib/submission_events.rb index e4f93e9e..1705ba41 100644 --- a/lib/submission_events.rb +++ b/lib/submission_events.rb @@ -3,22 +3,6 @@ module SubmissionEvents TRACKING_PARAM_LENGTH = 6 - EVENT_NAMES = { - send_email: 'Email sent', - send_reminder_email: 'Reminder email sent', - send_sms: 'SMS sent', - send_2fa_sms: 'Verification SMS sent', - open_email: 'Email opened', - click_email: 'Email link clicked', - click_sms: 'SMS link clicked', - phone_verified: 'Phone verified', - start_form: 'Submission started', - view_form: 'Form viewed', - invite_party: 'Invited', - complete_form: 'Submission completed', - api_complete_form: 'Submission completed via API' - }.freeze - module_function def build_tracking_param(submitter, event_type = 'click_email') diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index 5eb39b8f..dbb46c02 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -12,9 +12,6 @@ module Submissions 'Helvetica' end - VERIFIED_TEXT = 'Verified' - UNVERIFIED_TEXT = 'Unverified' - CURRENCY_SYMBOLS = { 'USD' => '$', 'EUR' => '€', @@ -32,35 +29,37 @@ module Submissions # rubocop:disable Metrics def call(submission) - document = build_audit_trail(submission) - account = submission.account - pkcs = Accounts.load_signing_pkcs(account) - tsa_url = Accounts.load_timeserver_url(account) + I18n.with_locale(account.locale) do + document = build_audit_trail(submission) - io = StringIO.new + pkcs = Accounts.load_signing_pkcs(account) + tsa_url = Accounts.load_timeserver_url(account) - document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})" + io = StringIO.new - last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at) + document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})" - sign_params = { - reason: sign_reason, - **Submissions::GenerateResultAttachments.build_signing_params(last_submitter, pkcs, tsa_url) - } + last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at) - document.sign(io, **sign_params) + sign_params = { + reason: sign_reason, + **Submissions::GenerateResultAttachments.build_signing_params(last_submitter, pkcs, tsa_url) + } - Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params) + document.sign(io, **sign_params) - ActiveStorage::Attachment.create!( - blob: ActiveStorage::Blob.create_and_upload!( - io: io.tap(&:rewind), filename: "Audit Log - #{submission.template.name}.pdf" - ), - name: 'audit_trail', - record: submission - ) + Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params) + + ActiveStorage::Attachment.create!( + blob: ActiveStorage::Blob.create_and_upload!( + io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - #{submission.template.name}.pdf" + ), + name: 'audit_trail', + record: submission + ) + end end def build_audit_trail(submission) @@ -130,7 +129,7 @@ module Submissions TESTING_FOOTER end else - "Document ID: #{document_id}" + "#{I18n.t('document_id')}: #{document_id}" end text = HexaPDF::Layout::TextFragment.create( @@ -158,17 +157,19 @@ module Submissions composer.column(columns: 1) do |column| add_logo(column, submission) - column.text(account.testing? ? 'Testing Log - Not for Production Use' : 'Audit Log', + column.text(account.testing? ? I18n.t('testing_log_not_for_production_use') : I18n.t('audit_log'), font_size: 16, padding: [10, 0, 0, 0], position: :float, text_align: :right) end composer.column(columns: 1) do |column| - column.text("Envelope ID: #{submission.id}", font_size: 12, padding: [15, 0, 8, 0], position: :float) + column.text("#{I18n.t('envelope_id')}: #{submission.id}", font_size: 12, + padding: [15, 0, 8, 0], + position: :float) unless submission.source.in?(%w[embed api]) - column.formatted_text([{ link: verify_url, text: 'Verify', style: :link }], + column.formatted_text([{ link: verify_url, text: I18n.t('verify'), style: :link }], font_size: 9, padding: [15, 0, 10, 0], position: :float, text_align: :right) end end @@ -197,13 +198,13 @@ module Submissions ), composer.document.layout.formatted_text_box( [ - { text: "Original SHA256:\n", font: [FONT_NAME, { variant: :bold }] }, + { text: "#{I18n.t('original_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] }, original_documents.map { |d| d.metadata['sha256'] || d.checksum }.join("\n"), "\n", - { text: "Result SHA256:\n", font: [FONT_NAME, { variant: :bold }] }, + { text: "#{I18n.t('result_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] }, document.metadata['sha256'] || document.checksum, "\n", - { text: 'Generated at: ', font: [FONT_NAME, { variant: :bold }] }, + { text: "#{I18n.t('generated_at')}: ", font: [FONT_NAME, { variant: :bold }] }, "#{I18n.l(document.created_at.in_time_zone(account.timezone), format: :long, locale: account.locale)} " \ "#{TimeUtils.timezone_abbr(account.timezone, document.created_at)}" ], line_spacing: 1.3 @@ -250,13 +251,13 @@ module Submissions composer.document.layout.formatted_text_box( [ submitter.email && click_email_event && { - text: "Email verification: #{VERIFIED_TEXT}\n" + text: "#{I18n.t('email_verification')}: #{I18n.t('verified')}\n" }, submitter.phone && is_phone_verified && { - text: "Phone verification: #{VERIFIED_TEXT}\n" + text: "#{I18n.t('phone_verification')}: #{I18n.t('verified')}\n" }, completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" }, - completed_event.data['sid'] && { text: "Session ID: #{completed_event.data['sid']}\n" }, + completed_event.data['sid'] && { text: "#{I18n.t('session_id')}: #{completed_event.data['sid']}\n" }, completed_event.data['ua'] && { text: "User agent: #{completed_event.data['ua']}\n" }, "\n" ].compact_blank, line_spacing: 1.3, padding: [10, 20, 20, 0] @@ -284,7 +285,8 @@ module Submissions [ { text: TextUtils.maybe_rtl_reverse(field_name).upcase.presence || - "#{field['type']} Field #{submitter_field_counters[field['type']]}\n".upcase, + "#{I18n.t('type_field', + type: field['type'])} #{submitter_field_counters[field['type']]}\n".upcase, font_size: 6 } ].compact_blank, @@ -316,7 +318,8 @@ module Submissions price = ApplicationController.helpers.number_to_currency(field['preferences']['price'], unit:) - composer.formatted_text_box([{ text: "Paid #{price}\n" }], padding: [0, 0, 10, 0]) + composer.formatted_text_box([{ text: "#{I18n.t('paid_price', price:)}\n" }], + padding: [0, 0, 10, 0]) end composer.formatted_text_box( @@ -354,31 +357,38 @@ module Submissions composer.draw_box(divider) end - composer.text('Event Log', font_size: 12, padding: [10, 0, 20, 0]) + composer.text(I18n.t('event_log'), font_size: 12, padding: [10, 0, 20, 0]) events_data = submission.submission_events.sort_by(&:event_timestamp).map do |event| submitter = submission.submitters.find { |e| e.id == event.submitter_id } + submitter_name = + if event.event_type.include?('sms') || event.event_type.include?('phone') + event.data['phone'] || submitter.phone + else + submitter.name || submitter.email || submitter.phone + end - text = SubmissionEvents::EVENT_NAMES[event.event_type.to_sym] - - if event.event_type == 'invite_party' && - (invited_submitter = submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && - (name = submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) - text += ['', invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' ') - end + text = + if event.event_type == 'invite_party' && + (invited_submitter = submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && + (name = submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) + invited_submitter_name = [invited_submitter.name || invited_submitter.email || invited_submitter.phone, + name].join(' ') + I18n.t('submission_event_names.invite_party_by_html', invited_submitter_name:, + submitter_name:) + elsif event.event_type.include?('send_') + I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:) + else + I18n.t("submission_event_names.#{event.event_type}_by_html", submitter_name:) + end [ "#{I18n.l(event.event_timestamp.in_time_zone(account.timezone), format: :long, locale: account.locale)} " \ "#{TimeUtils.timezone_abbr(account.timezone, event.event_timestamp)}", composer.document.layout.formatted_text_box( [ - { text:, font: [FONT_NAME, { variant: :bold }] }, - event.event_type.include?('send_') ? ' to ' : ' by ', - if event.event_type.include?('sms') || event.event_type.include?('phone') - event.data['phone'] || submitter.phone - else - submitter.name || submitter.email || submitter.phone - end + { text: text[%r{(.*?)}, 1], font: [FONT_NAME, { variant: :bold }] }, + text[%r{(.*?)(.*)}, 2] ] ) ] diff --git a/lib/submissions/generate_combined_attachment.rb b/lib/submissions/generate_combined_attachment.rb index cf1838af..1c7bc1f3 100644 --- a/lib/submissions/generate_combined_attachment.rb +++ b/lib/submissions/generate_combined_attachment.rb @@ -38,7 +38,9 @@ module Submissions def build_combined_pdf(submitter) pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter) - audit_trail = Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission) + audit_trail = I18n.with_locale(submitter.account.locale) do + Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission) + end audit_trail.dispatch_message(:complete_objects)