From 5f419954b408d3bb43a098af20ed74aa9d37afe9 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 12 May 2021 19:04:31 +0200 Subject: [PATCH 01/11] Fix dossier deleted user display --- app/models/dossier.rb | 4 ++-- app/views/instructeurs/dossiers/print.html.haml | 2 +- app/views/shared/dossiers/_demande.html.haml | 2 +- app/views/shared/dossiers/_user_infos.html.haml | 2 +- spec/models/user_spec.rb | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index acb3b8db5..cd288cae0 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -63,7 +63,6 @@ class Dossier < ApplicationRecord has_one :etablissement, dependent: :destroy has_one :individual, validate: false, dependent: :destroy has_one :attestation, dependent: :destroy - has_one :france_connect_information, through: :user # FIXME: some dossiers have more than one attestation has_many :attestations, dependent: :destroy @@ -88,6 +87,7 @@ class Dossier < ApplicationRecord belongs_to :groupe_instructeur, optional: true belongs_to :revision, class_name: 'ProcedureRevision', optional: false belongs_to :user, optional: true + has_one :france_connect_information, through: :user has_one :procedure, through: :revision has_many :types_de_champ, through: :revision @@ -340,7 +340,7 @@ class Dossier < ApplicationRecord accepts_nested_attributes_for :individual delegate :siret, :siren, to: :etablissement, allow_nil: true - delegate :france_connect_information, to: :user + delegate :france_connect_information, to: :user, allow_nil: true before_save :build_default_champs, if: Proc.new { revision_id_was.nil? } before_save :update_search_terms diff --git a/app/views/instructeurs/dossiers/print.html.haml b/app/views/instructeurs/dossiers/print.html.haml index 25bfa2aec..656085d58 100644 --- a/app/views/instructeurs/dossiers/print.html.haml +++ b/app/views/instructeurs/dossiers/print.html.haml @@ -3,7 +3,7 @@ %h2 Identité du demandeur -= render partial: "shared/dossiers/user_infos", locals: { user: @dossier.user } += render partial: "shared/dossiers/user_infos", locals: { user_deleted: @dossier.user_deleted?, email: @dossier.user_email_for(:display) } - if @dossier.etablissement.present? = render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: @dossier.etablissement, profile: 'instructeur' } diff --git a/app/views/shared/dossiers/_demande.html.haml b/app/views/shared/dossiers/_demande.html.haml index a4c45ec87..585a49319 100644 --- a/app/views/shared/dossiers/_demande.html.haml +++ b/app/views/shared/dossiers/_demande.html.haml @@ -7,7 +7,7 @@ .card - if dossier.france_connect_information.present? = render partial: "shared/dossiers/france_connect_informations", locals: { user_information: dossier.france_connect_information } - = render partial: "shared/dossiers/user_infos", locals: { user: dossier.user } + = render partial: "shared/dossiers/user_infos", locals: { user_deleted: dossier.user_deleted?, email: dossier.user_email_for(:display) } - if dossier.etablissement.present? = render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: dossier.etablissement, profile: profile } diff --git a/app/views/shared/dossiers/_user_infos.html.haml b/app/views/shared/dossiers/_user_infos.html.haml index bf003b719..5aed997d0 100644 --- a/app/views/shared/dossiers/_user_infos.html.haml +++ b/app/views/shared/dossiers/_user_infos.html.haml @@ -2,4 +2,4 @@ %tbody %tr %th.libelle Email : - %td= user.email + %td= user_deleted ? "#{email} (l‘usager a supprimé son compte)" : email diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 002f0b2d2..15401ff14 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -348,6 +348,7 @@ describe User, type: :model do expect(dossier_termine.user).to be_nil expect(dossier_termine.user_email_for(:display)).to eq(user.email) expect(dossier_termine.valid?).to be_truthy + expect(dossier_termine.france_connect_information).to be_nil expect { dossier_termine.user_email_for(:notification) }.to raise_error(RuntimeError) expect(User.find_by(id: user.id)).to be_nil From b3caa2e5f4e5c9ed04c453e13c59be6b7f525257 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 29 Apr 2021 09:32:25 +0200 Subject: [PATCH 02/11] add route --- config/routes.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index acdac88ea..c6bb99c2a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -147,6 +147,7 @@ Rails.application.routes.draw do end resources :attachments, only: [:show, :destroy] + resources :recherche, only: [:index] get "patron" => "root#patron" get "suivi" => "root#suivi" @@ -294,7 +295,6 @@ Rails.application.routes.draw do # scope module: 'experts', as: 'expert' do get 'avis', to: 'avis#index', as: 'all_avis' - # this redirections are ephemeral, to ensure that emails sent to experts before are still valid # TODO : they will be removed in September, 2020 get 'avis/:id', to: redirect('/procedures/old/avis/%{id}') @@ -387,7 +387,6 @@ Rails.application.routes.draw do resources :archives, only: [:index, :create, :show], controller: 'archives' end end - get "recherche" => "recherche#index" end # From 7d9cf63056308c935d8b5a9447c42039a367bb29 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 29 Apr 2021 09:32:47 +0200 Subject: [PATCH 03/11] add and modify controllers --- .../instructeurs/recherche_controller.rb | 12 ----- app/controllers/recherche_controller.rb | 53 +++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) delete mode 100644 app/controllers/instructeurs/recherche_controller.rb create mode 100644 app/controllers/recherche_controller.rb diff --git a/app/controllers/instructeurs/recherche_controller.rb b/app/controllers/instructeurs/recherche_controller.rb deleted file mode 100644 index 298abb3ea..000000000 --- a/app/controllers/instructeurs/recherche_controller.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Instructeurs - class RechercheController < InstructeurController - def index - @search_terms = params[:q] - @dossiers = DossierSearchService.matching_dossiers_for_instructeur(@search_terms, current_instructeur) - @followed_dossiers_id = current_instructeur - .followed_dossiers - .where(groupe_instructeur_id: @dossiers.pluck(:groupe_instructeur_id)) - .pluck(:id) - end - end -end diff --git a/app/controllers/recherche_controller.rb b/app/controllers/recherche_controller.rb new file mode 100644 index 000000000..681ddb166 --- /dev/null +++ b/app/controllers/recherche_controller.rb @@ -0,0 +1,53 @@ +class RechercheController < ApplicationController + before_action :authenticate_logged_user! + ITEMS_PER_PAGE = 25 + PROJECTIONS = [ + { "table" => 'procedure', "column" => 'libelle' }, + { "table" => 'user', "column" => 'email' }, + { "table" => 'procedure', "column" => 'procedure_id' } + ] + + def index + @search_terms = search_terms + matching_dossiers_ids = [] + instructeur_dossiers_ids = [] + expert_dossiers_ids = [] + + if instructeur_signed_in? + instructeur_dossiers_ids.concat(current_instructeur.dossiers.ids) + @followed_dossiers_id = current_instructeur.followed_dossiers.where(id: instructeur_dossiers_ids).ids + + if instructeur_dossiers_ids.present? + matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(instructeur_dossiers_ids, @search_terms, true)) + end + end + + if expert_signed_in? + @dossier_avis_ids_h = current_expert.avis.pluck(:dossier_id, :id).to_h + expert_dossiers_ids.concat(@dossier_avis_ids_h.keys) + + if expert_dossiers_ids.present? + matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(expert_dossiers_ids, @search_terms)) + end + end + + @paginated_ids = Kaminari + .paginate_array(matching_dossiers_ids.uniq) + .page(page) + .per(ITEMS_PER_PAGE) + + @dossiers_count = matching_dossiers_ids.count + + @projected_dossiers = DossierProjectionService.project(@paginated_ids, PROJECTIONS) + end + + private + + def page + params[:page].presence || 1 + end + + def search_terms + params[:q] + end +end From fdde55f6753abcbb0f4c62b32153d0e599860ca1 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 29 Apr 2021 09:33:04 +0200 Subject: [PATCH 04/11] modify service --- app/services/dossier_projection_service.rb | 6 ++++++ app/services/dossier_search_service.rb | 25 ++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/services/dossier_projection_service.rb b/app/services/dossier_projection_service.rb index eec073616..a8672bba0 100644 --- a/app/services/dossier_projection_service.rb +++ b/app/services/dossier_projection_service.rb @@ -74,6 +74,12 @@ class DossierProjectionService .where(id: dossiers_ids) .pluck('dossiers.id, groupe_instructeurs.label') .to_h + when 'procedure' + Dossier + .joins(:procedure) + .where(id: dossiers_ids) + .pluck(:id, *fields.map { |f| f[COLUMN].to_sym }) + .each { |id, *columns| fields.zip(columns).each { |field, value| field[:id_value_h][id] = value } } when 'followers_instructeurs' fields[0][:id_value_h] = Follow .active diff --git a/app/services/dossier_search_service.rb b/app/services/dossier_search_service.rb index 583ab512d..dabf56e7a 100644 --- a/app/services/dossier_search_service.rb +++ b/app/services/dossier_search_service.rb @@ -1,7 +1,7 @@ class DossierSearchService - def self.matching_dossiers_for_instructeur(search_terms, instructeur) - dossier_by_exact_id_for_instructeur(search_terms, instructeur) - .presence || dossier_by_full_text_for_instructeur(search_terms, instructeur) + def self.matching_dossiers(ids, search_terms, with_annotations = false) + dossier_by_exact_id(ids, search_terms) + .presence || dossier_by_full_text(ids, search_terms, with_annotations) end def self.matching_dossiers_for_user(search_terms, user) @@ -11,17 +11,24 @@ class DossierSearchService private - def self.dossier_by_exact_id_for_instructeur(search_terms, instructeur) + def self.dossier_by_exact_id(ids, search_terms) id = search_terms.to_i if id != 0 && id_compatible?(id) # Sometimes instructeur is searching dossiers with a big number (ex: SIRET), ActiveRecord can't deal with them and throws ActiveModel::RangeError. id_compatible? prevents this. - dossiers_by_id(id, instructeur) + ids.filter { |dossier_id| dossier_id == id }.uniq else Dossier.none end end - def self.dossiers_by_id(id, instructeur) - instructeur.dossiers.where(id: id).uniq + def self.dossier_by_full_text(ids, search_terms, with_annotations) + ts_vector = "to_tsvector('french', #{with_annotations ? 'dossiers.search_terms || dossiers.private_search_terms' : 'dossiers.search_terms'})" + ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})" + + Dossier.where(id: ids) + .where("#{ts_vector} @@ #{ts_query}") + .order(Arel.sql("COALESCE(ts_rank(#{ts_vector}, #{ts_query}), 0) DESC")) + .pluck('id') + .uniq end def self.id_compatible?(number) @@ -49,11 +56,11 @@ class DossierSearchService end end - def self.dossier_by_full_text_for_instructeur(search_terms, instructeur) + def self.dossier_by_full_text_for_current_user(search_terms, user) ts_vector = "to_tsvector('french', search_terms || private_search_terms)" ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})" - instructeur + user .dossiers .state_not_brouillon .where("#{ts_vector} @@ #{ts_query}") From 6a1ed2e02d22e68111c063de859ae0752f462c27 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 29 Apr 2021 09:33:23 +0200 Subject: [PATCH 05/11] layout --- .../procedures/_dossier_actions.html.haml | 8 +- .../instructeurs/recherche/index.html.haml | 42 ------- app/views/layouts/_header.haml | 8 +- app/views/recherche/index.html.haml | 105 ++++++++++++++++++ 4 files changed, 116 insertions(+), 47 deletions(-) delete mode 100644 app/views/instructeurs/recherche/index.html.haml create mode 100644 app/views/recherche/index.html.haml diff --git a/app/views/instructeurs/procedures/_dossier_actions.html.haml b/app/views/instructeurs/procedures/_dossier_actions.html.haml index 14983ffa8..6aba070b5 100644 --- a/app/views/instructeurs/procedures/_dossier_actions.html.haml +++ b/app/views/instructeurs/procedures/_dossier_actions.html.haml @@ -1,19 +1,19 @@ - if Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state) - if dossier_is_followed - = link_to unfollow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do + = link_to unfollow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do %span.icon.unfollow> Ne plus suivre - else - = link_to follow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do + = link_to follow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do %span.icon.follow> Suivre le dossier - elsif Dossier::TERMINE.include?(state) - if archived - = link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do + = link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do %span.icon.unarchive> Désarchiver le dossier - else - = link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do + = link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do %span.icon.archive> Archiver le dossier diff --git a/app/views/instructeurs/recherche/index.html.haml b/app/views/instructeurs/recherche/index.html.haml deleted file mode 100644 index b08a849b5..000000000 --- a/app/views/instructeurs/recherche/index.html.haml +++ /dev/null @@ -1,42 +0,0 @@ -- content_for(:title, "Recherche : #{@search_terms}") - -.container - .page-title - Résultat de la recherche : - = t('pluralize.dossier_trouve', count: @dossiers.count) - - - if @dossiers.present? - %table.table.dossiers-table.hoverable - %thead - %tr - %th.notification-col - %th.number-col Nº dossier - %th Démarche - %th Demandeur - %th.status-col Statut - %th.action-col.follow-col - %tbody - - @dossiers.each do |dossier| - / # FIXME: here we have a n+1, we fire a request - / (due to dossier_linked_path) per result - %tr - %td.folder-col - = link_to(dossier_linked_path(current_instructeur, dossier), class: 'cell-link') do - %span.icon.folder - %td.number-col - = link_to(dossier_linked_path(current_instructeur, dossier), class: 'cell-link') do - = dossier.id - %td= link_to(dossier.procedure.libelle, dossier_linked_path(current_instructeur, dossier), class: 'cell-link') - %td= link_to(dossier.user_email_for(:display), dossier_linked_path(current_instructeur, dossier), class: 'cell-link') - %td.status-col - = link_to(dossier_linked_path(current_instructeur, dossier), class: 'cell-link') do - = status_badge(dossier.state) - %td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions", - locals: { procedure_id: dossier.procedure.id, - dossier_id: dossier.id, - state: dossier.state, - archived: dossier.archived, - dossier_is_followed: @followed_dossiers_id.include?(dossier.id) } - - - else - %h2 Aucun dossier correspondant à votre recherche n'a été trouvé diff --git a/app/views/layouts/_header.haml b/app/views/layouts/_header.haml index 88e2c7266..7d3c684e7 100644 --- a/app/views/layouts/_header.haml +++ b/app/views/layouts/_header.haml @@ -50,9 +50,15 @@ = active_link_to "Dossiers", dossiers_path, active: :inclusive, class: 'tab-link' %ul.header-right-content + - if params[:controller] == 'recherche' + = render partial: 'layouts/search_dossiers_form', locals: { search_endpoint: recherche_index_path } + - if nav_bar_profile == :instructeur && instructeur_signed_in? %li - = render partial: 'layouts/search_dossiers_form', locals: { search_endpoint: instructeur_recherche_path } + = render partial: 'layouts/search_dossiers_form', locals: { search_endpoint: recherche_index_path } + + - if nav_bar_profile == :expert && expert_signed_in? + = render partial: 'layouts/search_dossiers_form', locals: { search_endpoint: recherche_index_path } - if nav_bar_profile == :user && user_signed_in? && current_user.dossiers.count > 2 %li diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml new file mode 100644 index 000000000..f83bbafe5 --- /dev/null +++ b/app/views/recherche/index.html.haml @@ -0,0 +1,105 @@ +- content_for(:title, "Recherche : #{@search_terms}") +- pagination = paginate @paginated_ids +- procedure_libelle_index = 0 +- user_email_index = 1 + +.container + .page-title + Résultat de la recherche : + = t('pluralize.dossier_trouve', count: @dossiers_count) + + - if @projected_dossiers.present? + %table.table.dossiers-table.hoverable + %thead + %tr + %th.notification-col + %th.number-col Nº dossier + %th Démarche + %th Demandeur + %th.status-col Statut + %th.action-col.follow-col + %tbody + - @projected_dossiers.each do |p| + - procedure_id = p.columns.last + - if current_expert.present? + - avis_id = @dossier_avis_ids_h[p.dossier_id] + + - if (current_expert.present? && current_instructeur.blank?) || (current_instructeur.present? && current_expert.blank?) + + - path = current_instructeur.present? ? instructeur_dossier_path(procedure_id, p.dossier_id) : expert_avis_path(procedure_id, avis_id) + + %tr + %td.folder-col + = link_to(path, class: 'cell-link') do + %span.icon.folder + %td.number-col + = link_to(path, class: 'cell-link') do + = p.dossier_id + + - p.columns.values_at(procedure_libelle_index, user_email_index).each do |column| + %td + %a.cell-link{ href: path }= column + + %td.status-col + = link_to(path, class: 'cell-link') do + = status_badge(p.state) + + - if current_instructeur.present? + %td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions", + locals: { procedure_id: procedure_id, + dossier_id: p.dossier_id, + state: p.state, + archived: p.archived, + button_class: false, + dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) } + + - else + %tr + %td.folder-col + %span.icon.folder + %td.number-col + .cell-link + = p.dossier_id + + - p.columns.values_at(procedure_libelle_index, user_email_index).each do |column| + %td + .cell-link + = column + + %td.status-col + .cell-link + = status_badge(p.state) + %td.action-col.follow-col + .dropdown + .button.dropdown-button + Actions + .dropdown-content.fade-in-down + %ul.dropdown-items.pl-0 + - if current_instructeur.present? + %li + .dropdown-description + %h4 + = render partial: "instructeurs/procedures/dossier_actions", + locals: { procedure_id: procedure_id, + dossier_id: p.dossier_id, + state: p.state, + archived: p.archived, + button_class: false, + dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) } + + %li + = link_to instructeur_dossier_path(procedure_id, p.dossier_id) do + %span.icon.in-progress + .dropdown-description + %h4 Instruire le dossier + - if avis_id.present? + %li + = link_to(expert_avis_path(procedure_id, avis_id)) do + %span.icon.in-progress + .dropdown-description + %h4 Donner mon avis + = pagination + + + - else + %h2 Aucun dossier correspondant à votre recherche n'a été trouvé From 468e9e849a2cfc9e7069b1dbfb85f2f1c5011fb5 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 29 Apr 2021 09:33:32 +0200 Subject: [PATCH 06/11] tests --- .../recherche_controller_spec.rb | 15 +++++++++++---- spec/services/dossier_search_service_spec.rb | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) rename spec/controllers/{instructeurs => }/recherche_controller_spec.rb (85%) diff --git a/spec/controllers/instructeurs/recherche_controller_spec.rb b/spec/controllers/recherche_controller_spec.rb similarity index 85% rename from spec/controllers/instructeurs/recherche_controller_spec.rb rename to spec/controllers/recherche_controller_spec.rb index 8698c6102..73ae9e0c1 100644 --- a/spec/controllers/instructeurs/recherche_controller_spec.rb +++ b/spec/controllers/recherche_controller_spec.rb @@ -1,23 +1,30 @@ -describe Instructeurs::RechercheController, type: :controller do +describe RechercheController, type: :controller do let(:dossier) { create(:dossier, :en_construction) } let(:dossier2) { create(:dossier, :en_construction, procedure: dossier.procedure) } let(:instructeur) { create(:instructeur) } - before { instructeur.groupe_instructeurs << dossier2.procedure.defaut_groupe_instructeur } + before do + #instructeur.groupe_instructeurs << dossier2.procedure.defaut_groupe_instructeur + instructeur.groupe_instructeurs << dossier.procedure.defaut_groupe_instructeur + end describe 'GET #index' do before { sign_in(instructeur.user) } - + subject { get :index, params: { q: query } } describe 'by id' do context 'when instructeur own the dossier' do + + before do + subject + end + let(:query) { dossier.id } it { is_expected.to have_http_status(200) } it 'returns the expected dossier' do - subject expect(assigns(:dossiers).count).to eq(1) expect(assigns(:dossiers).first.id).to eq(dossier.id) end diff --git a/spec/services/dossier_search_service_spec.rb b/spec/services/dossier_search_service_spec.rb index c004f1d5a..cf8b02456 100644 --- a/spec/services/dossier_search_service_spec.rb +++ b/spec/services/dossier_search_service_spec.rb @@ -1,9 +1,9 @@ describe DossierSearchService do - describe '#matching_dossiers_for_instructeur' do + describe '#matching_dossiers_for_current_user' do subject { liste_dossiers } let(:liste_dossiers) do - described_class.matching_dossiers_for_instructeur(terms, instructeur_1) + described_class.matching_dossiers_for_current_user(terms, instructeur_1.user) end let(:administrateur_1) { create(:administrateur) } From e043645a885a1ae9d548566893aa76fb7316f4e8 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 6 May 2021 01:14:57 +0200 Subject: [PATCH 07/11] cleanup tests --- app/services/dossier_search_service.rb | 21 ++---- spec/controllers/recherche_controller_spec.rb | 73 +++++++++++++++---- spec/services/dossier_search_service_spec.rb | 4 +- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/app/services/dossier_search_service.rb b/app/services/dossier_search_service.rb index dabf56e7a..dc5eab1a1 100644 --- a/app/services/dossier_search_service.rb +++ b/app/services/dossier_search_service.rb @@ -31,13 +31,6 @@ class DossierSearchService .uniq end - def self.id_compatible?(number) - ActiveRecord::Type::Integer.new.serialize(number) - true - rescue ActiveModel::RangeError - false - end - def self.dossier_by_full_text_for_user(search_terms, dossiers) ts_vector = "to_tsvector('french', search_terms)" ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})" @@ -56,15 +49,11 @@ class DossierSearchService end end - def self.dossier_by_full_text_for_current_user(search_terms, user) - ts_vector = "to_tsvector('french', search_terms || private_search_terms)" - ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})" - - user - .dossiers - .state_not_brouillon - .where("#{ts_vector} @@ #{ts_query}") - .order(Arel.sql("COALESCE(ts_rank(#{ts_vector}, #{ts_query}), 0) DESC")) + def self.id_compatible?(number) + ActiveRecord::Type::Integer.new.serialize(number) + true + rescue ActiveModel::RangeError + false end def self.to_tsquery(search_terms) diff --git a/spec/controllers/recherche_controller_spec.rb b/spec/controllers/recherche_controller_spec.rb index 73ae9e0c1..2e643bd84 100644 --- a/spec/controllers/recherche_controller_spec.rb +++ b/spec/controllers/recherche_controller_spec.rb @@ -1,32 +1,47 @@ describe RechercheController, type: :controller do - let(:dossier) { create(:dossier, :en_construction) } + let(:dossier) { create(:dossier, :en_construction, :with_all_annotations) } let(:dossier2) { create(:dossier, :en_construction, procedure: dossier.procedure) } let(:instructeur) { create(:instructeur) } + let(:dossier_with_expert) { avis.dossier } + let(:avis) { create(:avis, dossier: create(:dossier, :en_construction, :with_all_annotations)) } + + let(:user) { instructeur.user } + before do - #instructeur.groupe_instructeurs << dossier2.procedure.defaut_groupe_instructeur - instructeur.groupe_instructeurs << dossier.procedure.defaut_groupe_instructeur + instructeur.assign_to_procedure(dossier.procedure) end describe 'GET #index' do - before { sign_in(instructeur.user) } - + before { sign_in(user) } + subject { get :index, params: { q: query } } describe 'by id' do context 'when instructeur own the dossier' do - - before do - subject - end - let(:query) { dossier.id } + before { subject } + it { is_expected.to have_http_status(200) } it 'returns the expected dossier' do - expect(assigns(:dossiers).count).to eq(1) - expect(assigns(:dossiers).first.id).to eq(dossier.id) + expect(assigns(:projected_dossiers).count).to eq(1) + expect(assigns(:projected_dossiers).first.dossier_id).to eq(dossier.id) + end + end + + context 'when expert own the dossier' do + let(:user) { avis.experts_procedure.expert.user } + let(:query) { dossier_with_expert.id } + + before { subject } + + it { is_expected.to have_http_status(200) } + + it 'returns the expected dossier' do + expect(assigns(:projected_dossiers).count).to eq(1) + expect(assigns(:projected_dossiers).first.dossier_id).to eq(dossier_with_expert.id) end end @@ -38,7 +53,7 @@ describe RechercheController, type: :controller do it 'does not return the dossier' do subject - expect(assigns(:dossiers).count).to eq(0) + expect(assigns(:projected_dossiers).count).to eq(0) end end @@ -49,7 +64,35 @@ describe RechercheController, type: :controller do it 'does not return the dossier' do subject - expect(assigns(:dossiers).count).to eq(0) + expect(assigns(:projected_dossiers).count).to eq(0) + end + end + end + + describe 'by private annotations' do + context 'when instructeur search by private annotations' do + let(:query) { dossier.private_search_terms } + + before { subject } + + it { is_expected.to have_http_status(200) } + + it 'returns the expected dossier' do + expect(assigns(:projected_dossiers).count).to eq(1) + expect(assigns(:projected_dossiers).first.dossier_id).to eq(dossier.id) + end + end + + context 'when expert search by private annotations' do + let(:user) { avis.experts_procedure.expert.user } + let(:query) { dossier_with_expert.private_search_terms } + + before { subject } + + it { is_expected.to have_http_status(200) } + + it 'returns 0 dossiers' do + expect(assigns(:projected_dossiers).count).to eq(0) end end end @@ -61,7 +104,7 @@ describe RechercheController, type: :controller do it 'returns 0 dossier' do subject - expect(assigns(:dossiers).count).to eq(0) + expect(assigns(:projected_dossiers).count).to eq(0) end end end diff --git a/spec/services/dossier_search_service_spec.rb b/spec/services/dossier_search_service_spec.rb index cf8b02456..6528847fe 100644 --- a/spec/services/dossier_search_service_spec.rb +++ b/spec/services/dossier_search_service_spec.rb @@ -1,9 +1,9 @@ describe DossierSearchService do - describe '#matching_dossiers_for_current_user' do + describe '#matching_dossiers' do subject { liste_dossiers } let(:liste_dossiers) do - described_class.matching_dossiers_for_current_user(terms, instructeur_1.user) + described_class.matching_dossiers(instructeur_1.dossiers, terms) end let(:administrateur_1) { create(:administrateur) } From 02e2128fb7244b4c1e9d374ab4e03812c31f202e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 17 May 2021 15:42:21 +0200 Subject: [PATCH 08/11] proposition de simplification --- app/controllers/recherche_controller.rb | 24 +---- app/views/recherche/index.html.haml | 117 ++++++++++-------------- 2 files changed, 53 insertions(+), 88 deletions(-) diff --git a/app/controllers/recherche_controller.rb b/app/controllers/recherche_controller.rb index 681ddb166..a5eadbf59 100644 --- a/app/controllers/recherche_controller.rb +++ b/app/controllers/recherche_controller.rb @@ -9,27 +9,13 @@ class RechercheController < ApplicationController def index @search_terms = search_terms - matching_dossiers_ids = [] - instructeur_dossiers_ids = [] - expert_dossiers_ids = [] - if instructeur_signed_in? - instructeur_dossiers_ids.concat(current_instructeur.dossiers.ids) - @followed_dossiers_id = current_instructeur.followed_dossiers.where(id: instructeur_dossiers_ids).ids + @instructeur_dossiers_ids = current_instructeur&.dossiers&.ids || [] + matching_dossiers_ids = DossierSearchService.matching_dossiers(@instructeur_dossiers_ids, @search_terms, true) - if instructeur_dossiers_ids.present? - matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(instructeur_dossiers_ids, @search_terms, true)) - end - end - - if expert_signed_in? - @dossier_avis_ids_h = current_expert.avis.pluck(:dossier_id, :id).to_h - expert_dossiers_ids.concat(@dossier_avis_ids_h.keys) - - if expert_dossiers_ids.present? - matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(expert_dossiers_ids, @search_terms)) - end - end + @dossier_avis_ids_h = current_expert&.avis&.pluck(:dossier_id, :id).to_h || {} + expert_dossiers_ids = @dossier_avis_ids_h.keys + matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(expert_dossiers_ids, @search_terms)) @paginated_ids = Kaminari .paginate_array(matching_dossiers_ids.uniq) diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml index f83bbafe5..d1806d853 100644 --- a/app/views/recherche/index.html.haml +++ b/app/views/recherche/index.html.haml @@ -1,13 +1,13 @@ - content_for(:title, "Recherche : #{@search_terms}") - pagination = paginate @paginated_ids -- procedure_libelle_index = 0 -- user_email_index = 1 .container .page-title Résultat de la recherche : = t('pluralize.dossier_trouve', count: @dossiers_count) + = pagination + - if @projected_dossiers.present? %table.table.dossiers-table.hoverable %thead @@ -20,84 +20,63 @@ %th.action-col.follow-col %tbody - @projected_dossiers.each do |p| - - procedure_id = p.columns.last - - if current_expert.present? - - avis_id = @dossier_avis_ids_h[p.dossier_id] + - procedure_libelle, user_email, procedure_id = p.columns + - instructeur_dossier = @instructeur_dossiers_ids.include?(p.dossier_id) + - expert_dossier = @dossier_avis_ids_h[p.dossier_id].present? + - instructeur_and_expert_dossier = instructeur_dossier && expert_dossier + - path = instructeur_dossier ? instructeur_dossier_path(procedure_id, p.dossier_id) : expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id]) - - if (current_expert.present? && current_instructeur.blank?) || (current_instructeur.present? && current_expert.blank?) - - - path = current_instructeur.present? ? instructeur_dossier_path(procedure_id, p.dossier_id) : expert_avis_path(procedure_id, avis_id) - - %tr - %td.folder-col - = link_to(path, class: 'cell-link') do - %span.icon.folder - %td.number-col - = link_to(path, class: 'cell-link') do - = p.dossier_id - - - p.columns.values_at(procedure_libelle_index, user_email_index).each do |column| - %td - %a.cell-link{ href: path }= column - - %td.status-col - = link_to(path, class: 'cell-link') do - = status_badge(p.state) - - - if current_instructeur.present? - %td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions", - locals: { procedure_id: procedure_id, - dossier_id: p.dossier_id, - state: p.state, - archived: p.archived, - button_class: false, - dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) } - - - else - %tr - %td.folder-col + %tr + - if instructeur_and_expert_dossier + %td.folder-col.cell-link %span.icon.folder %td.number-col - .cell-link - = p.dossier_id + .cell-link= p.dossier_id + %td + .cell-link= procedure_libelle + %td + .cell-link= user_email + %td + .cell-link= status_badge(p.state) - - p.columns.values_at(procedure_libelle_index, user_email_index).each do |column| - %td - .cell-link - = column + - else + %td.folder-col + %a.cell-link{ href: path } + %span.icon.folder + + %td.number-col + %a.cell-link{ href: path }= p.dossier_id + + %td + %a.cell-link{ href: path }= procedure_libelle + + %td + %a.cell-link{ href: path }= user_email %td.status-col - .cell-link - = status_badge(p.state) + %a.cell-link{ href: path }= status_badge(p.state) + + + - if instructeur_dossier && expert_dossier %td.action-col.follow-col .dropdown .button.dropdown-button Actions - .dropdown-content.fade-in-down - %ul.dropdown-items.pl-0 - - if current_instructeur.present? - %li - .dropdown-description - %h4 - = render partial: "instructeurs/procedures/dossier_actions", - locals: { procedure_id: procedure_id, - dossier_id: p.dossier_id, - state: p.state, - archived: p.archived, - button_class: false, - dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) } + .dropdown-content.fade-in-down + %ul.dropdown-items + %li + = link_to(instructeur_dossier_path(procedure_id, p.dossier_id)) do + %span.icon.in-progress> + .dropdown-description + Voir le dossier + %li + = link_to(expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id])) do + %span.icon.in-progress> + .dropdown-description + Donner mon avis + - else + %td - %li - = link_to instructeur_dossier_path(procedure_id, p.dossier_id) do - %span.icon.in-progress - .dropdown-description - %h4 Instruire le dossier - - if avis_id.present? - %li - = link_to(expert_avis_path(procedure_id, avis_id)) do - %span.icon.in-progress - .dropdown-description - %h4 Donner mon avis = pagination From 663d2879620c0630f01acf039ef8b9990b5de8b4 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 17 May 2021 15:42:21 +0200 Subject: [PATCH 09/11] proposition de simplification --- app/controllers/recherche_controller.rb | 6 ++++-- .../procedures/_dossier_actions.html.haml | 8 ++++---- app/views/recherche/index.html.haml | 12 ++++++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/controllers/recherche_controller.rb b/app/controllers/recherche_controller.rb index a5eadbf59..d6bb47dc2 100644 --- a/app/controllers/recherche_controller.rb +++ b/app/controllers/recherche_controller.rb @@ -17,14 +17,16 @@ class RechercheController < ApplicationController expert_dossiers_ids = @dossier_avis_ids_h.keys matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(expert_dossiers_ids, @search_terms)) + @dossiers_count = matching_dossiers_ids.count + @paginated_ids = Kaminari .paginate_array(matching_dossiers_ids.uniq) .page(page) .per(ITEMS_PER_PAGE) - @dossiers_count = matching_dossiers_ids.count - @projected_dossiers = DossierProjectionService.project(@paginated_ids, PROJECTIONS) + + @followed_dossiers_id = current_instructeur&.followed_dossiers&.where(id: @paginated_ids)&.ids || [] end private diff --git a/app/views/instructeurs/procedures/_dossier_actions.html.haml b/app/views/instructeurs/procedures/_dossier_actions.html.haml index 6aba070b5..14983ffa8 100644 --- a/app/views/instructeurs/procedures/_dossier_actions.html.haml +++ b/app/views/instructeurs/procedures/_dossier_actions.html.haml @@ -1,19 +1,19 @@ - if Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state) - if dossier_is_followed - = link_to unfollow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do + = link_to unfollow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do %span.icon.unfollow> Ne plus suivre - else - = link_to follow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do + = link_to follow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do %span.icon.follow> Suivre le dossier - elsif Dossier::TERMINE.include?(state) - if archived - = link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do + = link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do %span.icon.unarchive> Désarchiver le dossier - else - = link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: defined?(button_class) && button_class.blank? ? '' : 'button' do + = link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do %span.icon.archive> Archiver le dossier diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml index d1806d853..ba1c84573 100644 --- a/app/views/recherche/index.html.haml +++ b/app/views/recherche/index.html.haml @@ -36,7 +36,7 @@ .cell-link= procedure_libelle %td .cell-link= user_email - %td + %td.status-col .cell-link= status_badge(p.state) - else @@ -74,11 +74,19 @@ %span.icon.in-progress> .dropdown-description Donner mon avis + + - elsif instructeur_dossier + %td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions", + locals: { procedure_id: procedure_id, + dossier_id: p.dossier_id, + state: p.state, + archived: p.archived, + dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) } + - else %td = pagination - - else %h2 Aucun dossier correspondant à votre recherche n'a été trouvé From 3c01488db2c813acf3440ff4e40da3e267ff2f8e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 17 May 2021 17:10:50 +0200 Subject: [PATCH 10/11] use set to avoid duplicate in @dossier_count --- app/controllers/recherche_controller.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/recherche_controller.rb b/app/controllers/recherche_controller.rb index d6bb47dc2..6d02bcbea 100644 --- a/app/controllers/recherche_controller.rb +++ b/app/controllers/recherche_controller.rb @@ -11,16 +11,18 @@ class RechercheController < ApplicationController @search_terms = search_terms @instructeur_dossiers_ids = current_instructeur&.dossiers&.ids || [] - matching_dossiers_ids = DossierSearchService.matching_dossiers(@instructeur_dossiers_ids, @search_terms, true) + matching_dossiers_ids = DossierSearchService + .matching_dossiers(@instructeur_dossiers_ids, @search_terms, with_annotation: true) + .to_set @dossier_avis_ids_h = current_expert&.avis&.pluck(:dossier_id, :id).to_h || {} expert_dossiers_ids = @dossier_avis_ids_h.keys - matching_dossiers_ids.concat(DossierSearchService.matching_dossiers(expert_dossiers_ids, @search_terms)) + matching_dossiers_ids.merge(DossierSearchService.matching_dossiers(expert_dossiers_ids, @search_terms)) @dossiers_count = matching_dossiers_ids.count @paginated_ids = Kaminari - .paginate_array(matching_dossiers_ids.uniq) + .paginate_array(matching_dossiers_ids.to_a) .page(page) .per(ITEMS_PER_PAGE) From a46000dc1f617c44cc9f32a5f97bb4f716e762aa Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 18 May 2021 11:34:05 +0200 Subject: [PATCH 11/11] ensure to_s is used on specialized champ --- app/services/dossier_projection_service.rb | 2 +- spec/services/dossier_projection_service_spec.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/services/dossier_projection_service.rb b/app/services/dossier_projection_service.rb index a8672bba0..c46525147 100644 --- a/app/services/dossier_projection_service.rb +++ b/app/services/dossier_projection_service.rb @@ -33,7 +33,7 @@ class DossierProjectionService types_de_champ: { stable_id: fields.map { |f| f[COLUMN] } }, dossier_id: dossiers_ids ) - .select(:dossier_id, :value, :type_de_champ_id, :stable_id) # we cannot pluck :value, as we need the champ.to_s method + .select(:dossier_id, :value, :type_de_champ_id, :stable_id, :type) # we cannot pluck :value, as we need the champ.to_s method .group_by(&:stable_id) # the champs are redispatched to their respective fields .map do |stable_id, champs| field = fields.find { |f| f[COLUMN] == stable_id.to_s } diff --git a/spec/services/dossier_projection_service_spec.rb b/spec/services/dossier_projection_service_spec.rb index ec14ac9e1..0589d7baa 100644 --- a/spec/services/dossier_projection_service_spec.rb +++ b/spec/services/dossier_projection_service_spec.rb @@ -162,6 +162,17 @@ describe DossierProjectionService do it { is_expected.to eq('quinoa') } end + + context 'for type_de_champ table and value to.s' do + let(:table) { 'type_de_champ' } + let(:procedure) { create(:procedure, :with_yes_no) } + let(:dossier) { create(:dossier, procedure: procedure) } + let(:column) { dossier.procedure.types_de_champ.first.stable_id.to_s } + + before { dossier.champs.first.update(value: 'true') } + + it { is_expected.to eq('Oui') } + end end end end