use displayed_columns !

This commit is contained in:
simon lehericey 2024-10-07 15:00:05 +02:00
parent 7e4ca07df2
commit 14fe11b612
No known key found for this signature in database
GPG key ID: CDE670D827C7B3C5
9 changed files with 78 additions and 97 deletions

View file

@ -12,7 +12,7 @@ class Instructeurs::ColumnPickerComponent < ApplicationComponent
def displayable_columns_for_select def displayable_columns_for_select
[ [
procedure.columns.filter(&:displayable).map { |column| [column.label, column.id] }, procedure.columns.filter(&:displayable).map { |column| [column.label, column.id] },
procedure_presentation.displayed_fields.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id: procedure.id)).id } procedure_presentation.displayed_columns.map(&:id)
] ]
end end
end end

View file

@ -104,7 +104,7 @@ module Instructeurs
.page(page) .page(page)
.per(ITEMS_PER_PAGE) .per(ITEMS_PER_PAGE)
@projected_dossiers = DossierProjectionService.project(@filtered_sorted_paginated_ids, procedure_presentation.displayed_fields) @projected_dossiers = DossierProjectionService.project(@filtered_sorted_paginated_ids, procedure_presentation.displayed_columns)
@disable_checkbox_all = @projected_dossiers.all? { _1.batch_operation_id.present? } @disable_checkbox_all = @projected_dossiers.all? { _1.batch_operation_id.present? }
@batch_operations = BatchOperation.joins(:groupe_instructeurs) @batch_operations = BatchOperation.joins(:groupe_instructeurs)
@ -133,9 +133,9 @@ module Instructeurs
end end
def update_displayed_fields def update_displayed_fields
values = (params['values'].presence || []).reject(&:empty?) ids = (params['values'].presence || []).reject(&:empty?)
procedure_presentation.update_displayed_fields(values) procedure_presentation.update!(displayed_columns: ids)
redirect_back(fallback_location: instructeur_procedure_url(procedure)) redirect_back(fallback_location: instructeur_procedure_url(procedure))
end end

View file

@ -3,10 +3,14 @@
class RechercheController < ApplicationController class RechercheController < ApplicationController
before_action :authenticate_logged_user! before_action :authenticate_logged_user!
ITEMS_PER_PAGE = 25 ITEMS_PER_PAGE = 25
# the columns are generally procedure specific
# but in the search context, we are looking for dossiers from multiple procedures
# so we are faking the columns with a random procedure_id
PROJECTIONS = [ PROJECTIONS = [
{ "table" => 'procedure', "column" => 'libelle' }, Column.new(procedure_id: 666, table: 'procedure', column: 'libelle'),
{ "table" => 'user', "column" => 'email' }, Column.new(procedure_id: 666, table: 'user', column: 'email'),
{ "table" => 'procedure', "column" => 'procedure_id' } Column.new(procedure_id: 666, table: 'procedure', column: 'procedure_id')
] ]
def nav_bar_profile def nav_bar_profile

View file

@ -21,6 +21,8 @@ class ProcedurePresentation < ApplicationRecord
validate :check_filters_max_length validate :check_filters_max_length
validate :check_filters_max_integer validate :check_filters_max_integer
attribute :displayed_columns, :column, array: true
attribute :sorted_column, :sorted_column attribute :sorted_column, :sorted_column
def sorted_column = super || procedure.default_sorted_column # Dummy override to set default value def sorted_column = super || procedure.default_sorted_column # Dummy override to set default value
@ -42,7 +44,7 @@ class ProcedurePresentation < ApplicationRecord
def displayed_fields_for_headers def displayed_fields_for_headers
[ [
Column.new(procedure_id: procedure.id, table: 'self', column: 'id', classname: 'number-col'), Column.new(procedure_id: procedure.id, table: 'self', column: 'id', classname: 'number-col'),
*displayed_fields.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id: procedure.id)) }, *displayed_columns,
Column.new(procedure_id: procedure.id, table: 'self', column: 'state', classname: 'state-col'), Column.new(procedure_id: procedure.id, table: 'self', column: 'state', classname: 'state-col'),
*procedure.sva_svr_columns *procedure.sva_svr_columns
] ]

View file

@ -40,8 +40,10 @@ class DossierProjectionService
# Those hashes are needed because: # Those hashes are needed because:
# - the order of the intermediary query results are unknown # - the order of the intermediary query results are unknown
# - some values can be missing (if a revision added or removed them) # - some values can be missing (if a revision added or removed them)
def self.project(dossiers_ids, fields) def self.project(dossiers_ids, columns)
fields = fields.deep_dup fields = columns.map { |c| { TABLE => c.table, COLUMN => c.column } }
champ_value = champ_value_formatter(dossiers_ids, fields)
state_field = { TABLE => 'self', COLUMN => 'state' } state_field = { TABLE => 'self', COLUMN => 'state' }
archived_field = { TABLE => 'self', COLUMN => 'archived' } archived_field = { TABLE => 'self', COLUMN => 'archived' }
batch_operation_field = { TABLE => 'self', COLUMN => 'batch_operation_id' } batch_operation_field = { TABLE => 'self', COLUMN => 'batch_operation_id' }
@ -53,7 +55,7 @@ class DossierProjectionService
individual_last_name = { TABLE => 'individual', COLUMN => 'nom' } individual_last_name = { TABLE => 'individual', COLUMN => 'nom' }
sva_svr_decision_on_field = { TABLE => 'self', COLUMN => 'sva_svr_decision_on' } sva_svr_decision_on_field = { TABLE => 'self', COLUMN => 'sva_svr_decision_on' }
dossier_corrections = { TABLE => 'dossier_corrections', COLUMN => 'resolved_at' } dossier_corrections = { TABLE => 'dossier_corrections', COLUMN => 'resolved_at' }
champ_value = champ_value_formatter(dossiers_ids, fields)
([state_field, archived_field, sva_svr_decision_on_field, hidden_by_user_at_field, hidden_by_administration_at_field, hidden_by_reason_field, for_tiers_field, individual_first_name, individual_last_name, batch_operation_field, dossier_corrections] + fields) ([state_field, archived_field, sva_svr_decision_on_field, hidden_by_user_at_field, hidden_by_administration_at_field, hidden_by_reason_field, for_tiers_field, individual_first_name, individual_last_name, batch_operation_field, dossier_corrections] + fields)
.each { |f| f[:id_value_h] = {} } .each { |f| f[:id_value_h] = {} }
.group_by { |f| f[TABLE] } # one query per table .group_by { |f| f[TABLE] } # one query per table

View file

@ -38,10 +38,7 @@ describe '20240920130741_migrate_procedure_presentation_to_columns.rake' do
it 'populates the columns' do it 'populates the columns' do
procedure_id = procedure.id procedure_id = procedure.id
expect(procedure_presentation.displayed_columns).to eq([ expect(procedure_presentation.displayed_columns.map(&:label)).to eq(["Raison sociale", procedure.active_revision.types_de_champ.first.libelle])
{ "procedure_id" => procedure_id, "column_id" => "etablissement/entreprise_raison_sociale" },
{ "procedure_id" => procedure_id, "column_id" => "type_de_champ/#{stable_id}" }
])
order, column_id = procedure_presentation order, column_id = procedure_presentation
.sorted_column .sorted_column

View file

@ -167,20 +167,6 @@ describe Instructeur, type: :model do
it { expect(errors).to be_nil } it { expect(errors).to be_nil }
end end
context 'with invalid presentation' do
let(:procedure_id) { procedure.id }
before do
pp = ProcedurePresentation.create(assign_to: procedure_assign, displayed_fields: [{ 'table' => 'invalid', 'column' => 'random' }])
pp.save(:validate => false)
end
it 'recreates a valid prsentation' do
expect(procedure_presentation).to be_persisted
end
it { expect(procedure_presentation).to be_valid }
it { expect(errors).to be_present }
end
context 'with default presentation' do context 'with default presentation' do
let(:procedure_id) { procedure_2.id } let(:procedure_id) { procedure_2.id }

View file

@ -40,8 +40,11 @@ describe ProcedurePresentation do
describe 'validation' do describe 'validation' do
it { expect(build(:procedure_presentation)).to be_valid } it { expect(build(:procedure_presentation)).to be_valid }
context 'of displayed fields' do context 'of displayed columns' do
it { expect(build(:procedure_presentation, displayed_fields: [{ table: "user", column: "reset_password_token", "order" => "asc" }])).to be_invalid } it do
pp = build(:procedure_presentation, displayed_columns: [{ table: "user", column: "reset_password_token", procedure_id: }])
expect { pp.displayed_columns }.to raise_error(ActiveRecord::RecordNotFound)
end
end end
context 'of filters' do context 'of filters' do
@ -795,6 +798,9 @@ describe ProcedurePresentation do
end end
describe '#update_displayed_fields' do describe '#update_displayed_fields' do
let(:en_construction_column) { procedure.find_column(label: 'En construction le') }
let(:mise_a_jour_column) { procedure.find_column(label: 'Mis à jour le') }
let(:procedure_presentation) do let(:procedure_presentation) do
create(:procedure_presentation, assign_to:).tap do |pp| create(:procedure_presentation, assign_to:).tap do |pp|
pp.update(sorted_column: SortedColumn.new(column: procedure.find_column(label: 'Demandeur'), order: 'desc')) pp.update(sorted_column: SortedColumn.new(column: procedure.find_column(label: 'Demandeur'), order: 'desc'))
@ -802,24 +808,19 @@ describe ProcedurePresentation do
end end
subject do subject do
procedure_presentation.update_displayed_fields([ procedure_presentation.update(displayed_columns: [
procedure.find_column(label: 'En construction le').id, en_construction_column.id, mise_a_jour_column.id
procedure.find_column(label: 'Mis à jour le').id
]) ])
end end
it 'should update displayed_fields' do it 'should update displayed_fields' do
expect(procedure_presentation.displayed_columns).to eq([]) expect(procedure_presentation.displayed_columns).to eq(procedure.default_displayed_columns)
subject subject
expect(procedure_presentation.displayed_columns).to eq([ expect(procedure_presentation.displayed_columns).to eq([
{ "column_id" => "self/en_construction_at", "procedure_id" => procedure.id }, en_construction_column, mise_a_jour_column
{ "column_id" => "self/updated_at", "procedure_id" => procedure.id }
]) ])
expect(procedure_presentation.sorted_column).to eq(procedure.default_sorted_column)
expect(procedure_presentation.sorted_column.order).to eq('desc')
end end
end end
end end

View file

@ -2,7 +2,7 @@
describe DossierProjectionService do describe DossierProjectionService do
describe '#project' do describe '#project' do
subject { described_class.project(dossiers_ids, fields) } subject { described_class.project(dossiers_ids, columns) }
context 'with multiple dossier' do context 'with multiple dossier' do
let!(:procedure) { create(:procedure, types_de_champ_public: [{}, { type: :linked_drop_down_list }]) } let!(:procedure) { create(:procedure, types_de_champ_public: [{}, { type: :linked_drop_down_list }]) }
@ -11,12 +11,9 @@ describe DossierProjectionService do
let!(:dossier_3) { create(:dossier, :en_instruction, procedure: procedure) } let!(:dossier_3) { create(:dossier, :en_instruction, procedure: procedure) }
let(:dossiers_ids) { [dossier_3.id, dossier_1.id, dossier_2.id] } let(:dossiers_ids) { [dossier_3.id, dossier_1.id, dossier_2.id] }
let(:fields) do let(:columns) do
procedure.active_revision.types_de_champ_public.map do |type_de_champ| procedure.active_revision.types_de_champ_public.map do |type_de_champ|
{ procedure.find_column(label: type_de_champ.libelle)
"table" => "type_de_champ",
"column" => type_de_champ.stable_id.to_s
}
end end
end end
@ -55,12 +52,9 @@ describe DossierProjectionService do
let!(:dossier) { create(:dossier, procedure:) } let!(:dossier) { create(:dossier, procedure:) }
let(:dossiers_ids) { [dossier.id] } let(:dossiers_ids) { [dossier.id] }
let(:fields) do let(:columns) do
[ [
{ procedure.find_column(label: procedure.active_revision.types_de_champ_public[0].libelle)
"table" => "type_de_champ",
"column" => procedure.active_revision.types_de_champ_public[0].stable_id.to_s
}
] ]
end end
@ -78,38 +72,37 @@ describe DossierProjectionService do
end end
context 'attributes by attributes' do context 'attributes by attributes' do
let(:fields) { [{ "table" => table, "column" => column }] } let(:procedure) { create(:procedure) }
let(:columns) { [procedure.find_column(label:)] }
let(:dossiers_ids) { [dossier.id] } let(:dossiers_ids) { [dossier.id] }
subject { super()[0].columns[0] } subject { super()[0].columns[0] }
context 'for self table' do context 'for self table' do
let(:table) { 'self' }
context 'for created_at column' do context 'for created_at column' do
let(:column) { 'created_at' } let(:label) { 'Créé le' }
let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier) } } let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier, procedure:) } }
it { is_expected.to eq('22/03/1992') } it { is_expected.to eq('22/03/1992') }
end end
context 'for en_construction_at column' do context 'for en_construction_at column' do
let(:column) { 'en_construction_at' } let(:label) { 'En construction le' }
let(:dossier) { create(:dossier, :en_construction, en_construction_at: Time.zone.local(2018, 10, 17)) } let(:dossier) { create(:dossier, :en_construction, en_construction_at: Time.zone.local(2018, 10, 17), procedure:) }
it { is_expected.to eq('17/10/2018') } it { is_expected.to eq('17/10/2018') }
end end
context 'for depose_at column' do context 'for depose_at column' do
let(:column) { 'depose_at' } let(:label) { 'Déposé le' }
let(:dossier) { create(:dossier, :en_construction, depose_at: Time.zone.local(2018, 10, 17)) } let(:dossier) { create(:dossier, :en_construction, depose_at: Time.zone.local(2018, 10, 17), procedure:) }
it { is_expected.to eq('17/10/2018') } it { is_expected.to eq('17/10/2018') }
end end
context 'for updated_at column' do context 'for updated_at column' do
let(:column) { 'updated_at' } let(:label) { 'Mis à jour le' }
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, procedure:) }
before { dossier.touch(time: Time.zone.local(2018, 9, 25)) } before { dossier.touch(time: Time.zone.local(2018, 9, 25)) }
@ -118,61 +111,56 @@ describe DossierProjectionService do
end end
context 'for user table' do context 'for user table' do
let(:table) { 'user' } let(:label) { 'Demandeur' }
let(:column) { 'email' }
let(:dossier) { create(:dossier, user: create(:user, email: 'bla@yopmail.com')) } let(:dossier) { create(:dossier, user: create(:user, email: 'bla@yopmail.com'), procedure:) }
it { is_expected.to eq('bla@yopmail.com') } it { is_expected.to eq('bla@yopmail.com') }
end end
context 'for individual table' do context 'for individual table' do
let(:table) { 'individual' }
let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) } let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) }
let(:dossier) { create(:dossier, procedure: procedure, individual: build(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) } let(:dossier) { create(:dossier, procedure:, individual: build(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) }
context 'for prenom column' do context 'for prenom column' do
let(:column) { 'prenom' } let(:label) { 'Prénom' }
it { is_expected.to eq('Jacques') } it { is_expected.to eq('Jacques') }
end end
context 'for nom column' do context 'for nom column' do
let(:column) { 'nom' } let(:label) { 'Nom' }
it { is_expected.to eq('Martin') } it { is_expected.to eq('Martin') }
end end
context 'for gender column' do context 'for gender column' do
let(:column) { 'gender' } let(:label) { 'Civilité' }
it { is_expected.to eq('M.') } it { is_expected.to eq('M.') }
end end
end end
context 'for etablissement table' do context 'for etablissement table' do
let(:table) { 'etablissement' } let(:label) { 'Code postal' }
let(:column) { 'code_postal' } # All other columns work the same, no extra test required
let!(:dossier) { create(:dossier, etablissement: create(:etablissement, code_postal: '75008')) } let!(:dossier) { create(:dossier, procedure:, etablissement: create(:etablissement, code_postal: '75008')) }
it { is_expected.to eq('75008') } it { is_expected.to eq('75008') }
end end
context 'for groupe_instructeur table' do context 'for groupe_instructeur table' do
let(:table) { 'groupe_instructeur' } let(:label) { 'Groupe instructeur' }
let(:column) { 'label' }
let!(:dossier) { create(:dossier) } let!(:dossier) { create(:dossier, procedure:) }
it { is_expected.to eq('défaut') } it { is_expected.to eq('défaut') }
end end
context 'for followers_instructeurs table' do context 'for followers_instructeurs table' do
let(:table) { 'followers_instructeurs' } let(:label) { 'Email instructeur' }
let(:column) { 'email' }
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, procedure:) }
let!(:follow1) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'b@host.fr')) } let!(:follow1) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'b@host.fr')) }
let!(:follow2) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'a@host.fr')) } let!(:follow2) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'a@host.fr')) }
let!(:follow3) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'c@host.fr')) } let!(:follow3) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'c@host.fr')) }
@ -181,19 +169,21 @@ describe DossierProjectionService do
end end
context 'for type_de_champ table' do context 'for type_de_champ table' do
let(:table) { 'type_de_champ' } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text }]) }
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, procedure:) }
let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle }
before { dossier.project_champs_public.first.update(value: 'kale') } before do
dossier.project_champs_public.first.update(value: 'kale')
end
it { is_expected.to eq('kale') } it { is_expected.to eq('kale') }
end end
context 'for type_de_champ_private table' do context 'for type_de_champ_private table' do
let(:table) { 'type_de_champ_private' } let(:procedure) { create(:procedure, types_de_champ_private: [{ type: :text }]) }
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, procedure:) }
let(:column) { dossier.procedure.active_revision.types_de_champ_private.first.stable_id.to_s } let(:label) { dossier.procedure.active_revision.types_de_champ_private.first.libelle }
before { dossier.project_champs_private.first.update(value: 'quinoa') } before { dossier.project_champs_private.first.update(value: 'quinoa') }
@ -201,10 +191,9 @@ describe DossierProjectionService do
end end
context 'for type_de_champ table and value to.s' do context 'for type_de_champ table and value to.s' do
let(:table) { 'type_de_champ' }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }]) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }]) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure:) }
let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle }
before { dossier.project_champs_public.first.update(value: 'true') } before { dossier.project_champs_public.first.update(value: 'true') }
@ -212,10 +201,9 @@ describe DossierProjectionService do
end end
context 'for type_de_champ table and value to.s which needs data field' do context 'for type_de_champ table and value to.s which needs data field' do
let(:table) { 'type_de_champ' }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :address }]) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :address }]) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure:) }
let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle }
before { dossier.project_champs_public.first.update(value: '18 a la bonne rue', data: { 'label' => '18 a la bonne rue', 'departement' => 'd' }) } before { dossier.project_champs_public.first.update(value: '18 a la bonne rue', data: { 'label' => '18 a la bonne rue', 'departement' => 'd' }) }
@ -223,10 +211,9 @@ describe DossierProjectionService do
end end
context 'for type_de_champ table: type_de_champ pays which needs external_id field' do context 'for type_de_champ table: type_de_champ pays which needs external_id field' do
let(:table) { 'type_de_champ' }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :pays }]) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :pays }]) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure:) }
let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle }
around do |example| around do |example|
I18n.with_locale(:fr) do I18n.with_locale(:fr) do
@ -254,8 +241,10 @@ describe DossierProjectionService do
context 'for dossier corrections table' do context 'for dossier corrections table' do
let(:table) { 'dossier_corrections' } let(:table) { 'dossier_corrections' }
let(:column) { 'resolved_at' } let(:column) { 'resolved_at' }
let(:dossier) { create(:dossier, :en_construction) } let(:procedure) { create(:procedure) }
subject { described_class.project(dossiers_ids, fields)[0] } let(:columns) { [Column.new(procedure_id: procedure.id, table:, column:)] } # should somehow be present in column concern
let(:dossier) { create(:dossier, :en_construction, procedure:) }
subject { described_class.project(dossiers_ids, columns)[0] }
context "when dossier has pending correction" do context "when dossier has pending correction" do
before { create(:dossier_correction, dossier:) } before { create(:dossier_correction, dossier:) }