From 7f3d4a26add98e65eff08e0f9105864d526ef725 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Fri, 2 Apr 2021 17:42:24 +0100 Subject: [PATCH 01/16] Make exports#key not-null --- app/models/export.rb | 2 +- db/migrate/20210402163003_exports_key_not_null.rb | 5 +++++ db/schema.rb | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20210402163003_exports_key_not_null.rb diff --git a/app/models/export.rb b/app/models/export.rb index 208b68df3..ef521e01e 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -4,7 +4,7 @@ # # id :bigint not null, primary key # format :string not null -# key :text +# key :text not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/db/migrate/20210402163003_exports_key_not_null.rb b/db/migrate/20210402163003_exports_key_not_null.rb new file mode 100644 index 000000000..3de3d70fc --- /dev/null +++ b/db/migrate/20210402163003_exports_key_not_null.rb @@ -0,0 +1,5 @@ +class ExportsKeyNotNull < ActiveRecord::Migration[6.1] + def change + change_column_null :exports, :key, false + end +end diff --git a/db/schema.rb b/db/schema.rb index 6f2234ccb..c46fd282f 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: 2021_03_31_123709) do +ActiveRecord::Schema.define(version: 2021_04_02_163003) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -354,7 +354,7 @@ ActiveRecord::Schema.define(version: 2021_03_31_123709) do t.string "format", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "key" + t.text "key", null: false t.index ["format", "key"], name: "index_exports_on_format_and_key", unique: true end From fc4d8362dcfb493c5a3791305c8ea60a406b0f77 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 1 Apr 2021 16:40:22 +0200 Subject: [PATCH 02/16] models: fix typo in comments --- app/controllers/users/dossiers_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index f77eff7ab..40fbe0f03 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -360,8 +360,8 @@ module Users if champs_params[:dossier] @dossier.assign_attributes(champs_params[:dossier]) - # FIXME in some cases a removed repetition bloc row is submitted. - # In this case it will be trated as a new records and action will fail. + # FIXME: in some cases a removed repetition bloc row is submitted. + # In this case it will be treated as a new record, and the action will fail. @dossier.champs.filter(&:repetition?).each do |champ| champ.champs = champ.champs.filter(&:persisted?) end From 6eadcf614decd6254869b00f5429d6e0d08201f5 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 1 Apr 2021 16:48:12 +0200 Subject: [PATCH 03/16] spec: improve the dossier_controller timestamp specs Make the specs clearer, and better test the various timestamps. --- .../users/dossiers_controller_spec.rb | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 1bd747264..d69c83670 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -619,13 +619,14 @@ describe Users::DossiersController, type: :controller do it 'updates the champs' do subject expect(first_champ.reload.value).to eq('beautiful value') - expect(first_champ.dossier.reload.last_champ_updated_at).to eq(now) expect(piece_justificative_champ.reload.piece_justificative_file).to be_attached end - it 'updates the dossier modification date' do + it 'updates the dossier timestamps' do subject - expect(dossier.reload.updated_at.year).to eq(2100) + dossier.reload + expect(dossier.updated_at).to eq(now) + expect(dossier.last_champ_updated_at).to eq(now) end it 'updates the dossier state' do @@ -635,22 +636,34 @@ describe Users::DossiersController, type: :controller do it { is_expected.to redirect_to(demande_dossier_path(dossier)) } - context 'when only files champs are modified' do + context 'when only a single file champ are modified' do + # A bug in ActiveRecord causes records changed through grand-parent <-> parent <-> child + # relationships do not touch the grand-parent record on change. + # This situation is hit when updating just the attachment of a champ (and not the + # champ itself). + # + # This test ensures that, whatever workaround we wrote for this, it still works properly. + # + # See https://github.com/rails/rails/issues/26726 let(:submit_payload) do { id: dossier.id, dossier: { - champs_attributes: { - id: piece_justificative_champ.id, - piece_justificative_file: file - } + champs_attributes: [ + { + id: piece_justificative_champ.id, + piece_justificative_file: file + } + ] } } end - it 'updates the dossier modification date' do + it 'updates the dossier timestamps' do subject - expect(dossier.reload.updated_at.year).to eq(2100) + dossier.reload + expect(dossier.updated_at).to eq(now) + expect(dossier.last_champ_updated_at).to eq(now) end end end @@ -667,6 +680,12 @@ describe Users::DossiersController, type: :controller do it { expect(response).to render_template(:modifier) } it { expect(flash.alert).to eq(['nop']) } + it 'does not update the dossier timestamps' do + dossier.reload + expect(dossier.updated_at).not_to eq(now) + expect(dossier.last_champ_updated_at).not_to eq(now) + end + it 'does not send an email' do expect(NotificationMailer).not_to receive(:send_initiated_notification) From 3499f5af9ab7c70d0ca5860f7034f0bc1717ca02 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 1 Apr 2021 15:22:47 +0000 Subject: [PATCH 04/16] =?UTF-8?q?models:=20remove=20invalid=20Dossier=20?= =?UTF-8?q?=E2=86=94=EF=B8=8E=20Champ=20inverse=20relationship?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Dossier.champs` is not really an inverse of `Champs.dossier`: when a Champ record is created, it should not always be added to dossier.champs (for instance if the champ is private). NB: this breaks the workaround we added in #3907 to fix the parent dossier not being touched in some cases (the workaround was to add an inverse relationship, but we now have to remove it). The new workaround is to watch for `changed_for_autosave?` on champs. Unlike `changed?`, `changed_for_autosave?` also detects changes to attachments. This allows us to touch both `last_champ_updated_at` and `updated_at` in a single pass. --- app/controllers/users/dossiers_controller.rb | 2 +- app/models/champ.rb | 2 +- app/models/dossier.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 40fbe0f03..f03d28f19 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -365,7 +365,7 @@ module Users @dossier.champs.filter(&:repetition?).each do |champ| champ.champs = champ.champs.filter(&:persisted?) end - if @dossier.champs.any?(&:changed?) + if @dossier.champs.any?(&:changed_for_autosave?) @dossier.last_champ_updated_at = Time.zone.now end if !@dossier.save diff --git a/app/models/champ.rb b/app/models/champ.rb index 864ced9fb..8cb1d8804 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -18,7 +18,7 @@ # type_de_champ_id :integer # class Champ < ApplicationRecord - belongs_to :dossier, -> { with_discarded }, inverse_of: :champs, touch: true, optional: false + belongs_to :dossier, -> { with_discarded }, inverse_of: false, touch: true, optional: false belongs_to :type_de_champ, inverse_of: :champ, optional: false belongs_to :parent, class_name: 'Champ', optional: true has_many :commentaires diff --git a/app/models/dossier.rb b/app/models/dossier.rb index aaf93e14f..2441035f0 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -65,8 +65,8 @@ class Dossier < ApplicationRecord has_one_attached :justificatif_motivation has_one_attached :pdf_export_for_instructeur - has_many :champs, -> { root.public_ordered }, inverse_of: :dossier, dependent: :destroy - has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: :dossier, dependent: :destroy + has_many :champs, -> { root.public_ordered }, inverse_of: false, dependent: :destroy + has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: false, dependent: :destroy has_many :commentaires, inverse_of: :dossier, dependent: :destroy has_many :invites, dependent: :destroy has_many :follows, -> { active }, inverse_of: :dossier From f4acc83269334442ce46a29ce641e30833b7862c Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 30 Mar 2021 17:54:25 +0200 Subject: [PATCH 05/16] change procedures controller --- .../new_administrateur/procedures_controller.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/new_administrateur/procedures_controller.rb b/app/controllers/new_administrateur/procedures_controller.rb index 8b2c0431d..212878085 100644 --- a/app/controllers/new_administrateur/procedures_controller.rb +++ b/app/controllers/new_administrateur/procedures_controller.rb @@ -186,7 +186,8 @@ module NewAdministrateur end def invited_expert_list - @experts_procedure = @procedure.experts_procedures.sort_by { |expert_procedure| expert_procedure.expert.email } + @experts_procedure = @procedure.experts_procedures.where(revoked_at: nil).sort_by { |expert_procedure| expert_procedure.expert.email } + @experts_emails = experts_procedure_emails end def update_allow_decision_access @@ -198,6 +199,10 @@ module NewAdministrateur private + def experts_procedure_emails + @procedure.experts.map(&:email).sort + end + def apercu_tab params[:tab] || 'dossier' end From 5b7b2d4966fcf9856f72b851b81c694f0cb59b29 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 30 Mar 2021 17:54:46 +0200 Subject: [PATCH 06/16] change routes --- config/routes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index ab3874c57..81156cf26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -408,7 +408,8 @@ Rails.application.routes.draw do post 'transfer' => 'procedures#transfer', as: :transfer get 'invited_expert_list' put 'update_allow_decision_access' => 'procedures#update_allow_decision_access', as: :update_allow_decision_access - + post 'add_expert_to_procedure' => 'experts_procedures#add_expert_to_procedure', as: :add_expert_to_procedure + put 'revoke_expert_from_procedure' => 'experts_procedures#revoke_expert_from_procedure', as: :revoke_expert_from_procedure resources :mail_templates, only: [:edit, :update] resources :groupe_instructeurs, only: [:index, :show, :create, :update, :destroy] do From 62b7d752f91cb8a876b5e112142b7b2a9ff45cc6 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 30 Mar 2021 17:55:07 +0200 Subject: [PATCH 07/16] layout --- .../procedures/invited_expert_list.html.haml | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/app/views/new_administrateur/procedures/invited_expert_list.html.haml b/app/views/new_administrateur/procedures/invited_expert_list.html.haml index 2d894e5b3..3272948d4 100644 --- a/app/views/new_administrateur/procedures/invited_expert_list.html.haml +++ b/app/views/new_administrateur/procedures/invited_expert_list.html.haml @@ -5,12 +5,35 @@ .container %h1.page-title.mt-2 Experts invités sur #{@procedure.libelle} + + .container.groupe-instructeur + + .card + .card-title Affecter des experts à la démarche + = form_for :experts_procedure, + url: admin_procedure_add_expert_to_procedure_path(@procedure), + html: { class: 'form' } do |f| + + .instructeur-wrapper + %p.notice Pendant l'instruction d'un dossier, les instructeurs peuvent demander leur avis à un ou plusieurs experts. + %p.notice Entrez les adresses email des experts que vous souhaitez affecter à cette démarche + - hidden_field_id = SecureRandom.uuid + = hidden_field_tag :emails, nil, data: { uuid: hidden_field_id } + = react_component("ComboMultipleDropdownList", + options: [], + selected: [], disabled: [], + hiddenFieldId: hidden_field_id, + label: 'email expert', + acceptNewValues: true) + + = f.submit 'Affecter à la démarche', class: 'button primary send' - if @experts_procedure.present? %table.table.mt-2 %thead %tr %th Liste des experts - - if @procedure.feature_enabled?(:make_experts_notifiable) + %th Nombre d'avis + - if @procedure.feature_enabled?(:admin_affect_experts_to_avis) %th Notifier des décisions sur les dossiers %tbody - @experts_procedure.each do |expert_procedure| @@ -18,8 +41,10 @@ %td %span.icon.person = expert_procedure.expert.email - - if @procedure.feature_enabled?(:make_experts_notifiable) - %td + %td.text-center + = expert_procedure.avis.count + - if @procedure.feature_enabled?(:admin_affect_experts_to_avis) + %td.text-center = form_for expert_procedure, url: admin_procedure_update_allow_decision_access_path(expert_procedure: expert_procedure), remote: true, @@ -31,6 +56,12 @@ %span.toggle-switch-control.round %span.toggle-switch-label.on %span.toggle-switch-label.off + %td.actions= button_to 'retirer', + { action: "revoke_expert_from_procedure", :controller=>"new_administrateur/experts_procedures" }, + { method: :put, + data: { confirm: "Êtes-vous sûr de vouloir révoquer l'expert « #{expert_procedure.expert.email} » de la démarche #{expert_procedure.procedure.libelle} ? Les instructeurs ne pourront plus lui demander d'avis" }, + params: { expert_procedure: { id: expert_procedure.id }}, + class: 'button' } - else .blank-tab %h2.empty-text Aucun expert invité pour le moment. From 39d0dd2af1f5e9469ec29e5755179f5f9860327b Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 30 Mar 2021 17:55:25 +0200 Subject: [PATCH 08/16] yml traduction --- .../views/new_administrateur/groupe_instructeurs/fr.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml b/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml index 6ac7df602..2243a9caf 100644 --- a/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml +++ b/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml @@ -1,5 +1,12 @@ fr: new_administrateur: + experts_procedures: + wrong_address: + one: "%{value} n’est pas une adresse email valide" + other: "%{value} ne sont pas des adresses emails valides" + experts_assignment: + one: "L'expert %{value} a été affecté à la démarche n° %{procedure}" + other: "Les experts %{value} ont été affectés à la démarche n° %{procedure}" groupe_instructeurs: index: existing_groupe: From 8a1cdf31e3843688bc299c5ce12fa7c64d22b139 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 30 Mar 2021 17:55:35 +0200 Subject: [PATCH 09/16] tests --- .../experts/avis_controller_spec.rb | 10 ++-- .../instructeurs/dossiers_controller_spec.rb | 6 +-- .../experts_procedures_controller_spec.rb | 49 +++++++++++++++++++ spec/features/instructeurs/expert_spec.rb | 4 +- 4 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 spec/controllers/new_administrateur/experts_procedures_controller_spec.rb diff --git a/spec/controllers/experts/avis_controller_spec.rb b/spec/controllers/experts/avis_controller_spec.rb index 3ce7d7e2d..af0b520dd 100644 --- a/spec/controllers/experts/avis_controller_spec.rb +++ b/spec/controllers/experts/avis_controller_spec.rb @@ -159,7 +159,7 @@ describe Experts::AvisController, type: :controller do let(:previous_avis_confidentiel) { false } let(:asked_confidentiel) { false } let(:intro) { 'introduction' } - let(:emails) { ["toto@totomail.com"] } + let(:emails) { "[\"toto@totomail.com\"]" } let(:invite_linked_dossiers) { nil } before do @@ -176,7 +176,7 @@ describe Experts::AvisController, type: :controller do describe '#create_avis' do let!(:previous_avis) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, confidentiel: previous_avis_confidentiel) } - let(:emails) { ['a@b.com'] } + let(:emails) { "[\"a@b.com\"]" } let(:intro) { 'introduction' } let(:created_avis) { Avis.last } let!(:old_avis_count) { Avis.count } @@ -194,7 +194,7 @@ describe Experts::AvisController, type: :controller do context 'when an invalid email' do let(:previous_avis_confidentiel) { false } let(:asked_confidentiel) { false } - let(:emails) { ["toto.fr"] } + let(:emails) { "[\"toto.fr\"]" } it { expect(response).to render_template :instruction } it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } @@ -205,7 +205,7 @@ describe Experts::AvisController, type: :controller do context 'ask review with attachment' do let(:previous_avis_confidentiel) { false } let(:asked_confidentiel) { false } - let(:emails) { ["toto@totomail.com"] } + let(:emails) { "[\"toto@totomail.com\"]" } it { expect(created_avis.introduction_file).to be_attached } it { expect(created_avis.introduction_file.filename).to eq("piece_justificative_0.pdf") } @@ -216,7 +216,7 @@ describe Experts::AvisController, type: :controller do context 'with multiple emails' do let(:asked_confidentiel) { false } let(:previous_avis_confidentiel) { false } - let(:emails) { ["toto.fr,titi@titimail.com"] } + let(:emails) { "[\"toto.fr\",\"titi@titimail.com\"]" } it { expect(response).to render_template :instruction } it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 82b08ad2d..b31cb81c0 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -448,7 +448,7 @@ describe Instructeurs::DossiersController, type: :controller do } end - let(:emails) { ['email@a.com'] } + let(:emails) { "[\"email@a.com\"]" } context "notifications updates" do context 'when an instructeur follows the dossier' do @@ -476,7 +476,7 @@ describe Instructeurs::DossiersController, type: :controller do it { expect(response).to redirect_to(avis_instructeur_dossier_path(dossier.procedure, dossier)) } context "with an invalid email" do - let(:emails) { ['emaila.com'] } + let(:emails) { "[\"emaila.com\"]" } before { subject } @@ -487,7 +487,7 @@ describe Instructeurs::DossiersController, type: :controller do end context 'with multiple emails' do - let(:emails) { ["toto.fr,titi@titimail.com"] } + let(:emails) { "[\"toto.fr\",\"titi@titimail.com\"]" } before { subject } diff --git a/spec/controllers/new_administrateur/experts_procedures_controller_spec.rb b/spec/controllers/new_administrateur/experts_procedures_controller_spec.rb new file mode 100644 index 000000000..39e28e3f9 --- /dev/null +++ b/spec/controllers/new_administrateur/experts_procedures_controller_spec.rb @@ -0,0 +1,49 @@ +describe NewAdministrateur::ExpertsProceduresController, type: :controller do + let(:admin) { create(:administrateur) } + + before do + sign_in(admin.user) + end + describe '#add_expert_to_procedure' do + let(:procedure) { create :procedure, administrateur: admin } + let(:expert) { create(:expert) } + let(:expert2) { create(:expert) } + + subject do + post :add_expert_to_procedure, + params: { procedure_id: procedure.id, emails: "[\"#{expert.email}\",\"#{expert2.email}\"]" } + end + + before do + subject + end + + context 'of multiple experts' do + it { expect(procedure.experts.include?(expert)).to be_truthy } + it { expect(procedure.experts.include?(expert2)).to be_truthy } + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(admin_procedure_invited_expert_list_path(procedure)) } + end + end + + describe '#revoke_expert_from_procedure' do + let(:procedure) { create :procedure, administrateur: admin } + let(:expert) { create(:expert) } + let(:expert_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + + subject do + put :revoke_expert_from_procedure, params: { procedure_id: procedure.id, expert_procedure: { id: expert_procedure.id } } + end + + before do + subject + expert_procedure.reload + end + + context 'of multiple experts' do + it { expect(expert_procedure.revoked_at).to be_present } + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(admin_procedure_invited_expert_list_path(procedure)) } + end + end +end diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index ea2353d45..1529421ae 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -1,4 +1,4 @@ -feature 'Inviting an expert:' do +feature 'Inviting an expert:', js: true do include ActiveJob::TestHelper include ActionView::Helpers @@ -21,7 +21,7 @@ feature 'Inviting an expert:' do click_on 'Avis externes' expect(page).to have_current_path(avis_instructeur_dossier_path(procedure, dossier)) - fill_in 'avis_emails', with: "#{expert.email}, #{expert2.email}" + page.execute_script("document.querySelector('#avis_emails').value = '[\"#{expert.email}\",\"#{expert2.email}\"]'") fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.' check 'avis_invite_linked_dossiers' page.select 'confidentiel', from: 'avis_confidentiel' From d6bed42a3e90ae093f1e711c778fc4b36c1b1911 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Wed, 31 Mar 2021 11:16:07 +0200 Subject: [PATCH 10/16] add css helper --- app/assets/stylesheets/helpers.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/helpers.scss b/app/assets/stylesheets/helpers.scss index de1fff1ed..92755dac9 100644 --- a/app/assets/stylesheets/helpers.scss +++ b/app/assets/stylesheets/helpers.scss @@ -28,3 +28,7 @@ display: inline-block; width: 5px; } + +.text-center { + text-align: center; +} From 60c06b6d8ca21380be7cd8cd855d98a6a965003d Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 1 Apr 2021 11:56:33 +0200 Subject: [PATCH 11/16] add revoked at to experts_procedures --- app/models/experts_procedure.rb | 1 + .../20210331184808_add_revoked_at_to_experts_procedures.rb | 5 +++++ db/schema.rb | 1 + 3 files changed, 7 insertions(+) create mode 100644 db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb diff --git a/app/models/experts_procedure.rb b/app/models/experts_procedure.rb index 203f927fc..438086376 100644 --- a/app/models/experts_procedure.rb +++ b/app/models/experts_procedure.rb @@ -4,6 +4,7 @@ # # id :bigint not null, primary key # allow_decision_access :boolean default(FALSE), not null +# revoked_at :datetime # created_at :datetime not null # updated_at :datetime not null # expert_id :bigint not null diff --git a/db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb b/db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb new file mode 100644 index 000000000..916ac6b67 --- /dev/null +++ b/db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb @@ -0,0 +1,5 @@ +class AddRevokedAtToExpertsProcedures < ActiveRecord::Migration[6.1] + def change + add_column :experts_procedures, :revoked_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index c46fd282f..ac440622d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -345,6 +345,7 @@ ActiveRecord::Schema.define(version: 2021_04_02_163003) do t.boolean "allow_decision_access", default: false, null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.datetime "revoked_at" t.index ["expert_id", "procedure_id"], name: "index_experts_procedures_on_expert_id_and_procedure_id", unique: true t.index ["expert_id"], name: "index_experts_procedures_on_expert_id" t.index ["procedure_id"], name: "index_experts_procedures_on_procedure_id" From 436f56706775b91291aebc5181446bfd2cb028d7 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 1 Apr 2021 11:58:02 +0200 Subject: [PATCH 12/16] change create avis concern --- app/controllers/concerns/create_avis_concern.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb index a76c03f01..9854ec405 100644 --- a/app/controllers/concerns/create_avis_concern.rb +++ b/app/controllers/concerns/create_avis_concern.rb @@ -9,7 +9,8 @@ module CreateAvisConcern # the :emails parameter is a 1-element array. # Hence the call to first # https://github.com/rails/rails/issues/17225 - expert_emails = create_avis_params[:emails].first.split(',').map(&:strip) + expert_emails = create_avis_params[:emails].presence || [].to_json + expert_emails = JSON.parse(expert_emails).map(&:strip).map(&:downcase) allowed_dossiers = [dossier] if create_avis_params[:invite_linked_dossiers].present? @@ -65,6 +66,6 @@ module CreateAvisConcern end def create_avis_params - params.require(:avis).permit(:introduction_file, :introduction, :confidentiel, :invite_linked_dossiers, emails: []) + params.require(:avis).permit(:introduction_file, :introduction, :confidentiel, :invite_linked_dossiers, :emails) end end From f9e4d9c982887c00d3d00bbde14a9f591b3d4f03 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 1 Apr 2021 11:58:33 +0200 Subject: [PATCH 13/16] add variable to dossier controller --- app/controllers/instructeurs/dossiers_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 7ce205c9c..74930bfcb 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -60,6 +60,7 @@ module Instructeurs def avis @avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at @avis = Avis.new + @experts_emails = dossier.procedure.experts_procedures.where(revoked_at: nil).map(&:expert).map(&:email).sort end def personnes_impliquees From 03fd6eaeea1a84ca272a2b7bf08954e30532dab6 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 1 Apr 2021 11:59:22 +0200 Subject: [PATCH 14/16] add react component to invite an expert --- .../instructeurs/shared/avis/_form.html.haml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/views/instructeurs/shared/avis/_form.html.haml b/app/views/instructeurs/shared/avis/_form.html.haml index a1bcf1fac..466b48c66 100644 --- a/app/views/instructeurs/shared/avis/_form.html.haml +++ b/app/views/instructeurs/shared/avis/_form.html.haml @@ -1,9 +1,22 @@ %section.ask-avis %h1.tab-title Inviter des personnes à donner leur avis %p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier. + - if @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis) + %p.avis-notice Choisissez des experts à qui vous souhaitez demander un avis parmi la liste prédéfinie par les administrateurs de la démarche + - else + %p.avis-notice Entrez les adresses email des experts à qui vous souhaitez demander un avis = form_for avis, url: url, html: { class: 'form' } do |f| - = f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);" + - hidden_field_id = SecureRandom.uuid + = hidden_field_tag 'avis[emails]', nil, data: { uuid: hidden_field_id } + = react_component("ComboMultipleDropdownList", + options: @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis) ? @experts_emails : [], + selected: [], + disabled: [], + hiddenFieldId: hidden_field_id, + label: 'avis_emails', + id: 'avis_emails', + acceptNewValues: @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis).blank?) = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true %p.tab-title Ajouter une pièce jointe .form-group From a8b30c0cdcf3b8b059ecb3d040bdda1ff3da3e84 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 1 Apr 2021 12:00:07 +0200 Subject: [PATCH 15/16] add react component (expert view) and block it when the flag is activated --- app/views/experts/shared/avis/_form.html.haml | 58 +++++++++++-------- .../features/instructeurs/instruction_spec.rb | 10 ++-- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/app/views/experts/shared/avis/_form.html.haml b/app/views/experts/shared/avis/_form.html.haml index a1bcf1fac..a8ec6a80c 100644 --- a/app/views/experts/shared/avis/_form.html.haml +++ b/app/views/experts/shared/avis/_form.html.haml @@ -1,30 +1,38 @@ -%section.ask-avis - %h1.tab-title Inviter des personnes à donner leur avis - %p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier. +- if @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis).blank? + %section.ask-avis + %h1.tab-title Inviter des personnes à donner leur avis + %p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier. - = form_for avis, url: url, html: { class: 'form' } do |f| - = f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);" - = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true - %p.tab-title Ajouter une pièce jointe - .form-group - = text_upload_and_render f, avis.introduction_file + = form_for avis, url: url, html: { class: 'form' } do |f| + - hidden_field_id = SecureRandom.uuid + = hidden_field_tag 'avis[emails]', nil, data: { uuid: hidden_field_id } + = react_component("ComboMultipleDropdownList", + options: [], + selected: [], disabled: [], + hiddenFieldId: hidden_field_id, + label: 'avis_emails', + acceptNewValues: true) + = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true + %p.tab-title Ajouter une pièce jointe + .form-group + = text_upload_and_render f, avis.introduction_file - - if linked_dossiers.present? - = f.check_box :invite_linked_dossiers, {}, true, false - = f.label :invite_linked_dossiers, t('helpers.label.invite_linked_dossiers', count: linked_dossiers.length, ids: linked_dossiers.map(&:id).to_sentence) + - if linked_dossiers.present? + = f.check_box :invite_linked_dossiers, {}, true, false + = f.label :invite_linked_dossiers, t('helpers.label.invite_linked_dossiers', count: linked_dossiers.length, ids: linked_dossiers.map(&:id).to_sentence) - .flex.justify-between.align-baseline - - if must_be_confidentiel - %p.confidentiel.flex - %span.icon.lock - %span - Cet avis sera confidentiel : il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. + .flex.justify-between.align-baseline + - if must_be_confidentiel + %p.confidentiel.flex + %span.icon.lock + %span + Cet avis sera confidentiel : il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. - - else - .confidentiel-wrapper - = f.label :confidentiel, 'Cet avis sera ' - = f.select :confidentiel, [['partagé avec les autres experts', false], ['confidentiel', true]], {}, onchange: "javascript:DS.toggleCondidentielExplanation(event);" - .confidentiel-explanation.hidden - Il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. + - else + .confidentiel-wrapper + = f.label :confidentiel, 'Cet avis sera ' + = f.select :confidentiel, [['partagé avec les autres experts', false], ['confidentiel', true]], {}, onchange: "javascript:DS.toggleCondidentielExplanation(event);" + .confidentiel-explanation.hidden + Il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. - = f.submit 'Demander un avis', class: 'button primary send' + = f.submit 'Demander un avis', class: 'button primary send' diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index b1c5a794d..b0c9aeef6 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -1,4 +1,4 @@ -feature 'Instructing a dossier:' do +feature 'Instructing a dossier:', js: true do include ActiveJob::TestHelper let(:password) { 'my-s3cure-p4ssword' } @@ -101,11 +101,13 @@ feature 'Instructing a dossier:' do click_on 'Avis externes' expect(page).to have_current_path(avis_instructeur_dossier_path(procedure, dossier)) + expert_email_formated = "[\"expert@tps.com\"]" expert_email = 'expert@tps.com' - ask_confidential_avis(expert_email, 'a good introduction') + ask_confidential_avis(expert_email_formated, 'a good introduction') + expert_email_formated = "[\"#{instructeur2.email}\"]" expert_email = instructeur2.email - ask_confidential_avis(expert_email, 'a good introduction') + ask_confidential_avis(expert_email_formated, 'a good introduction') click_on 'Personnes impliquées' expect(page).to have_text(expert_email) @@ -206,7 +208,7 @@ feature 'Instructing a dossier:' do end def ask_confidential_avis(to, introduction) - fill_in 'avis_emails', with: to + page.execute_script("document.querySelector('#avis_emails').value = '#{to}'") fill_in 'avis_introduction', with: introduction select 'confidentiel', from: 'avis_confidentiel' click_on 'Demander un avis' From 2a0c40c25f5a197e9f5aab509914c6d771f1cd0d Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Fri, 2 Apr 2021 16:00:32 +0200 Subject: [PATCH 16/16] add experts_procedures controller --- .../experts_procedures_controller.rb | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 app/controllers/new_administrateur/experts_procedures_controller.rb diff --git a/app/controllers/new_administrateur/experts_procedures_controller.rb b/app/controllers/new_administrateur/experts_procedures_controller.rb new file mode 100644 index 000000000..1b965f0dc --- /dev/null +++ b/app/controllers/new_administrateur/experts_procedures_controller.rb @@ -0,0 +1,43 @@ +module NewAdministrateur + class ExpertsProceduresController < AdministrateurController + before_action :retrieve_procedure, only: [:add_expert_to_procedure, :revoke_expert_from_procedure] + + def add_expert_to_procedure + emails = params['emails'].presence || [].to_json + emails = JSON.parse(emails).map(&:strip).map(&:downcase) + + valid_users, invalid_users = emails + .map { |email| User.create_or_promote_to_expert(email, SecureRandom.hex) } + .partition(&:valid?) + + if invalid_users.any? + flash[:alert] = invalid_users + .filter { |user| user.errors.present? } + .map { |user| "#{user.email} : #{user.errors.full_messages_for(:email).join(', ')}" } + end + + if valid_users.present? + valid_users.each do |user| + experts_procedure = ExpertsProcedure.find_or_create_by(expert: user.expert, procedure: @procedure) + if !experts_procedure.revoked_at.nil? + experts_procedure.update!(revoked_at: nil) + end + end + + flash[:notice] = t('.experts_assignment', + count: valid_users.count, + value: valid_users.map(&:email).join(', '), + procedure: @procedure.id) + end + redirect_to admin_procedure_invited_expert_list_path(@procedure) + end + + def revoke_expert_from_procedure + expert_procedure = ExpertsProcedure.find_by!(procedure: @procedure, id: params[:expert_procedure][:id]) + expert_email = expert_procedure.expert.email + expert_procedure.update!(revoked_at: Time.zone.now) + flash[:notice] = "#{expert_email} a été révoqué de la démarche et ne pourra plus déposer d'avis." + redirect_to admin_procedure_invited_expert_list_path(@procedure) + end + end +end