diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 4210f1497..7a255186e 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -254,7 +254,8 @@ module Instructeurs private def assign_to_params - params.require(:assign_to).permit(:instant_email_message_notifications_enabled, :daily_email_notifications_enabled, :weekly_email_notifications_enabled) + params.require(:assign_to) + .permit(:instant_email_dossier_notifications_enabled, :instant_email_message_notifications_enabled, :daily_email_notifications_enabled, :weekly_email_notifications_enabled) end def assign_exports diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index c0dba8b9b..f94983007 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -153,6 +153,9 @@ module Users if passage_en_construction? && errors.blank? @dossier.en_construction! NotificationMailer.send_initiated_notification(@dossier).deliver_later + @dossier.procedure.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| + DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later + end return redirect_to(merci_dossier_path(@dossier)) elsif errors.present? flash.now.alert = errors diff --git a/app/javascript/shared/activestorage/errors.js b/app/javascript/shared/activestorage/errors.js new file mode 100644 index 000000000..072e99b5c --- /dev/null +++ b/app/javascript/shared/activestorage/errors.js @@ -0,0 +1,22 @@ +// Convert an error message returned by DirectUpload to a proper error object. +// +// This function has two goals: +// 1. Remove the file name from the DirectUpload error message +// (because the filename confuses Sentry error grouping) +// 2. Create each kind of error on a different line +// (so that Sentry knows they are different kind of errors, from +// the line they were created.) +export default function errorFromDirectUploadMessage(message) { + let matches = message.match(/ Status: [0-9]{1,3}/); + let status = (matches && matches[0]) || ''; + + if (message.includes('Error creating')) { + return new Error('Error creating file.' + status); + } else if (message.includes('Error storing')) { + return new Error('Error storing file.' + status); + } else if (message.includes('Error reading')) { + return new Error('Error reading file.' + status); + } else { + return new Error(message); + } +} diff --git a/app/javascript/shared/activestorage/ujs.js b/app/javascript/shared/activestorage/ujs.js index 3f8109953..aa3530ec2 100644 --- a/app/javascript/shared/activestorage/ujs.js +++ b/app/javascript/shared/activestorage/ujs.js @@ -1,4 +1,5 @@ import ProgressBar from './progress-bar'; +import errorFromDirectUploadMessage from './errors'; import { fire } from '@utils'; const INITIALIZE_EVENT = 'direct-upload:initialize'; @@ -40,17 +41,22 @@ addUploadEventListener(PROGRESS_EVENT, ({ detail: { id, progress } }) => { }); addUploadEventListener(ERROR_EVENT, event => { + let id = event.detail.id; + let errorMsg = event.detail.error; + // Display an error message alert( `Nous sommes désolés, une erreur s’est produite lors de l’envoi du fichier. - (${event.detail.error})` + (${errorMsg})` ); // Prevent ActiveStorage from displaying its own error message event.preventDefault(); - ProgressBar.error(event.detail.id, event.detail.error); - fire(document, 'sentry:capture-exception', new Error(event.detail.error)); + ProgressBar.error(id, errorMsg); + + let error = errorFromDirectUploadMessage(errorMsg); + fire(document, 'sentry:capture-exception', error); }); addUploadEventListener(END_EVENT, ({ detail: { id } }) => { diff --git a/app/javascript/shared/activestorage/uploader.js b/app/javascript/shared/activestorage/uploader.js index 1e29c2ac6..b303a441e 100644 --- a/app/javascript/shared/activestorage/uploader.js +++ b/app/javascript/shared/activestorage/uploader.js @@ -1,5 +1,6 @@ import { DirectUpload } from '@rails/activestorage'; import ProgressBar from './progress-bar'; +import errorFromDirectUploadMessage from './errors'; /** Uploader class is a delegate for DirectUpload instance @@ -18,7 +19,8 @@ export default class Uploader { this.directUpload.create((errorMsg, attributes) => { if (errorMsg) { this.progressBar.error(errorMsg); - reject(new Error(errorMsg)); + let error = errorFromDirectUploadMessage(errorMsg); + reject(error); } else { resolve(attributes.signed_id); } diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index e0f0e254a..a217f4d16 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -35,6 +35,12 @@ class DossierMailer < ApplicationMailer mail(from: NO_REPLY_EMAIL, to: instructeur_email, subject: @subject) end + def notify_new_dossier_depose_to_instructeur(dossier, instructeur_email) + @dossier = dossier + @subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle) + mail(from: NO_REPLY_EMAIL, to: instructeur_email, subject: @subject) + end + def notify_revert_to_instruction(dossier) @dossier = dossier @service = dossier.procedure.service diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb index 7ea2d737b..27fc5223e 100644 --- a/app/models/instructeur.rb +++ b/app/models/instructeur.rb @@ -23,6 +23,10 @@ class Instructeur < ApplicationRecord includes(:assign_to).where(assign_tos: { instant_email_message_notifications_enabled: true }) } + scope :with_instant_email_dossier_notifications, -> { + includes(:assign_to).where(assign_tos: { instant_email_dossier_notifications_enabled: true }) + } + default_scope { eager_load(:user) } def self.by_email(email) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 94ec8f307..581b3e616 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -26,6 +26,7 @@ class Procedure < ApplicationRecord has_many :administrateurs_procedures has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! } has_many :groupe_instructeurs, dependent: :destroy + has_many :instructeurs, through: :groupe_instructeurs has_many :dossiers, through: :groupe_instructeurs, dependent: :restrict_with_exception diff --git a/app/views/dossier_mailer/notify_new_dossier_depose_to_instructeur.html.haml b/app/views/dossier_mailer/notify_new_dossier_depose_to_instructeur.html.haml new file mode 100644 index 000000000..1d05aa596 --- /dev/null +++ b/app/views/dossier_mailer/notify_new_dossier_depose_to_instructeur.html.haml @@ -0,0 +1,10 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p + = t('.body', dossier_id: @dossier.id, libelle_demarche: @dossier.procedure.libelle) +%p= link_to("Consulter le dossier n°#{@dossier.id}", instructeur_dossier_url(procedure_id: @dossier.procedure.id, dossier_id: @dossier.id)) + += render partial: "layouts/mailers/signature" diff --git a/app/views/instructeurs/procedures/email_notifications.html.haml b/app/views/instructeurs/procedures/email_notifications.html.haml index 6cc0544d4..2f23b5fc1 100644 --- a/app/views/instructeurs/procedures/email_notifications.html.haml +++ b/app/views/instructeurs/procedures/email_notifications.html.haml @@ -11,6 +11,22 @@ .explication Configurez sur cette page les notifications que vous souhaitez recevoir par email pour cette démarche. + = form.label :email_notification, "Recevoir une notification à chaque dossier déposé" + + %p.notice + Cet email vous signale le dépôt d'un nouveau dossier. + %p.notice + Il est envoyé à chaque fois qu'un usager dépose un dossier. + + .radios + %label + = form.radio_button :instant_email_dossier_notifications_enabled, true + Oui + + %label + = form.radio_button :instant_email_dossier_notifications_enabled, false + Non + = form.label :email_notification, "Recevoir une notification à chaque message déposé" %p.notice diff --git a/config/locales/views/dossier_mailer/notify_new_dossier_depose_to_instructeur/fr.yml b/config/locales/views/dossier_mailer/notify_new_dossier_depose_to_instructeur/fr.yml new file mode 100644 index 000000000..736475042 --- /dev/null +++ b/config/locales/views/dossier_mailer/notify_new_dossier_depose_to_instructeur/fr.yml @@ -0,0 +1,5 @@ +fr: + dossier_mailer: + notify_new_dossier_depose_to_instructeur: + subject: Nouveau dossier déposé pour la démarche %{libelle_demarche} + body: Un nouveau dossier a été déposé (n° %{dossier_id}) pour la démarche %{libelle_demarche} diff --git a/db/migrate/20200409075320_add_instant_email_dossier_notifications_to_assign_tos.rb b/db/migrate/20200409075320_add_instant_email_dossier_notifications_to_assign_tos.rb new file mode 100644 index 000000000..4d4ed8d93 --- /dev/null +++ b/db/migrate/20200409075320_add_instant_email_dossier_notifications_to_assign_tos.rb @@ -0,0 +1,5 @@ +class AddInstantEmailDossierNotificationsToAssignTos < ActiveRecord::Migration[5.2] + def change + add_column :assign_tos, :instant_email_dossier_notifications_enabled, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 93bdc8f5a..4ea488b03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_04_07_135256) do +ActiveRecord::Schema.define(version: 2020_04_09_075320) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -104,6 +104,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_135256) do t.boolean "weekly_email_notifications_enabled", default: true, null: false t.boolean "daily_email_notifications_enabled", default: false, null: false t.boolean "instant_email_message_notifications_enabled", default: false, null: false + t.boolean "instant_email_dossier_notifications_enabled", default: false, null: false t.index ["groupe_instructeur_id", "instructeur_id"], name: "unique_couple_groupe_instructeur_instructeur", unique: true t.index ["groupe_instructeur_id"], name: "index_assign_tos_on_groupe_instructeur_id" t.index ["instructeur_id", "procedure_id"], name: "index_assign_tos_on_instructeur_id_and_procedure_id", unique: true diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 9f7c3f092..dfe54c93b 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -421,6 +421,24 @@ describe Users::DossiersController, type: :controller do expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction)) end + context 'with instructeurs ok to be notified instantly' do + let!(:instructeur_with_instant_email_dossier) { create(:instructeur) } + let!(:instructeur_without_instant_email_dossier) { create(:instructeur) } + + before do + allow(DossierMailer).to receive(:notify_new_dossier_depose_to_instructeur).and_return(double(deliver_later: nil)) + create(:assign_to, instructeur: instructeur_with_instant_email_dossier, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: true, groupe_instructeur: dossier.procedure.defaut_groupe_instructeur) + create(:assign_to, instructeur: instructeur_without_instant_email_dossier, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: false, groupe_instructeur: dossier.procedure.defaut_groupe_instructeur) + end + + it "sends notification mail to instructeurs" do + subject + + expect(DossierMailer).to have_received(:notify_new_dossier_depose_to_instructeur).once.with(dossier, instructeur_with_instant_email_dossier.email) + expect(DossierMailer).not_to have_received(:notify_new_dossier_depose_to_instructeur).with(dossier, instructeur_without_instant_email_dossier.email) + end + end + context "on an closed procedure" do before { dossier.procedure.close! } diff --git a/yarn.lock b/yarn.lock index b74c8a567..3dddf088a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1087,55 +1087,55 @@ webpack-sources "^1.4.3" "@sentry/browser@^5.11.2": - version "5.11.2" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.2.tgz#f0b19bd97e9f09a20e9f93a9835339ed9ab1f5a4" - integrity sha512-ls6ARX5m+23ld8OsuoPnR+kehjR5ketYWRcDYlmJDX2VOq5K4EzprujAo8waDB0o5a92yLXQ0ZSoK/zzAV2VoA== + version "5.15.4" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.15.4.tgz#5a7e7bad088556665ed8e69bceb0e18784e4f6c7" + integrity sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw== dependencies: - "@sentry/core" "5.11.2" - "@sentry/types" "5.11.0" - "@sentry/utils" "5.11.1" + "@sentry/core" "5.15.4" + "@sentry/types" "5.15.4" + "@sentry/utils" "5.15.4" tslib "^1.9.3" -"@sentry/core@5.11.2": - version "5.11.2" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.2.tgz#f2d9d37940d291dbcb9a9e4a012f76919474bdf6" - integrity sha512-IFCXGy7ebqIq/Kb8RVryCo/SjwhPcrfBmOjkicr4+DxN1UybLre2N3p9bejQMPIteOfDVHlySLYeipjTf+mxZw== +"@sentry/core@5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.4.tgz#08b617e093a636168be5aebad141d1f744217085" + integrity sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ== dependencies: - "@sentry/hub" "5.11.2" - "@sentry/minimal" "5.11.2" - "@sentry/types" "5.11.0" - "@sentry/utils" "5.11.1" + "@sentry/hub" "5.15.4" + "@sentry/minimal" "5.15.4" + "@sentry/types" "5.15.4" + "@sentry/utils" "5.15.4" tslib "^1.9.3" -"@sentry/hub@5.11.2": - version "5.11.2" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.2.tgz#a3b7ec27cd4cea2cddd75c372fbf1b4bc04c6aae" - integrity sha512-5BiDin6ZPsaiTm29rCC41MAjP1vOaKniqfjtXHVPm7FeOBA2bpHm95ncjLkshKGJTPfPZHXTpX/1IZsHrfGVEA== +"@sentry/hub@5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.4.tgz#cb64473725a60eec63b0be58ed1143eaaf894bee" + integrity sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg== dependencies: - "@sentry/types" "5.11.0" - "@sentry/utils" "5.11.1" + "@sentry/types" "5.15.4" + "@sentry/utils" "5.15.4" tslib "^1.9.3" -"@sentry/minimal@5.11.2": - version "5.11.2" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.2.tgz#ae417699342266ecd109a97e53cd9519c0893b21" - integrity sha512-oNuJuz3EZhVtamzABmPdr6lcYo06XHLWb2LvgnoNaYcMD1ExUSvhepOSyZ2h5STCMbmVgGVfXBNPV9RUTp8GZg== +"@sentry/minimal@5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.4.tgz#113f01fefb86b7830994c3dfa7ad4889ba7b2003" + integrity sha512-GL4GZ3drS9ge+wmxkHBAMEwulaE7DMvAEfKQPDAjg2p3MfcCMhAYfuY4jJByAC9rg9OwBGGehz7UmhWMFjE0tw== dependencies: - "@sentry/hub" "5.11.2" - "@sentry/types" "5.11.0" + "@sentry/hub" "5.15.4" + "@sentry/types" "5.15.4" tslib "^1.9.3" -"@sentry/types@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6" - integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg== +"@sentry/types@5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.15.4.tgz#37f30e35b06e8e12ad1101f1beec3e9b88ca1aab" + integrity sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg== -"@sentry/utils@5.11.1": - version "5.11.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607" - integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA== +"@sentry/utils@5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.15.4.tgz#02865ab3c9b745656cea0ab183767ec26c96f6e6" + integrity sha512-lO8SLBjrUDGADl0LOkd55R5oL510d/1SaI08/IBHZCxCUwI4TiYo5EPECq8mrj3XGfgCyq9osw33bymRlIDuSQ== dependencies: - "@sentry/types" "5.11.0" + "@sentry/types" "5.15.4" tslib "^1.9.3" "@turf/area@^6.0.1": @@ -9105,16 +9105,11 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -tslib@^1.9.0: +tslib@^1.9.0, tslib@^1.9.3: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslib@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== - tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"