From 81df0332828e69209f5a058ae8878dedf64b53ca Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Thu, 26 Jan 2023 17:57:57 +0100 Subject: [PATCH 001/202] First draft repeatable --- app/models/champ.rb | 2 ++ .../concerns/dossier_prefillable_concern.rb | 2 +- app/models/prefill_params.rb | 25 +++++++++++++++---- app/models/type_de_champ.rb | 3 ++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/models/champ.rb b/app/models/champ.rb index 21e170978..b53e0e710 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -72,6 +72,8 @@ class Champ < ApplicationRecord :refresh_after_update?, to: :type_de_champ + delegate :revision, to: :dossier, prefix: true + scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) } scope :public_only, -> { where(private: false) } scope :private_only, -> { where(private: true) } diff --git a/app/models/concerns/dossier_prefillable_concern.rb b/app/models/concerns/dossier_prefillable_concern.rb index 245527e6f..3ecbe7a04 100644 --- a/app/models/concerns/dossier_prefillable_concern.rb +++ b/app/models/concerns/dossier_prefillable_concern.rb @@ -7,7 +7,7 @@ module DossierPrefillableConcern return unless champs_public_attributes.any? attr = { prefilled: true } - attr[:champs_public_attributes] = champs_public_attributes.map { |h| h.merge(prefilled: true) } + attr[:champs_public_all_attributes] = champs_public_attributes.map { |h| h.merge(prefilled: true) } assign_attributes(attr) save(validate: false) diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index e4ded1975..68f717df2 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -5,7 +5,7 @@ class PrefillParams end def to_a - build_prefill_values.filter(&:prefillable?).map(&:to_h) + build_prefill_values.filter(&:prefillable?).map(&:to_h).flatten end private @@ -55,10 +55,14 @@ class PrefillParams end def to_h - { - id: champ.id, - value: value - } + if champ.type_champ == TypeDeChamp.type_champs.fetch(:repetition) + repeatable_hashes + else + { + id: champ.id, + value: value + } + end end private @@ -69,5 +73,16 @@ class PrefillParams champ.value = value champ.valid?(:prefill) end + + def repeatable_hashes + value.map.with_index do |repetition, index| + row = champ.rows[index] || champ.add_row(champ.dossier_revision) + JSON.parse(repetition).map do |key, value| + id = row.find { |champ| champ.libelle == key }.id + { id: id, value: value } + end + rescue JSON::ParserError + end.flatten + end end end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 4c1a7cb71..16bfb5cbf 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -269,7 +269,8 @@ class TypeDeChamp < ApplicationRecord TypeDeChamp.type_champs.fetch(:yes_no), TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:drop_down_list), - TypeDeChamp.type_champs.fetch(:regions) + TypeDeChamp.type_champs.fetch(:regions), + TypeDeChamp.type_champs.fetch(:repetition), ]) end From 20ba96ba3c2c96c7493af2b1fa2c007435ff58df Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 31 Jan 2023 14:54:23 +0100 Subject: [PATCH 002/202] POST working with prefill query example for repeating fields --- .../api/public/v1/dossiers_controller.rb | 1 + .../concerns/dossier_prefillable_concern.rb | 2 +- app/models/prefill_description.rb | 8 +++- app/models/prefill_params.rb | 8 +++- app/models/type_de_champ.rb | 7 ++++ .../prefill_repetition_type_de_champ.rb | 37 +++++++++++++++++++ .../types_de_champ/prefill_type_de_champ.rb | 14 ++++++- .../_types_de_champs.html.haml | 7 +--- config/locales/en.yml | 1 + config/locales/fr.yml | 1 + 10 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 app/models/types_de_champ/prefill_repetition_type_de_champ.rb diff --git a/app/controllers/api/public/v1/dossiers_controller.rb b/app/controllers/api/public/v1/dossiers_controller.rb index 2e770d6ce..051bc9aac 100644 --- a/app/controllers/api/public/v1/dossiers_controller.rb +++ b/app/controllers/api/public/v1/dossiers_controller.rb @@ -2,6 +2,7 @@ class API::Public::V1::DossiersController < API::Public::V1::BaseController before_action :retrieve_procedure def create + byebug dossier = Dossier.new( revision: @procedure.active_revision, groupe_instructeur: @procedure.defaut_groupe_instructeur_for_new_dossier, diff --git a/app/models/concerns/dossier_prefillable_concern.rb b/app/models/concerns/dossier_prefillable_concern.rb index 3ecbe7a04..7537a7357 100644 --- a/app/models/concerns/dossier_prefillable_concern.rb +++ b/app/models/concerns/dossier_prefillable_concern.rb @@ -7,7 +7,7 @@ module DossierPrefillableConcern return unless champs_public_attributes.any? attr = { prefilled: true } - attr[:champs_public_all_attributes] = champs_public_attributes.map { |h| h.merge(prefilled: true) } + attr[:champs_public_all_attributes] = champs_public_attributes.compact.map { |h| h.merge(prefilled: true) } assign_attributes(attr) save(validate: false) diff --git a/app/models/prefill_description.rb b/app/models/prefill_description.rb index a09d89c80..2198f5412 100644 --- a/app/models/prefill_description.rb +++ b/app/models/prefill_description.rb @@ -50,7 +50,13 @@ class PrefillDescription < SimpleDelegator end def prefilled_champs_for_query - prefilled_champs.map { |type_de_champ| "\"champ_#{type_de_champ.to_typed_id}\": \"#{type_de_champ.example_value}\"" } .join(', ') + prefilled_champs.map do |type_de_champ| + if type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:repetition) + "\"champ_#{type_de_champ.to_typed_id}\": #{type_de_champ.example_value}" + else + "\"champ_#{type_de_champ.to_typed_id}\": \"#{type_de_champ.example_value}\"" + end + end.join(', ') end def active_fillable_public_types_de_champ diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 68f717df2..177aef1b4 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -11,6 +11,9 @@ class PrefillParams private def build_prefill_values + byebug + # nop : "[{\"txt\":\"Texte court\", \"nb\":\"3.14\", \"Test dropdown\":\"Premier choix\", \"régio\":\"53\"}, {\"txt\":\"Texte court\", \"nb\":\"3.14\", \"Test dropdown\":\"Premier choix\", \"régio\":\"53\"}]" + # {"champ_Q2hhbXAtNDI="=>["{\"txt\":\"abc\", \"nb\":\"1,12\"}", "{\"txt\":\"def\", \"nb\":\"2,12\"}"]} value_by_stable_id = @params .map { |prefixed_typed_id, value| [stable_id_from_typed_id(prefixed_typed_id), value] } .filter { |stable_id, value| stable_id.present? && value.present? } @@ -78,11 +81,12 @@ class PrefillParams value.map.with_index do |repetition, index| row = champ.rows[index] || champ.add_row(champ.dossier_revision) JSON.parse(repetition).map do |key, value| - id = row.find { |champ| champ.libelle == key }.id + id = row.find { |champ| champ.libelle == key }&.id + next unless id { id: id, value: value } end rescue JSON::ParserError - end.flatten + end.compact end end end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 16bfb5cbf..c52c2ab08 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -128,6 +128,7 @@ class TypeDeChamp < ApplicationRecord has_one :procedure, through: :revision delegate :estimated_fill_duration, :estimated_read_duration, :tags_for_template, :libelle_for_export, to: :dynamic_type + delegate :active_revision, to: :procedure, prefix: true class WithIndifferentAccess def self.load(options) @@ -486,6 +487,12 @@ class TypeDeChamp < ApplicationRecord end end + def active_revision_type_de_champ + procedure_active_revision.revision_types_de_champ_public.find do |rtc| + rtc.type_de_champ_id == id + end + end + private DEFAULT_EMPTY = [''] diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb new file mode 100644 index 000000000..01112ec58 --- /dev/null +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -0,0 +1,37 @@ +class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + include ActionView::Helpers::UrlHelper + + def possible_values + prefillable_subchamps.map do |prefill_type_de_champ| + if prefill_type_de_champ.too_many_possible_values? + link = link_to "Voir toutes les valeurs possibles", Rails.application.routes.url_helpers.prefill_type_de_champ_path("piece-jointe", self) + "#{prefill_type_de_champ.libelle}: #{link}" + else + "#{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values_sentence}" + end + end + end + + def possible_values_sentence + "#{I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")}
#{possible_values.join("
")}".html_safe + end + + def example_value + [row_values_format, row_values_format].map { |row| row.to_s.gsub("=>", ":") } + end + + private + + def row_values_format + @row_example_value ||= + prefillable_subchamps.map do |prefill_type_de_champ| + [prefill_type_de_champ.libelle, prefill_type_de_champ.example_value.to_s] + end.to_h + end + + def prefillable_subchamps + return [] unless active_revision_type_de_champ + + TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ).filter(&:prefillable?)) + end +end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 296c04c48..6e481cd88 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -9,6 +9,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator TypesDeChamp::PrefillPaysTypeDeChamp.new(type_de_champ) when TypeDeChamp.type_champs.fetch(:regions) TypesDeChamp::PrefillRegionTypeDeChamp.new(type_de_champ) + when TypeDeChamp.type_champs.fetch(:repetition) + TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ) else new(type_de_champ) end @@ -19,7 +21,9 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator end def possible_values - [] + return [] unless prefillable? + + [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] end def example_value @@ -31,4 +35,12 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator def too_many_possible_values? possible_values.count > POSSIBLE_VALUES_THRESHOLD end + + def possible_values_sentence + if too_many_possible_values? + I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe + else + possible_values.to_sentence + end + end end diff --git a/app/views/prefill_descriptions/_types_de_champs.html.haml b/app/views/prefill_descriptions/_types_de_champs.html.haml index bab61b198..01db2707d 100644 --- a/app/views/prefill_descriptions/_types_de_champs.html.haml +++ b/app/views/prefill_descriptions/_types_de_champs.html.haml @@ -39,13 +39,10 @@ %th = t("views.prefill_descriptions.edit.possible_values.title") %td - - if I18n.exists?("views.prefill_descriptions.edit.possible_values.#{type_de_champ.type_champ}_html") - = t("views.prefill_descriptions.edit.possible_values.#{type_de_champ.type_champ}_html") - %br + = type_de_champ.possible_values_sentence + %br - if type_de_champ.too_many_possible_values? = link_to "Voir toutes les valeurs possibles", prefill_type_de_champ_path(prefill_description.path, type_de_champ) - - else - = type_de_champ.possible_values.to_sentence %tr{ class: prefillable ? "" : "fr-text-mention--grey" } %th = t("views.prefill_descriptions.edit.examples.title") diff --git a/config/locales/en.yml b/config/locales/en.yml index 916baa59f..6b70d5d3c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -128,6 +128,7 @@ en: date_html: ISO8601 date datetime_html: ISO8601 datetime drop_down_list_other_html: Any value + repetition_html: A array of hashes with possible values for each field of the repetition. examples: title: Example text: Short text diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 70e562cc2..e69c9df4c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -119,6 +119,7 @@ fr: datetime_html: Datetime au format ISO8601 date_html: Date au format ISO8601 drop_down_list_other_html: Toute valeur + repetition_html: Un array de hash avec les valeurs possibles pour chaque champ de la répétition. examples: title: Exemple text: Texte court From f91cc05d953f8a738b3034f72569ec070338d3ba Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 31 Jan 2023 16:24:32 +0100 Subject: [PATCH 003/202] Small fix link to prefill type de champ --- app/controllers/api/public/v1/dossiers_controller.rb | 1 - app/controllers/prefill_type_de_champs_controller.rb | 2 +- app/models/prefill_params.rb | 3 --- app/models/types_de_champ/prefill_repetition_type_de_champ.rb | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/public/v1/dossiers_controller.rb b/app/controllers/api/public/v1/dossiers_controller.rb index 051bc9aac..2e770d6ce 100644 --- a/app/controllers/api/public/v1/dossiers_controller.rb +++ b/app/controllers/api/public/v1/dossiers_controller.rb @@ -2,7 +2,6 @@ class API::Public::V1::DossiersController < API::Public::V1::BaseController before_action :retrieve_procedure def create - byebug dossier = Dossier.new( revision: @procedure.active_revision, groupe_instructeur: @procedure.defaut_groupe_instructeur_for_new_dossier, diff --git a/app/controllers/prefill_type_de_champs_controller.rb b/app/controllers/prefill_type_de_champs_controller.rb index 22f5f1a54..82817f324 100644 --- a/app/controllers/prefill_type_de_champs_controller.rb +++ b/app/controllers/prefill_type_de_champs_controller.rb @@ -12,6 +12,6 @@ class PrefillTypeDeChampsController < ApplicationController end def set_prefill_type_de_champ - @type_de_champ = TypesDeChamp::PrefillTypeDeChamp.build(@procedure.active_revision.types_de_champ_public.fillable.find(params[:id])) + @type_de_champ = TypesDeChamp::PrefillTypeDeChamp.build(@procedure.active_revision.types_de_champ.fillable.find(params[:id])) end end diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 177aef1b4..6f16c1e2e 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -11,9 +11,6 @@ class PrefillParams private def build_prefill_values - byebug - # nop : "[{\"txt\":\"Texte court\", \"nb\":\"3.14\", \"Test dropdown\":\"Premier choix\", \"régio\":\"53\"}, {\"txt\":\"Texte court\", \"nb\":\"3.14\", \"Test dropdown\":\"Premier choix\", \"régio\":\"53\"}]" - # {"champ_Q2hhbXAtNDI="=>["{\"txt\":\"abc\", \"nb\":\"1,12\"}", "{\"txt\":\"def\", \"nb\":\"2,12\"}"]} value_by_stable_id = @params .map { |prefixed_typed_id, value| [stable_id_from_typed_id(prefixed_typed_id), value] } .filter { |stable_id, value| stable_id.present? && value.present? } diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 01112ec58..cdf435f6e 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -4,7 +4,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh def possible_values prefillable_subchamps.map do |prefill_type_de_champ| if prefill_type_de_champ.too_many_possible_values? - link = link_to "Voir toutes les valeurs possibles", Rails.application.routes.url_helpers.prefill_type_de_champ_path("piece-jointe", self) + link = link_to "Voir toutes les valeurs possibles", Rails.application.routes.url_helpers.prefill_type_de_champ_path("piece-jointe", prefill_type_de_champ) "#{prefill_type_de_champ.libelle}: #{link}" else "#{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values_sentence}" From d7b01255fe8054c2cdc08bbdc0b743e790173612 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 31 Jan 2023 16:39:00 +0100 Subject: [PATCH 004/202] Merge branch 'main' into feature/prefill_repetible --- Gemfile.lock | 2 +- app/assets/stylesheets/auth.scss | 1 - app/assets/stylesheets/contact.scss | 8 + app/assets/stylesheets/forms.scss | 6 + app/assets/stylesheets/instructeur.scss | 4 + app/assets/stylesheets/new_footer.scss | 6 + app/assets/stylesheets/procedure_context.scss | 11 +- .../export_component.html.haml | 40 ++-- app/components/dropdown/menu_component.rb | 50 +++++ .../menu_component/menu_component.html.haml | 15 ++ .../drop_down_other_input_component.html.haml | 4 +- .../attestation_templates_controller.rb | 1 + app/controllers/experts/avis_controller.rb | 2 +- .../instructeurs/dossiers_controller.rb | 2 +- app/controllers/users/dossiers_controller.rb | 6 +- app/javascript/components/shared/hooks.ts | 7 +- .../controllers/application_controller.ts | 5 +- .../controllers/autosave_controller.ts | 9 +- .../controllers/champ_dropdown_controller.ts | 1 + .../controllers/menu_button_controller.ts | 61 ++--- app/lib/active_storage/downloadable_file.rb | 6 +- app/models/avis.rb | 1 + app/models/champ.rb | 4 +- app/models/champs/date_champ.rb | 14 +- app/models/concerns/dossier_rebase_concern.rb | 48 ++-- app/models/dossier.rb | 2 +- app/models/dossier_transfer.rb | 3 + app/models/procedure_presentation.rb | 7 +- app/models/type_de_champ.rb | 2 +- app/services/pieces_justificatives_service.rb | 12 +- app/services/procedure_archive_service.rb | 2 +- app/services/procedure_export_service.rb | 2 +- .../procedures/_procedures_list.html.haml | 69 +++--- app/views/experts/avis/_header.html.haml | 10 +- .../dossiers/_header_actions.html.haml | 42 ++-- .../dossiers/_state_button.html.haml | 211 ++++++++---------- .../procedures/_dossier_actions.html.haml | 55 ++--- .../_dossiers_filter_dropdown.html.haml | 8 +- .../instructeurs/procedures/show.html.haml | 7 +- app/views/invites/_dropdown.html.haml | 7 +- .../layouts/_search_dossiers_form.html.haml | 10 +- .../layouts/commencer/_no_procedure.html.haml | 17 +- .../layouts/mailers/_service_footer.html.haml | 3 - .../_types_de_champs.html.haml | 2 +- app/views/recherche/index.html.haml | 28 +-- .../help/_help_dropdown_dossier.html.haml | 25 ++- .../help/_help_dropdown_instructeur.html.haml | 13 +- .../help/_help_dropdown_procedure.html.haml | 15 +- .../help/dropdown_items/_email_item.html.haml | 4 +- .../help/dropdown_items/_faq_item.html.haml | 16 +- .../dropdown_items/_messagerie_item.html.haml | 11 +- .../dropdown_items/_service_item.html.haml | 29 ++- app/views/support/index.html.haml | 7 +- app/views/users/_procedure_footer.html.haml | 7 +- .../users/dossiers/_dossier_actions.html.haml | 82 +++---- .../dossiers/_identity_dropdown.html.haml | 6 +- app/views/users/dossiers/demande.html.haml | 2 +- .../users/dossiers/show/_header.html.haml | 2 +- .../dossiers/show/_print_dossier.html.haml | 7 +- .../users/dossiers/update.turbo_stream.haml | 2 +- config/locales/en.yml | 6 +- config/locales/fr.yml | 6 +- config/locales/views/support/en.yml | 28 +-- config/locales/views/support/fr.yml | 32 ++- .../20230126145329_add_reminded_at_to_avis.rb | 5 + db/schema.rb | 3 +- lib/tasks/benchmarks.rake | 23 +- ...6_fix_dossier_transfer_with_uppercase.rake | 20 ++ .../instructeurs/dossiers_controller_spec.rb | 4 + .../users/transfers_controller_spec.rb | 5 + .../active_storage/downloadable_file_spec.rb | 2 +- spec/models/champs/date_champ_spec.rb | 8 +- .../concern/tags_substitution_concern_spec.rb | 9 +- spec/models/dossier_rebase_concern_spec.rb | 9 +- spec/models/dossier_spec.rb | 53 +++++ spec/models/procedure_presentation_spec.rb | 11 + spec/models/procedure_revision_spec.rb | 17 ++ .../pieces_justificatives_service_spec.rb | 17 +- .../administrateurs/procedure_cloning_spec.rb | 2 +- spec/system/experts/expert_spec.rb | 1 - spec/system/users/dropdown_spec.rb | 15 ++ .../dossiers/show/_header.html.haml_spec.rb | 10 +- 82 files changed, 764 insertions(+), 563 deletions(-) create mode 100644 app/components/dropdown/menu_component.rb create mode 100644 app/components/dropdown/menu_component/menu_component.html.haml create mode 100644 db/migrate/20230126145329_add_reminded_at_to_avis.rb create mode 100644 lib/tasks/deployment/20230131132616_fix_dossier_transfer_with_uppercase.rake diff --git a/Gemfile.lock b/Gemfile.lock index 557559d7f..2463d2b2a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -635,7 +635,7 @@ GEM nokogiri (>= 1.6.2) rexml xmlenc (>= 0.7.1) - sanitize (6.0.0) + sanitize (6.0.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) sanitize-url (0.1.4) diff --git a/app/assets/stylesheets/auth.scss b/app/assets/stylesheets/auth.scss index 57a11d5f2..a7f8f4e4b 100644 --- a/app/assets/stylesheets/auth.scss +++ b/app/assets/stylesheets/auth.scss @@ -11,7 +11,6 @@ // The procedure description can still be read from the /commencer // pages. @media (max-width: $two-columns-breakpoint) { - .procedure-preview, .agent-intro { display: none; } diff --git a/app/assets/stylesheets/contact.scss b/app/assets/stylesheets/contact.scss index 3e6cd4496..0da338204 100644 --- a/app/assets/stylesheets/contact.scss +++ b/app/assets/stylesheets/contact.scss @@ -9,6 +9,14 @@ $contact-padding: $default-space * 2; padding-bottom: $contact-padding; } + .recommandations { + p { + font-size: 1.5rem; + font-weight: bold; + line-height: 1.5rem; + } + } + ul { margin-bottom: $default-space; } diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss index 0a0cb8154..b73872b55 100644 --- a/app/assets/stylesheets/forms.scss +++ b/app/assets/stylesheets/forms.scss @@ -163,6 +163,12 @@ } } + .drop_down_other { // scss-lint:disable SelectorFormat + label { + font-weight: normal; + } + } + input[type=text], input[type=email], input[type=password], diff --git a/app/assets/stylesheets/instructeur.scss b/app/assets/stylesheets/instructeur.scss index e1737651e..9227d5063 100644 --- a/app/assets/stylesheets/instructeur.scss +++ b/app/assets/stylesheets/instructeur.scss @@ -44,6 +44,10 @@ position: relative; } +.dropdown-export .dropdown-content { + width: 450px; +} + .print-menu { display: none; position: absolute; diff --git a/app/assets/stylesheets/new_footer.scss b/app/assets/stylesheets/new_footer.scss index 53c69617f..68cfeffba 100644 --- a/app/assets/stylesheets/new_footer.scss +++ b/app/assets/stylesheets/new_footer.scss @@ -18,3 +18,9 @@ width: 9rem; } } + +.fr-footer__top-link p { + margin-bottom: 0; + font-size: 0.75rem; + line-height: 1.25rem; +} diff --git a/app/assets/stylesheets/procedure_context.scss b/app/assets/stylesheets/procedure_context.scss index e734c5a31..ee5a96010 100644 --- a/app/assets/stylesheets/procedure_context.scss +++ b/app/assets/stylesheets/procedure_context.scss @@ -17,7 +17,8 @@ $procedure-description-line-height: 22px; } .simple { - font-size: 24px; + margin-bottom: 0.2rem; + font-size: 1.5rem; color: $blue-france-500; font-weight: bold; } @@ -143,6 +144,14 @@ $procedure-description-line-height: 22px; } } +.no-procedure-presentation { + margin-bottom: 1.6rem; + + p { + margin: 0; + } +} + .procedure-context-content { @media (max-width: $procedure-context-breakpoint) { input[type=submit] { diff --git a/app/components/dossiers/export_component/export_component.html.haml b/app/components/dossiers/export_component/export_component.html.haml index 3e986e433..12bc03cd7 100644 --- a/app/components/dossiers/export_component/export_component.html.haml +++ b/app/components/dossiers/export_component/export_component.html.haml @@ -1,21 +1,21 @@ -%span.dropdown{ data: { controller: 'menu-button' } } - %button.fr-btn.fr-btn--sm.dropdown-button{ data: { menu_button_target: 'button' }, class: @class_btn.present? ? @class_btn : 'fr-btn--secondary' } - - if @count.nil? - = t(".download_all") += render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', @class_btn.present? ? @class_btn : 'fr-btn--secondary']}, menu_options: { id: @count.nil? ? "download_menu" : "download_all_menu", class: ['dropdown-export'] }) do |menu| + - menu.with_button_inner_html do + = @count.nil? ? t(".download_all") : t(".download", count: @count) + - exports.each do |item| + - export = item[:export] + + - if export.nil? + - menu.with_item do + = link_to download_export_path(export_format: item[:format]), role: 'menuitem', data: { turbo_method: :post, turbo: true } do + = t(".everything_#{item[:format]}_html") + - elsif export.available? + - menu.with_item do + %div + = link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener", role: 'menuitem' + - if export.old? + = button_to download_export_path(export_format: export.format, force_export: true), refresh_button_options(export).merge(role: 'menuitem') do + .icon.retry - else - = t(".download", count: @count) - .dropdown-content.fade-in-down{ style: 'width: 450px', data: { menu_button_target: 'menu' }, id: @count.nil? ? "download_menu" : "download_all_menu" } - %ul.dropdown-items{ 'data-turbo': 'true' } - - exports.each do |item| - - export = item[:export] - %li - - if export.nil? - = link_to t(".everything_#{item[:format]}_html"), download_export_path(export_format: item[:format]), data: { turbo_method: :post } - - elsif export.available? - = link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener" - - if export.old? - = button_to download_export_path(export_format: export.format, force_export: true), **refresh_button_options(export) do - .icon.retry - - else - %span{ data: poll_controller_options(export) } - = pending_label(export) + - menu.with_item(aria: {disabled:"true"}, class: 'selected') do + %span{ data: poll_controller_options(export) } + = pending_label(export) diff --git a/app/components/dropdown/menu_component.rb b/app/components/dropdown/menu_component.rb new file mode 100644 index 000000000..72457abc1 --- /dev/null +++ b/app/components/dropdown/menu_component.rb @@ -0,0 +1,50 @@ +class Dropdown::MenuComponent < ApplicationComponent + renders_one :button_inner_html + # beware, items elements like button_to/link_to must include role: 'menuitem' for aria reason + renders_many :items, -> (options = {}, &block) do + tag.li(**options.merge(role: 'none'), &block) + end + renders_many :forms + + def initialize(wrapper:, + wrapper_options: {}, + button_options: {}, + menu_options: {}) + @wrapper = wrapper + @wrapper_options = wrapper_options + @button_options = button_options + @menu_options = menu_options + end + + def wrapper_options + @wrapper_options.deep_merge({ + class: wrapper_class_names, + data: { controller: 'menu-button' } + }) + end + + def wrapper_class_names + ['dropdown'] + Array(@wrapper_options[:class]) + end + + def button_id + "#{menu_id}_button" + end + + def menu_id + @menu_options[:id] ||= SecureRandom.uuid + @menu_options[:id] + end + + def menu_role + forms? ? :region : :menu + end + + def menu_class_names + ['dropdown-content'] + Array(@menu_options[:class]) + end + + def button_class_names + ['fr-btn', 'dropdown-button'] + Array(@button_options[:class]) + end +end diff --git a/app/components/dropdown/menu_component/menu_component.html.haml b/app/components/dropdown/menu_component/menu_component.html.haml new file mode 100644 index 000000000..a1f3859ed --- /dev/null +++ b/app/components/dropdown/menu_component/menu_component.html.haml @@ -0,0 +1,15 @@ += content_tag(@wrapper, wrapper_options) do + %button{ class: button_class_names, id: button_id, data: { menu_button_target: 'button' }, "aria-expanded": "false", 'aria-haspopup': 'true', 'aria-controls': menu_id } + = button_inner_html + + %div{ data: { menu_button_target: 'menu' }, id: menu_id, 'aria-labelledby': button_id, role: menu_role, 'tab-index': -1, class: menu_class_names } + + -# the dropdown can be a menu with a list of item + - if items? + %ul.dropdown-items.fr-pl-0{ role: 'none' } + - items.each do |dropdown_item| + = dropdown_item + -# the dropdown can be a menu with forms + - if forms? + - forms.each do |form| + = form diff --git a/app/components/editable_champ/drop_down_other_input_component/drop_down_other_input_component.html.haml b/app/components/editable_champ/drop_down_other_input_component/drop_down_other_input_component.html.haml index e41e1a435..168c008dc 100644 --- a/app/components/editable_champ/drop_down_other_input_component/drop_down_other_input_component.html.haml +++ b/app/components/editable_champ/drop_down_other_input_component/drop_down_other_input_component.html.haml @@ -1,4 +1,4 @@ .drop_down_other{ class: @champ.other_value_present? ? '' : 'hidden' } .notice - %p Veuillez saisir votre autre choix - = @form.text_field :value_other, maxlength: 200, size: nil, disabled: !@champ.other_value_present? + %label{ for: dom_id(@champ, :value_other) } Veuillez saisir votre autre choix + = @form.text_field :value_other, maxlength: 200, size: nil, id: dom_id(@champ, :value_other), disabled: !@champ.other_value_present? diff --git a/app/controllers/administrateurs/attestation_templates_controller.rb b/app/controllers/administrateurs/attestation_templates_controller.rb index a71c64bed..3308eb36d 100644 --- a/app/controllers/administrateurs/attestation_templates_controller.rb +++ b/app/controllers/administrateurs/attestation_templates_controller.rb @@ -8,6 +8,7 @@ module Administrateurs def edit @attestation_template = build_attestation_template + @attestation_template.validate end def update diff --git a/app/controllers/experts/avis_controller.rb b/app/controllers/experts/avis_controller.rb index 4971fc676..2801c3343 100644 --- a/app/controllers/experts/avis_controller.rb +++ b/app/controllers/experts/avis_controller.rb @@ -140,7 +140,7 @@ module Experts end def telecharger_pjs - files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: @dossier.id), true) + files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: @dossier.id)) cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files) zipline(cleaned_files, "dossier-#{@dossier.id}.zip") diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index db2f996f3..09bf7222b 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -235,7 +235,7 @@ module Instructeurs end def telecharger_pjs - files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), true) + files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), with_champs_private: true, include_infos_administration: true) cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files) zipline(cleaned_files, "dossier-#{dossier.id}.zip") diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index c1e5ed2ed..4e2791be8 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -26,11 +26,7 @@ module Users @dossiers_invites = current_user.dossiers_invites.merge(dossiers_visibles) @dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(dossiers) @dossiers_supprimes_definitivement = current_user.deleted_dossiers.order_by_updated_at.page(page) - @dossier_transfers = DossierTransfer - .includes(dossiers: :user) - .with_dossiers - .where(email: current_user.email) - .page(page) + @dossier_transfers = DossierTransfer.for_email(current_user.email).page(page) @statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transfers, @dossiers_close_to_expiration, params[:statut]) end diff --git a/app/javascript/components/shared/hooks.ts b/app/javascript/components/shared/hooks.ts index dd000143f..4bbf579d0 100644 --- a/app/javascript/components/shared/hooks.ts +++ b/app/javascript/components/shared/hooks.ts @@ -17,8 +17,11 @@ export function useDeferredSubmit(input?: HTMLInputElement): { runCallback(); if ( - !Array.from(form.elements).some((e) => - e.hasAttribute('data-direct-upload-url') + !Array.from(form.elements).some( + (e) => + e.hasAttribute('data-direct-upload-url') && + 'value' in e && + e.value != '' ) ) { form.submit(); diff --git a/app/javascript/controllers/application_controller.ts b/app/javascript/controllers/application_controller.ts index 4070dbb03..ada9f6a78 100644 --- a/app/javascript/controllers/application_controller.ts +++ b/app/javascript/controllers/application_controller.ts @@ -65,7 +65,10 @@ export class ApplicationController extends Controller { FOCUS_EVENTS.includes(targetOrEventName) ); } else { - invariant(eventNameOrHandler == 'string', 'event name is required'); + invariant( + typeof eventNameOrHandler == 'string', + 'event name is required' + ); invariant(handler, 'handler is required'); this.onTarget(targetOrEventName, eventNameOrHandler, handler); } diff --git a/app/javascript/controllers/autosave_controller.ts b/app/javascript/controllers/autosave_controller.ts index a71efd9fc..6f4389af6 100644 --- a/app/javascript/controllers/autosave_controller.ts +++ b/app/javascript/controllers/autosave_controller.ts @@ -89,9 +89,12 @@ export class AutosaveController extends ApplicationController { isCheckboxOrRadioInputElement(target) || (!this.saveOnInput && isTextInputElement(target)) ) { - this.enqueueAutosaveRequest(); - - this.showConditionnalSpinner(target); + // Wait next tick so champs having JS can interact + // with form elements before extracting form data. + setTimeout(() => { + this.enqueueAutosaveRequest(); + this.showConditionnalSpinner(target); + }, 0); } } } diff --git a/app/javascript/controllers/champ_dropdown_controller.ts b/app/javascript/controllers/champ_dropdown_controller.ts index 1615439df..b9dbfe1e4 100644 --- a/app/javascript/controllers/champ_dropdown_controller.ts +++ b/app/javascript/controllers/champ_dropdown_controller.ts @@ -34,6 +34,7 @@ export class ChampDropdownController extends ApplicationController { if (target.value == '__other__') { show(inputGroup); input.disabled = false; + input.focus(); } else { hide(inputGroup); input.disabled = true; diff --git a/app/javascript/controllers/menu_button_controller.ts b/app/javascript/controllers/menu_button_controller.ts index f1a31e02d..208252260 100644 --- a/app/javascript/controllers/menu_button_controller.ts +++ b/app/javascript/controllers/menu_button_controller.ts @@ -6,53 +6,26 @@ export class MenuButtonController extends ApplicationController { declare readonly buttonTarget: HTMLButtonElement; declare readonly menuTarget: HTMLElement; - #teardown?: () => void; - connect() { this.setup(); } - disconnect(): void { - this.#teardown?.(); - } - private get isOpen() { return (this.element as HTMLElement).classList.contains('open'); } private get isMenu() { - return !(this.element as HTMLElement).dataset.popover; + return this.menuTarget.getAttribute('role') == 'menu'; } private setup() { - this.buttonTarget.setAttribute( - 'aria-haspopup', - this.isMenu ? 'menu' : 'true' - ); - this.buttonTarget.setAttribute('aria-controls', this.menuTarget.id); - if (!this.buttonTarget.id) { - this.buttonTarget.id = `${this.menuTarget.id}_button`; - } - - this.menuTarget.setAttribute('aria-labelledby', this.buttonTarget.id); - this.menuTarget.setAttribute('role', this.isMenu ? 'menu' : 'region'); + // see: + // To progressively enhance this navigation widget that is by default accessible, + // the class to hide the menu and the inclusion of tabindex="-1" on the interactive menuitem + // content should be added with JavaScript on load. this.menuTarget.classList.add('fade-in-down'); - this.menuTarget.setAttribute('tab-index', '-1'); - if (this.isMenu) { - for (const menuItem of this.menuTarget.querySelectorAll('a')) { - menuItem.setAttribute('role', 'menuitem'); - } - for (const dropdownItems of this.menuTarget.querySelectorAll( - '.dropdown-items' - )) { - dropdownItems.setAttribute('role', 'none'); - } - for (const dropdownItems of this.menuTarget.querySelectorAll( - '.dropdown-items > li' - )) { - dropdownItems.setAttribute('role', 'none'); - } + this.menuItems.map((menuItem) => menuItem.setAttribute('tabindex', '-1')); } this.on('click', (event) => { @@ -78,6 +51,14 @@ export class MenuButtonController extends ApplicationController { this.onMenuKeydown(event); } }); + + this.on(document.body, 'click', (event) => { + const target = event.target as HTMLElement; + if (this.isOpen && this.isClickOutside(target)) { + this.menuTarget.classList.remove('fade-in-down'); + this.close(); + } + }); } private open(focusMenuItem: 'first' | 'last' = 'first') { @@ -85,30 +66,18 @@ export class MenuButtonController extends ApplicationController { this.menuTarget.parentElement?.classList.add('open'); this.menuTarget.focus(); - const onClickBody = (event: Event) => { - const target = event.target as HTMLElement; - if (this.isClickOutside(target)) { - this.menuTarget.classList.remove('fade-in-down'); - this.close(); - } - }; requestAnimationFrame(() => { if (focusMenuItem == 'first') { this.setFocusToFirstMenuitem(); } else { this.setFocusToLastMenuitem(); } - document.body.addEventListener('click', onClickBody); }); - - this.#teardown = () => - document.body.removeEventListener('click', onClickBody); } private close() { - this.buttonTarget.removeAttribute('aria-expanded'); + this.buttonTarget.setAttribute('aria-expanded', 'false'); this.menuTarget.parentElement?.classList.remove('open'); - this.#teardown?.(); this.setFocusToMenuitem(null); } diff --git a/app/lib/active_storage/downloadable_file.rb b/app/lib/active_storage/downloadable_file.rb index 4f84c7940..09713dadb 100644 --- a/app/lib/active_storage/downloadable_file.rb +++ b/app/lib/active_storage/downloadable_file.rb @@ -1,7 +1,7 @@ class ActiveStorage::DownloadableFile - def self.create_list_from_dossiers(dossiers, for_expert = false) - PiecesJustificativesService.generate_dossier_export(dossiers, include_infos_administration: !for_expert) + - PiecesJustificativesService.liste_documents(dossiers, for_expert) + def self.create_list_from_dossiers(dossiers, with_bills: false, with_champs_private: false, include_infos_administration: false) + PiecesJustificativesService.generate_dossier_export(dossiers, include_infos_administration:) + + PiecesJustificativesService.liste_documents(dossiers, with_bills:, with_champs_private:) end def self.cleanup_list_from_dossier(files) diff --git a/app/models/avis.rb b/app/models/avis.rb index 15d49d894..334625fe7 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -8,6 +8,7 @@ # confidentiel :boolean default(FALSE), not null # email :string # introduction :text +# reminded_at :datetime # revoked_at :datetime # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/champ.rb b/app/models/champ.rb index b53e0e710..06a7d365f 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -226,7 +226,7 @@ class Champ < ApplicationRecord private def html_id - "#{stable_id}-#{id}" + "champ-#{stable_id}-#{id}" end def needs_dossier_id? @@ -238,7 +238,7 @@ class Champ < ApplicationRecord end def cleanup_if_empty - if external_id_changed? + if persisted? && external_id_changed? self.data = nil end end diff --git a/app/models/champs/date_champ.rb b/app/models/champs/date_champ.rb index b7ce24a46..a462a242d 100644 --- a/app/models/champs/date_champ.rb +++ b/app/models/champs/date_champ.rb @@ -39,7 +39,7 @@ class Champs::DateChamp < Champ private def convert_to_iso8601 - return if valid_iso8601? + return if likely_iso8601_format? && parsable_iso8601? self.value = if /^\d{2}\/\d{2}\/\d{4}$/.match?(value) Date.parse(value).iso8601 @@ -49,12 +49,20 @@ class Champs::DateChamp < Champ end def iso_8601 - return if valid_iso8601? || value.blank? + return if parsable_iso8601? || value.blank? # i18n-tasks-use t('errors.messages.not_a_date') errors.add :date, errors.generate_message(:value, :not_a_date) end - def valid_iso8601? + def likely_iso8601_format? /^\d{4}-\d{2}-\d{2}$/.match?(value) end + + def parsable_iso8601? + Date.parse(value) + true + rescue ArgumentError, # case 2023-27-02, out of range + TypeError # nil + false + end end diff --git a/app/models/concerns/dossier_rebase_concern.rb b/app/models/concerns/dossier_rebase_concern.rb index 5e2f5be67..54f1445fa 100644 --- a/app/models/concerns/dossier_rebase_concern.rb +++ b/app/models/concerns/dossier_rebase_concern.rb @@ -54,32 +54,32 @@ module DossierRebaseConcern .group_by(&:op) .tap { _1.default = [] } + champs_by_stable_id = champs + .joins(:type_de_champ) + .group_by(&:stable_id) + .transform_values { Champ.where(id: _1) } + # add champ changes_by_op[:add] - .map(&:stable_id) - .map { target_coordinates_by_stable_id[_1] } - .each { add_new_champs_for_revision(_1) } + .each { add_new_champs_for_revision(target_coordinates_by_stable_id[_1.stable_id]) } # remove champ changes_by_op[:remove] - .each { delete_champs_for_revision(_1.stable_id) } + .each { champs_by_stable_id[_1.stable_id].destroy_all } if brouillon? changes_by_op[:update] - .map { |change| [change, champs.joins(:type_de_champ).where(type_de_champ: { stable_id: change.stable_id })] } - .each { |change, champs| apply(change, champs) } + .each { apply(_1, champs_by_stable_id[_1.stable_id]) } end # due to repetition tdc clone on update or erase # we must reassign tdc to the latest version - Champ - .includes(:type_de_champ) - .where(dossier: self) - .map { [_1, target_coordinates_by_stable_id[_1.stable_id].type_de_champ] } - .each { |champ, target_tdc| champ.update_columns(type_de_champ_id: target_tdc.id, rebased_at: Time.zone.now) } + champs_by_stable_id + .filter_map { |stable_id, champs| [target_coordinates_by_stable_id[stable_id].type_de_champ_id, champs] if champs.present? } + .each { |type_de_champ_id, champs| champs.update_all(type_de_champ_id:) } # update dossier revision - self.update_column(:revision_id, target_revision.id) + update_column(:revision_id, target_revision.id) end def apply(change, champs) @@ -92,18 +92,27 @@ module DossierRebaseConcern value: nil, value_json: nil, external_id: nil, - data: nil) + data: nil, + rebased_at: Time.zone.now) when :drop_down_options # we are removing options, we need to remove the value if it contains one of the removed options removed_options = change.from - change.to if removed_options.present? && champs.any? { _1.in?(removed_options) } - champs.filter { _1.in?(removed_options) }.each { _1.remove_option(removed_options) } + champs.filter { _1.in?(removed_options) }.each do + _1.remove_option(removed_options) + _1.update_column(:rebased_at, Time.zone.now) + end end when :carte_layers # if we are removing cadastres layer, we need to remove cadastre geo areas if change.from.include?(:cadastres) && !change.to.include?(:cadastres) - champs.each { _1.cadastres.each(&:destroy) } + champs.filter { _1.cadastres.present? }.each do + _1.cadastres.each(&:destroy) + _1.update_column(:rebased_at, Time.zone.now) + end end + else + champs.update_all(rebased_at: Time.zone.now) end end @@ -126,20 +135,13 @@ module DossierRebaseConcern end def create_champ(target_coordinate, parent, row_id: nil) - params = { revision: target_coordinate.revision, row_id: }.compact + params = { revision: target_coordinate.revision, rebased_at: Time.zone.now, row_id: }.compact champ = target_coordinate .type_de_champ .build_champ(params) parent.champs << champ end - def delete_champs_for_revision(stable_id) - champs - .joins(:type_de_champ) - .where(types_de_champ: { stable_id: }) - .destroy_all - end - def purge_piece_justificative_file(champ) ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 9ba6b74c0..6964e1a8a 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1184,7 +1184,7 @@ class Dossier < ApplicationRecord end def geo_data? - geo_areas.present? + GeoArea.exists?(champ_id: champs_public.ids + champs_private.ids) end def to_feature_collection diff --git a/app/models/dossier_transfer.rb b/app/models/dossier_transfer.rb index 8fbf6ec1d..15c77d845 100644 --- a/app/models/dossier_transfer.rb +++ b/app/models/dossier_transfer.rb @@ -8,15 +8,18 @@ # updated_at :datetime not null # class DossierTransfer < ApplicationRecord + include EmailSanitizableConcern has_many :dossiers, dependent: :nullify EXPIRATION_LIMIT = 2.weeks validates :email, format: { with: Devise.email_regexp } + before_validation -> { sanitize_email(:email) } scope :pending, -> { where('created_at > ?', (Time.zone.now - EXPIRATION_LIMIT)) } scope :stale, -> { where('created_at < ?', (Time.zone.now - EXPIRATION_LIMIT)) } scope :with_dossiers, -> { joins(:dossiers).merge(Dossier.visible_by_user) } + scope :for_email, -> (email) { includes(dossiers: :user).with_dossiers.where(email: email) } after_create_commit :send_notification diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index f6dd14071..4f536db34 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -330,8 +330,11 @@ class ProcedurePresentation < ApplicationRecord if field['scope'].present? I18n.t(field['scope']).map(&:to_a).map(&:reverse) elsif field['table'] == 'groupe_instructeur' - instructeur.groupe_instructeurs - .map { [_1.label, _1.id] } + instructeur.groupe_instructeurs.filter_map do + if _1.procedure_id == procedure.id + [_1.label, _1.id] + end + end end end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index c52c2ab08..098a7e8c0 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -243,7 +243,7 @@ class TypeDeChamp < ApplicationRecord end def only_present_on_draft? - revisions.size == 1 + revisions.one? && revisions.first.draft? end def drop_down_other? diff --git a/app/services/pieces_justificatives_service.rb b/app/services/pieces_justificatives_service.rb index f7bb33861..1b18ea4ca 100644 --- a/app/services/pieces_justificatives_service.rb +++ b/app/services/pieces_justificatives_service.rb @@ -1,13 +1,13 @@ class PiecesJustificativesService - def self.liste_documents(dossiers, for_expert) + def self.liste_documents(dossiers, with_bills:, with_champs_private:) bill_ids = [] docs = dossiers.in_batches.flat_map do |batch| - pjs = pjs_for_champs(batch, for_expert) + + pjs = pjs_for_champs(batch, with_champs_private:) + pjs_for_commentaires(batch) + pjs_for_dossier(batch) - if !for_expert + if with_bills # some bills are shared among operations # so first, all the bill_ids are fetched operation_logs, some_bill_ids = operation_logs_and_signature_ids(batch) @@ -19,7 +19,7 @@ class PiecesJustificativesService pjs end - if !for_expert + if with_bills # then the bills are retrieved without duplication docs += signatures(bill_ids.uniq) end @@ -117,12 +117,12 @@ class PiecesJustificativesService private - def self.pjs_for_champs(dossiers, for_expert = false) + def self.pjs_for_champs(dossiers, with_champs_private:) champs = Champ .joins(:piece_justificative_file_attachments) .where(type: "Champs::PieceJustificativeChamp", dossier: dossiers) - if for_expert + if !with_champs_private champs = champs.where(private: false) end diff --git a/app/services/procedure_archive_service.rb b/app/services/procedure_archive_service.rb index 594801961..db87c0785 100644 --- a/app/services/procedure_archive_service.rb +++ b/app/services/procedure_archive_service.rb @@ -15,7 +15,7 @@ class ProcedureArchiveService dossiers.processed_in_month(archive.month) end - attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers) + attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, with_bills: true, with_champs_private: true) DownloadableFileService.download_and_zip(@procedure, attachments, zip_root_folder(archive)) do |zip_filepath| ArchiveUploader.new(procedure: @procedure, filename: archive.filename(@procedure), filepath: zip_filepath) diff --git a/app/services/procedure_export_service.rb b/app/services/procedure_export_service.rb index def935f06..ee24e574c 100644 --- a/app/services/procedure_export_service.rb +++ b/app/services/procedure_export_service.rb @@ -35,7 +35,7 @@ class ProcedureExportService end def to_zip - attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, true) + attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, with_champs_private: true) DownloadableFileService.download_and_zip(procedure, attachments, base_filename) do |zip_filepath| ArchiveUploader.new(procedure: procedure, filename: filename(:zip), filepath: zip_filepath).blob diff --git a/app/views/administrateurs/procedures/_procedures_list.html.haml b/app/views/administrateurs/procedures/_procedures_list.html.haml index b58e0e573..3cbec375e 100644 --- a/app/views/administrateurs/procedures/_procedures_list.html.haml +++ b/app/views/administrateurs/procedures/_procedures_list.html.haml @@ -41,42 +41,43 @@ %li = link_to admin_procedure_path(procedure), class: 'fr-btn fr-icon-draft-line fr-btn--tertiary' do Modifier - %li.dropdown{ data: { controller: 'menu-button' } } - %button.fr-btn.fr-btn--tertiary.dropdown-button.procedures-actions-btn{ data: { menu_button_target: 'button' } } + + = render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-btn--tertiary'] }, menu_options: { id: dom_id(procedure, :actions_menu)}) do |menu| + - menu.with_button_inner_html do Actions - .dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(procedure, :actions_menu) } - %ul.dropdown-items.pl-0 - - if !procedure.close? && !procedure.discarded? - %li - = link_to sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener do - %span.icon.in-progress - .dropdown-description - %h4= t('administrateurs.dropdown_actions.to_test') - - unless procedure.discarded? - %li - = link_to admin_procedure_clone_path(procedure.id), class: 'clone-btn', data: { method: :put } do - %span.icon.new-folder - .dropdown-description - %h4= t('administrateurs.dropdown_actions.to_clone') + - if !procedure.close? && !procedure.discarded? + - menu.with_item do + = link_to(sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener, role: 'menuitem') do + %span.icon.in-progress + .dropdown-description + %h4= t('administrateurs.dropdown_actions.to_test') - - if procedure.publiee? - %li - = link_to admin_procedure_close_path(procedure_id: procedure.id) do - %span.icon.archive - .dropdown-description - %h4= t('administrateurs.dropdown_actions.to_close') + - if !procedure.discarded? + - menu.with_item do + = link_to(admin_procedure_clone_path(procedure.id), role: 'menuitem', class: 'clone-btn', data: { method: :put }) do + %span.icon.new-folder + .dropdown-description + %h4= t('administrateurs.dropdown_actions.to_clone') - - if procedure.can_be_deleted_by_administrateur? && !procedure.discarded? - %li - = link_to admin_procedure_path(procedure), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do - %span.icon.refuse - .dropdown-description - %h4= t('administrateurs.dropdown_actions.delete') + - if procedure.publiee? + - menu.with_item do + = link_to(admin_procedure_close_path(procedure_id: procedure.id), role: 'menuitem') do + %span.icon.archive + .dropdown-description + %h4= t('administrateurs.dropdown_actions.to_close') + + - if procedure.can_be_deleted_by_administrateur? && !procedure.discarded? + - menu.with_item do + = link_to admin_procedure_path(procedure), role: 'menuitem', method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do + %span.icon.refuse + .dropdown-description + %h4= t('administrateurs.dropdown_actions.delete') + + - if procedure.discarded? + - menu.with_item do + = link_to restore_admin_procedure_path(procedure), role: 'menuitem', method: :put do + %span.icon.unarchive + .dropdown-description + %h4= t('administrateurs.dropdown_actions.restore') - - if procedure.discarded? - %li - = link_to restore_admin_procedure_path(procedure), method: :put do - %span.icon.unarchive - .dropdown-description - %h4= t('administrateurs.dropdown_actions.restore') diff --git a/app/views/experts/avis/_header.html.haml b/app/views/experts/avis/_header.html.haml index 1fe7ea5e0..24312fd9f 100644 --- a/app/views/experts/avis/_header.html.haml +++ b/app/views/experts/avis/_header.html.haml @@ -9,11 +9,11 @@ %li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis)) .header-actions - %span.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } - %button.button.dropdown-button.icon-only{ data: { menu_button_target: 'button' } } - %span.icon.attached - %ul.print-menu.dropdown-content#print-pj-menu{ data: { menu_button_target: 'menu' } } - %li= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_expert_avis_path(avis.procedure, avis), target: "_blank", rel: "noopener", class: "menu-item menu-link" + .fr-download + = link_to telecharger_pjs_expert_avis_path(avis.procedure, avis), download: :download, class: "menu-item menu-link fr-download__link" do + Télécharger le dossier et toutes ses pièces jointes + %span.fr-download__detail + ZIP %nav.tabs %ul diff --git a/app/views/instructeurs/dossiers/_header_actions.html.haml b/app/views/instructeurs/dossiers/_header_actions.html.haml index 9a03b37ce..9cdba1d83 100644 --- a/app/views/instructeurs/dossiers/_header_actions.html.haml +++ b/app/views/instructeurs/dossiers/_header_actions.html.haml @@ -1,21 +1,31 @@ %ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline-md.fr-btns-group--icon-right - %li.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } - %button.fr-btn.fr-btn--tertiary.fr-icon-printer-line.dropdown-button{ title: 'imprimer', 'aria-label': 'Imprimer', data: { menu_button_target: 'button' } } Imprimer - %ul#print-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } } - %li - = link_to "Tout le dossier", print_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link" - %li - = link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link" - %li - = link_to "Export PDF", instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link" - - if dossier.geo_data? - %li - = link_to "Export GeoJSON", geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link" + = render Dropdown::MenuComponent.new(wrapper: :li, menu_options: { id: 'print-menu'}, button_options: { class: ['fr-btn--tertiary', 'fr-icon-printer-line']}) do |menu| + - menu.with_button_inner_html do + Imprimer - %li.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } - %button.fr-btn.fr-btn--tertiary.fr-icon-download-line.dropdown-button{ data: { menu_button_target: 'button', 'aria-label': 'Télécharger' } } Télécharger - %ul#print-pj-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } } - %li= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link" + - menu.with_item do + = link_to print_instructeur_dossier_path(dossier.procedure, dossier), role: 'menuitem', target: "_blank", rel: "noopener", class: "menu-item menu-link" do + Tout le dossier + + - menu.with_item do + = link_to '#', role: 'menuitem', onclick: "window.print()", class: "menu-item menu-link" do + Uniquement cet onglet + + - menu.with_item do + = link_to instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do + Export PDF + + - if dossier.geo_data? + - menu.with_item do + = link_to geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do + Export GeoJSON + + = render Dropdown::MenuComponent.new(wrapper: :li, menu_options: { id: 'print-pj-menu'}, button_options: { class: ['fr-btn--tertiary', 'fr-icon-download-line']}) do |menu| + - menu.with_button_inner_html do + Télécharger + - menu.with_item do + = link_to telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do + Télécharger le dossier et toutes ses pièces jointes %li = render partial: "instructeurs/procedures/dossier_actions", diff --git a/app/views/instructeurs/dossiers/_state_button.html.haml b/app/views/instructeurs/dossiers/_state_button.html.haml index 296edd63b..c8a2e172e 100644 --- a/app/views/instructeurs/dossiers/_state_button.html.haml +++ b/app/views/instructeurs/dossiers/_state_button.html.haml @@ -1,120 +1,107 @@ -.dropdown{ data: { controller: 'menu-button', popover: 'true', turbo_force: true } } - -# Dropdown button title - %button.fr-btn.dropdown-button{ class: button_or_label_class(dossier), data: { menu_button_target: 'button' } } - = dossier_display_state dossier += render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: { data: {'turbo-force': true} }, button_options: { class: [button_or_label_class(dossier)] }) do |menu| + - menu.with_button_inner_html do + = dossier_display_state(dossier) - -# Dropdown content - #state-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } + - if dossier.en_construction? + - menu.with_item(aria: {disabled:"true"}, class: 'selected') do + %span.icon.edit + .dropdown-description + %h4 En construction + Vous permettez à l'usager de modifier ses réponses au formulaire - - if dossier.en_construction? - -# ------------------------------------------------------ - -# EN CONSTRUCTION - -# ------------------------------------------------------ - %ul.dropdown-items + - menu.with_item('data-turbo': 'true') do + = link_to(passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en instruction de ce dossier ?", turbo: true }, role: 'menuitem') do + %span.icon.in-progress + .dropdown-description + %h4 Passer en instruction + L’usager ne pourra plus modifier le formulaire - %li.selected - %span.icon.edit + - elsif dossier.en_instruction? + - menu.with_item('data-turbo': 'true') do + = link_to(repasser_en_construction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en construction de ce dossier ?"}, role: 'menuitem') do + %span.icon.edit + .dropdown-description + %h4 Repasser en construction + Vous permettrez à l'usager de modifier ses réponses au formulaire + + - menu.with_item(aria: {disabled:"true"}, class: 'selected') do + %span.icon.in-progress + .dropdown-description + %h4 En instruction + L’usager ne peut modifier son dossier pendant l'instruction + + - menu.with_item do + = link_to('#', onclick: "DS.showMotivation(event, 'accept');", role: 'menuitem') do + %span.icon.accept + .dropdown-description + %h4 Accepter + L’usager sera notifié que son dossier a été accepté + + + - menu.with_item do + = link_to('#', onclick: "DS.showMotivation(event, 'without-continuation');", role: 'menuitem') do + %span.icon.without-continuation + .dropdown-description + %h4 Classer sans suite + L’usager sera notifié que son dossier a été classé sans suite + + + - menu.with_item do + = link_to('#', onclick: "DS.showMotivation(event, 'refuse');", role: 'menuitem') do + %span.icon.refuse + .dropdown-description + %h4 Refuser + L’usager sera notifié que son dossier a été refusé + + - menu.with_form do + = render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" } + + - menu.with_form do + = render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' } + + - menu.with_form do + = render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' } + + - elsif dossier.termine? + - if dossier.motivation.present? + - menu.with_item(class: 'inactive') do + %span.icon.info + .dropdown-description + %h4 Motivation + %p « #{dossier.motivation} » + + - if dossier.justificatif_motivation.attached? + - menu.with_item(class: 'inactive') do + %span.icon.justificatif + .dropdown-description + %h4 Justificatif + %p Ce justificatif joint par l’instructeur a été envoyé au demandeur. + = render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier } + + - if dossier.attestation.present? + - menu.with_item do + = link_to(attestation_instructeur_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', role: 'menuitem') do + %span.icon.preview .dropdown-description - %h4 En construction - Vous permettez à l'usager de modifier ses réponses au formulaire + %h4 Voir l’attestation + %p Cette attestation a été envoyée automatiquement au demandeur. - %li{ 'data-turbo': 'true' } - = link_to passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en instruction de ce dossier ?" } do - %span.icon.in-progress - .dropdown-description - %h4 Passer en instruction - L’usager ne pourra plus modifier le formulaire - - - elsif dossier.en_instruction? - -# ------------------------------------------------------ - -# EN INSTRUCTION - -# ------------------------------------------------------ - %ul.dropdown-items - - %li{ 'data-turbo': 'true' } - = link_to repasser_en_construction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en construction de ce dossier ?" } do - %span.icon.edit - .dropdown-description - %h4 Repasser en construction - Vous permettrez à l'usager de modifier ses réponses au formulaire - - %li.selected + - if dossier.can_repasser_en_instruction? + - menu.with_item do + = link_to(repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?", turbo: true }, role: 'menuitem') do %span.icon.in-progress .dropdown-description - %h4 En instruction - L’usager ne peut modifier son dossier pendant l'instruction + %h4 Repasser en instruction + L’usager sera notifié que son dossier est réexaminé. + - elsif dossier.user_deleted? + - menu.with_item do + %span.icon.info + .dropdown-description + %h4 En attente d’archivage + L’usager a supprimé son compte. Vous pouvez archiver puis supprimer le dossier. - %li - %a{ href: '#', onclick: "DS.showMotivation(event, 'accept');" } - %span.icon.accept - .dropdown-description - %h4 Accepter - L’usager sera notifié que son dossier a été accepté - - %li - %a{ href: '#', onclick: "DS.showMotivation(event, 'without-continuation');" } - %span.icon.without-continuation - .dropdown-description - %h4 Classer sans suite - L’usager sera notifié que son dossier a été classé sans suite - - %li - %a{ href: '#', onclick: "DS.showMotivation(event, 'refuse');" } - %span.icon.refuse - .dropdown-description - %h4 Refuser - L’usager sera notifié que son dossier a été refusé - - = render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" } - - = render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' } - - = render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' } - - - elsif dossier.termine? - -# --------------------------------------------------- - -# TERMINÉ - -# --------------------------------------------------- - %ul.dropdown-items - - if dossier.motivation.present? - %li.inactive - %span.icon.info - .dropdown-description - %h4 Motivation - %p « #{dossier.motivation} » - - - if dossier.justificatif_motivation.attached? - %li.inactive - %span.icon.justificatif - .dropdown-description - %h4 Justificatif - %p Ce justificatif joint par l’instructeur a été envoyé au demandeur. - = render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier } - - - if dossier.attestation.present? - %li - = link_to attestation_instructeur_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener' do - %span.icon.preview - .dropdown-description - %h4 Voir l’attestation - %p Cette attestation a été envoyée automatiquement au demandeur. - - - if dossier.can_repasser_en_instruction? - %li{ 'data-turbo': 'true' } - = link_to repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?" } do - %span.icon.in-progress - .dropdown-description - %h4 Repasser en instruction - L’usager sera notifié que son dossier est réexaminé. - - elsif dossier.user_deleted? - %li - %span.icon.info - .dropdown-description - %h4 En attente d’archivage - L’usager a supprimé son compte. Vous pouvez archiver puis supprimer le dossier. - - %li - = link_to instructeur_dossier_path(dossier.procedure, dossier), method: :delete do - %span.icon.delete - .dropdown-description - %h4 Supprimer le dossier + - menu.with_item do + = link_to(instructeur_dossier_path(dossier.procedure, dossier), method: :delete, role: 'menuitem') do + %span.icon.delete + .dropdown-description + %h4 Supprimer le dossier diff --git a/app/views/instructeurs/procedures/_dossier_actions.html.haml b/app/views/instructeurs/procedures/_dossier_actions.html.haml index 582af3ab6..3c9bf64d4 100644 --- a/app/views/instructeurs/procedures/_dossier_actions.html.haml +++ b/app/views/instructeurs/procedures/_dossier_actions.html.haml @@ -2,33 +2,36 @@ = link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: "fr-btn fr-btn--secondary" do = t('views.instructeurs.dossiers.restore') - elsif close_to_expiration || Dossier::TERMINE.include?(state) - %li.dropdown.user-dossier-actions{ data: { controller: 'menu-button' } } - %button.fr-btn.fr-mb-0.dropdown-button{ data: { menu_button_target: 'button' } } + = render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-mb-0']}, menu_options: { id: "dossier_#{dossier_id}_actions_menu", class: 'user-dossier-actions' }) do |menu| + - menu.with_button_inner_html do Actions - .dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: "dossier_#{dossier_id}_actions_menu" } - %ul.dropdown-items - - if close_to_expiration - %li - = link_to repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post do - %span.icon.standby - %span.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration') - - if archived - %li - = link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do - %span.icon.unarchive - %span.dropdown-description - Désarchiver le dossier - - else - %li - = link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do - %span.icon.archive - %span.dropdown-description - Archiver le dossier - %li.danger - = link_to instructeur_dossier_path(procedure_id, dossier_id), method: :delete do - %span.icon.delete - %span.dropdown-description - = t('views.instructeurs.dossiers.delete_dossier') + + - if close_to_expiration + - menu.with_item do + = link_to(repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post, role: 'menuitem') do + %span.icon.standby + %span.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration') + + - if archived + - menu.with_item do + = link_to( unarchive_instructeur_dossier_path(procedure_id, dossier_id), role: 'menuitem', method: :patch) do + %span.icon.unarchive + %span.dropdown-description + Désarchiver le dossier + + - else + - menu.with_item do + = link_to( archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, role: 'menuitem') do + %span.icon.archive + %span.dropdown-description + Archiver le dossier + + - menu.with_item(class: 'danger') do + = link_to(instructeur_dossier_path(procedure_id, dossier_id), method: :delete, role: 'menuitem') do + %span.icon.delete + %span.dropdown-description + = t('views.instructeurs.dossiers.delete_dossier') + - elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state) - if dossier_is_followed diff --git a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml index 896b2d0ed..7106f0d58 100644 --- a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml +++ b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml @@ -1,5 +1,7 @@ -%span.dropdown{ data: { controller: 'menu-button', popover: 'true' } } - %button.fr-btn.fr-btn--tertiary.fr-btn--sm.fr-mr-2w.dropdown-button{ data: { menu_button_target: 'button' } } += render Dropdown::MenuComponent.new(wrapper: :div, button_options: { class: ['fr-btn--secondary', 'fr-btn--sm', 'fr-mr-1w'] }, menu_options: { id: 'filter-menu', class:['left-aligned'] }) do |menu| + - menu.with_button_inner_html do = t('views.instructeurs.dossiers.filters.title') - #filter-menu.dropdown-content.left-aligned.fade-in-down{ data: { menu_button_target: 'menu' } } + + - menu.with_form do = render Dossiers::FilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut) + diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 07ca9acca..074a6f27e 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -62,10 +62,10 @@ = render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation) .fr-ml-auto - %span.dropdown{ data: { controller: 'menu-button', popover: 'true' } } - %button.fr-btn.fr-btn--sm.fr-btn--tertiary.dropdown-button.fr-ml-1w{ data: { menu_button_target: 'button' } } + = render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', 'fr-btn--secondary'] }, menu_options: { id: 'custom-menu' }) do |menu| + - menu.with_button_inner_html do = t('views.instructeurs.dossiers.personalize') - #custom-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } + - menu.with_form do = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do = hidden_field_tag :values, nil = react_component("ComboMultiple", @@ -120,6 +120,7 @@ %th.action-col.follow-col Actions + %tr %tbody diff --git a/app/views/invites/_dropdown.html.haml b/app/views/invites/_dropdown.html.haml index 5902deb5d..dad812910 100644 --- a/app/views/invites/_dropdown.html.haml +++ b/app/views/invites/_dropdown.html.haml @@ -1,6 +1,6 @@ - invites = dossier.invites.load -.dropdown.invite-user-action{ data: { controller: 'menu-button', popover: 'true' } } - %button.button.dropdown-button{ data: { menu_button_target: 'button' } } += render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: {class: 'invite-user-action'}, button_options: { class: ['fr-btn--secondary'] }, menu_options: { id: 'invite-content' }) do |menu| + - menu.with_button_inner_html do %span.icon.person - if invites.present? = t('views.invites.dropdown.view_invited_people') @@ -10,6 +10,5 @@ = t('views.invites.dropdown.invite_to_view') - else = t('views.invites.dropdown.invite_to_edit') - - #invite-content.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } + - menu.with_form do = render partial: "invites/form", locals: { dossier: dossier, invites: invites } diff --git a/app/views/layouts/_search_dossiers_form.html.haml b/app/views/layouts/_search_dossiers_form.html.haml index 9662e116d..7299098f5 100644 --- a/app/views/layouts/_search_dossiers_form.html.haml +++ b/app/views/layouts/_search_dossiers_form.html.haml @@ -1,11 +1,9 @@ #search-modal.fr-header__search.fr-modal .fr-container.fr-container-lg--fluid %button.fr-btn--close.fr-btn{ "aria-controls" => "search-modal", :title => t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header]) - #search-473.fr-search-bar{ :role => "search" } - = form_tag "#{search_endpoint}", method: :get, class: "flex width-100" do + #search-473.fr-search-bar + = form_tag "#{search_endpoint}", method: :get, :role => "search", class: "flex width-100" do = label_tag "q", t('views.users.dossiers.search.search_file'), class: 'fr-label' - = text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: t('views.users.dossiers.search.search_file'), aria: { label: t('views.users.dossiers.search.search_file') }, class: "fr-input" + = text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: t('views.users.dossiers.search.placeholder'), class: "fr-input" %button.fr-btn{ title: t('views.users.dossiers.search.search_file') } - = image_tag "icons/search-blue.svg", alt: t('views.users.dossiers.search.search_file'), 'aria-hidden':'true', width: 24, height: 24, loading: 'lazy' - - + = t('views.users.dossiers.search.search_file') diff --git a/app/views/layouts/commencer/_no_procedure.html.haml b/app/views/layouts/commencer/_no_procedure.html.haml index 2580c0243..c1573b002 100644 --- a/app/views/layouts/commencer/_no_procedure.html.haml +++ b/app/views/layouts/commencer/_no_procedure.html.haml @@ -1,15 +1,10 @@ .no-procedure = image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: "" .baseline.center - %p - %span.simple= t('.line1') - %br - = t('.line2') - %br - = t('.line3') + .no-procedure-presentation + %p.simple= t('.line1') + %p= t('.line2') + %p= t('.line3') %hr - %p - %span.small-simple= t('.are_you_new', app_name: APPLICATION_NAME.gsub("-","‑")).html_safe - %br - %br - = link_to t('views.users.sessions.new.find_procedure'), t("links.common.faq.comment_trouver_ma_demarche_url"), title: new_tab_suffix(t('views.users.sessions.new.find_procedure')), class: "fr-btn fr-btn--secondary", **external_link_attributes + %p.small-simple= t('.are_you_new', app_name: APPLICATION_NAME.gsub("-","‑")).html_safe + = link_to t('views.users.sessions.new.find_procedure'), t("links.common.faq.comment_trouver_ma_demarche_url"), title: new_tab_suffix(t('views.users.sessions.new.find_procedure')), class: "fr-btn fr-btn--secondary", **external_link_attributes diff --git a/app/views/layouts/mailers/_service_footer.html.haml b/app/views/layouts/mailers/_service_footer.html.haml index c5e5a3958..984a7ce9e 100644 --- a/app/views/layouts/mailers/_service_footer.html.haml +++ b/app/views/layouts/mailers/_service_footer.html.haml @@ -12,11 +12,8 @@ %p %strong = t('.procedure_management') - %br = service.nom - %br = service.organisme - %br = service.adresse %td{ width: "50%", valign: "top" } %p diff --git a/app/views/prefill_descriptions/_types_de_champs.html.haml b/app/views/prefill_descriptions/_types_de_champs.html.haml index 01db2707d..b668fc17d 100644 --- a/app/views/prefill_descriptions/_types_de_champs.html.haml +++ b/app/views/prefill_descriptions/_types_de_champs.html.haml @@ -42,7 +42,7 @@ = type_de_champ.possible_values_sentence %br - if type_de_champ.too_many_possible_values? - = link_to "Voir toutes les valeurs possibles", prefill_type_de_champ_path(prefill_description.path, type_de_champ) + = link_to t("views.prefill_descriptions.edit.possible_values.link.text"), prefill_type_de_champ_path(prefill_description.path, type_de_champ), title: new_tab_suffix(t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes %tr{ class: prefillable ? "" : "fr-text-mention--grey" } %th = t("views.prefill_descriptions.edit.examples.title") diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml index 69cfb52fc..46aa5b6ed 100644 --- a/app/views/recherche/index.html.haml +++ b/app/views/recherche/index.html.haml @@ -67,21 +67,21 @@ - if instructeur_dossier && expert_dossier %td.action-col.follow-col - .dropdown{ data: { controller: 'menu-button' } } - %button.button.dropdown-button{ data: { menu_button_target: 'button' } } + = render Dropdown::MenuComponent.new(wrapper: :div, button_options: {class: ['fr-btn--sm']}) do |menu| + - menu.with_button_inner_html do Actions - .dropdown-content{ data: { menu_button_target: 'menu' } } - %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 + + - menu.with_item do + = link_to(instructeur_dossier_path(procedure_id, p.dossier_id), role: 'menuitem') do + %span.icon.in-progress> + .dropdown-description + Voir le dossier + + - menu.with_item do + = link_to(expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id]), role: 'menuitem') do + %span.icon.in-progress> + .dropdown-description + Donner mon avis - elsif instructeur_dossier - if hidden_by_administration diff --git a/app/views/shared/help/_help_dropdown_dossier.html.haml b/app/views/shared/help/_help_dropdown_dossier.html.haml index 51f1460e3..7b58386af 100644 --- a/app/views/shared/help/_help_dropdown_dossier.html.haml +++ b/app/views/shared/help/_help_dropdown_dossier.html.haml @@ -1,15 +1,16 @@ -.dropdown.help-dropdown{ data: { controller: 'menu-button' } } - %button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } } += render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu| + - menu.with_button_inner_html do = t('help') - #help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - %ul.dropdown-items - - title = dossier.brouillon? ? "Besoin d’aide pour remplir votre dossier ?" : "Une question sur votre dossier ?" + - title = dossier.brouillon? ? "Besoin d’aide pour remplir votre dossier ?" : "Une question sur votre dossier ?" - - if dossier.messagerie_available? - = render partial: 'shared/help/dropdown_items/messagerie_item', - locals: { dossier: dossier, title: title } - - elsif dossier.procedure.service.present? - = render partial: 'shared/help/dropdown_items/service_item', - locals: { service: dossier.procedure.service, title: title } + - if dossier.messagerie_available? + - menu.with_item do + = render partial: 'shared/help/dropdown_items/messagerie_item', locals: { dossier: dossier, title: title } - = render partial: 'shared/help/dropdown_items/faq_item' + - elsif dossier.procedure.service.present? + - menu.with_item do + = render partial: 'shared/help/dropdown_items/service_item', + locals: { service: dossier.procedure.service, title: title } + + - menu.with_item do + = render partial: 'shared/help/dropdown_items/faq_item' diff --git a/app/views/shared/help/_help_dropdown_instructeur.html.haml b/app/views/shared/help/_help_dropdown_instructeur.html.haml index d1e8ef299..329b80e50 100644 --- a/app/views/shared/help/_help_dropdown_instructeur.html.haml +++ b/app/views/shared/help/_help_dropdown_instructeur.html.haml @@ -1,7 +1,8 @@ -.dropdown.help-dropdown{ data: { controller: 'menu-button' } } - %button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } } += render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu| + - menu.with_button_inner_html do = t('help') - #help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - %ul.dropdown-items - = render partial: 'shared/help/dropdown_items/faq_item' - = render partial: 'shared/help/dropdown_items/email_item' + + - menu.with_item do + = render partial: 'shared/help/dropdown_items/faq_item' + - menu.with_item do + = render partial: 'shared/help/dropdown_items/email_item' diff --git a/app/views/shared/help/_help_dropdown_procedure.html.haml b/app/views/shared/help/_help_dropdown_procedure.html.haml index b3781d6eb..80b939161 100644 --- a/app/views/shared/help/_help_dropdown_procedure.html.haml +++ b/app/views/shared/help/_help_dropdown_procedure.html.haml @@ -1,10 +1,9 @@ -.dropdown.help-dropdown{ data: { controller: 'menu-button' } } - %button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } } += render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu| + - menu.with_button_inner_html do = t('help') - #help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - %ul.dropdown-items - - if procedure.service.present? - = render partial: 'shared/help/dropdown_items/service_item', - locals: { service: procedure.service, title: "Une question sur cette démarche ?" } - = render partial: 'shared/help/dropdown_items/faq_item' + - if procedure.service.present? + - menu.with_item do + = render partial: 'shared/help/dropdown_items/service_item', locals: { service: procedure.service, title: "Une question sur cette démarche ?" } + - menu.with_item do + = render partial: 'shared/help/dropdown_items/faq_item' diff --git a/app/views/shared/help/dropdown_items/_email_item.html.haml b/app/views/shared/help/dropdown_items/_email_item.html.haml index 834fb5924..3eecc2f5e 100644 --- a/app/views/shared/help/dropdown_items/_email_item.html.haml +++ b/app/views/shared/help/dropdown_items/_email_item.html.haml @@ -1,5 +1,5 @@ -%li - = mail_to CONTACT_EMAIL do +%li{ role: 'none' } + = mail_to CONTACT_EMAIL, role: 'menuitem' do %span.icon.mail .dropdown-description %span.help-dropdown-title diff --git a/app/views/shared/help/dropdown_items/_faq_item.html.haml b/app/views/shared/help/dropdown_items/_faq_item.html.haml index 28e52de5d..cea9ceaa4 100644 --- a/app/views/shared/help/dropdown_items/_faq_item.html.haml +++ b/app/views/shared/help/dropdown_items/_faq_item.html.haml @@ -1,9 +1,7 @@ -%li - = link_to t("links.common.faq.url"), title: new_tab_suffix(t('help_dropdown.general_title')), **external_link_attributes do - %span.icon.help - .dropdown-description - %span.help-dropdown-title - = t('help_dropdown.problem_title') - %p - = t('help_dropdown.problem_description') - += link_to t("links.common.faq.url"), title: new_tab_suffix(t('help_dropdown.general_title')), **external_link_attributes, role: 'menuitem' do + %span.icon.help + .dropdown-description + %span.help-dropdown-title + = t('help_dropdown.problem_title') + %p + = t('help_dropdown.problem_description') diff --git a/app/views/shared/help/dropdown_items/_messagerie_item.html.haml b/app/views/shared/help/dropdown_items/_messagerie_item.html.haml index 94d31143d..87244e6af 100644 --- a/app/views/shared/help/dropdown_items/_messagerie_item.html.haml +++ b/app/views/shared/help/dropdown_items/_messagerie_item.html.haml @@ -1,6 +1,5 @@ -%li - = link_to messagerie_dossier_path(dossier) do - %span.icon.mail - .dropdown-description - %span.help-dropdown-title= title - %p Envoyez directement un message à l’instructeur. += link_to messagerie_dossier_path(dossier), role: 'menuitem' do + %span.icon.mail + .dropdown-description + %span.help-dropdown-title= title + %p Envoyez directement un message à l’instructeur. diff --git a/app/views/shared/help/dropdown_items/_service_item.html.haml b/app/views/shared/help/dropdown_items/_service_item.html.haml index 215793ec5..3aae66c04 100644 --- a/app/views/shared/help/dropdown_items/_service_item.html.haml +++ b/app/views/shared/help/dropdown_items/_service_item.html.haml @@ -1,15 +1,14 @@ -%li.help-dropdown-service - %span.icon.person - .dropdown-description - %span.help-dropdown-title= title - .help-dropdown-service-action - %p Contactez directement l’administration : - %p.help-dropdown-service-item - %span.icon.small.mail - = link_to service.email, "mailto:#{service.email}" - %p.help-dropdown-service-item - %span.icon.small.phone - = link_to service.telephone, service.telephone_url - %p.help-dropdown-service-item - %span.icon.small.clock - = service.horaires +%span.icon.person +.dropdown-description + %span.help-dropdown-title= title + .help-dropdown-service-action + %p Contactez directement l’administration : + %p.help-dropdown-service-item + %span.icon.small.mail + = link_to service.email, "mailto:#{service.email}", role: 'menuitem' + %p.help-dropdown-service-item + %span.icon.small.phone + = link_to service.telephone, service.telephone_url, role: 'menuitem' + %p.help-dropdown-service-item + %span.icon.small.clock + = service.horaires diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml index e8d4c950e..261304735 100644 --- a/app/views/support/index.html.haml +++ b/app/views/support/index.html.haml @@ -10,8 +10,9 @@ = form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do .description - %h2= t('.intro_html') - %br + .recommandations + %h2 + = t('.intro_html') %p.mandatory-explanation= t('asterisk_html', scope: [:utils]) - if !user_signed_in? @@ -34,7 +35,7 @@ - if link.present? .support.card.featured.mb-4.ml-4.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } } - .card-title + %p.card-title = t('.our_answer') .card-content -# i18n-tasks-use t("support.index.#{question_type}.answer_html") diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index b12929bb1..cda5f0b6d 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -15,9 +15,10 @@ %li - horaires = "#{I18n.t('users.procedure_footer.contact.schedule.prefix')}#{formatted_horaires(service.horaires)}" = link_to service.telephone_url, class: 'fr-footer__top-link' do - = I18n.t('users.procedure_footer.contact.phone.link', service_telephone: service.telephone) - %br - = horaires + %p + = I18n.t('users.procedure_footer.contact.phone.link', service_telephone: service.telephone) + %p + = horaires %li = link_to I18n.t('users.procedure_footer.contact.stats.link'), statistiques_path(procedure.path), class: 'fr-footer__top-link', rel: 'noopener' diff --git a/app/views/users/dossiers/_dossier_actions.html.haml b/app/views/users/dossiers/_dossier_actions.html.haml index 52a89f4f3..4ca3f6118 100644 --- a/app/views/users/dossiers/_dossier_actions.html.haml +++ b/app/views/users/dossiers/_dossier_actions.html.haml @@ -4,49 +4,51 @@ - has_transfer_action = dossier.user == current_user - has_actions = has_edit_action || has_delete_action || has_new_dossier_action || has_transfer_action + + - if has_actions - .dropdown.user-dossier-actions{ data: { controller: 'menu-button' } } - %button.fr-btn.fr-btn--secondary.dropdown-button{ data: { menu_button_target: 'button' } } + = render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: {class: 'invite-user-actions'}, menu_options: {id: dom_id(dossier, :actions_menu)}, button_options: {class: 'fr-btn--sm fr-btn--secondary'}) do |menu| + - menu.with_button_inner_html do = t('views.users.dossiers.dossier_action.actions') - .dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(dossier, :actions_menu) } - %ul.dropdown-items - - if has_edit_action - - if dossier.brouillon? - %li - = link_to(url_for_dossier(dossier)) do - %span.icon.edit - .dropdown-description - = t('views.users.dossiers.dossier_action.edit_draft') - - else - %li - = link_to modifier_dossier_path(dossier) do - %span.icon.edit - .dropdown-description - = t('views.users.dossiers.dossier_action.edit_dossier') - - if has_transfer_action - %li - = link_to transferer_dossier_path(dossier) do - %span.icon.person - .dropdown-description - = t('views.users.dossiers.dossier_action.transfer_dossier') + - if has_edit_action + - if dossier.brouillon? + - menu.with_item do + = link_to(url_for_dossier(dossier), role: 'menuitem') do + %span.icon.edit + .dropdown-description + = t('views.users.dossiers.dossier_action.edit_draft') + - else + - menu.with_item do + = link_to(modifier_dossier_path(dossier), role: 'menuitem') do + %span.icon.edit + .dropdown-description + = t('views.users.dossiers.dossier_action.edit_dossier') - - if has_new_dossier_action - %li - = link_to procedure_lien(dossier.procedure) do - %span.icon.new-folder - .dropdown-description - = t('views.users.dossiers.dossier_action.start_other_dossier') - %li - = link_to clone_dossier_path(dossier), method: :post do - %span.icon.new-folder - .dropdown-description - = t('views.users.dossiers.dossier_action.clone') + - if has_transfer_action + - menu.with_item do + = link_to(transferer_dossier_path(dossier), role: 'menuitem') do + %span.icon.person + .dropdown-description + = t('views.users.dossiers.dossier_action.transfer_dossier') - - if has_delete_action - %li.danger - = link_to delete_dossier_dossier_path(dossier), method: :patch, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraîne l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" } do - %span.icon.delete - .dropdown-description - = t('views.users.dossiers.dossier_action.delete_dossier') + - if has_new_dossier_action + - menu.with_item do + = link_to(procedure_lien(dossier.procedure), role: 'menuitem') do + %span.icon.new-folder + .dropdown-description + = t('views.users.dossiers.dossier_action.start_other_dossier') + - menu.with_item do + = link_to(clone_dossier_path(dossier), method: :post, role: 'menuitem') do + %span.icon.new-folder + .dropdown-description + = t('views.users.dossiers.dossier_action.clone') + + - if has_delete_action + - menu.with_item(class: 'danger') do + = link_to(delete_dossier_dossier_path(dossier), role: 'menuitem', method: :patch, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraîne l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" }) do + + %span.icon.delete + .dropdown-description + = t('views.users.dossiers.dossier_action.delete_dossier') diff --git a/app/views/users/dossiers/_identity_dropdown.html.haml b/app/views/users/dossiers/_identity_dropdown.html.haml index 3da4cd863..88781adfb 100644 --- a/app/views/users/dossiers/_identity_dropdown.html.haml +++ b/app/views/users/dossiers/_identity_dropdown.html.haml @@ -1,8 +1,8 @@ -.dropdown.edit-identity-action{ data: { controller: 'menu-button', popover: 'true' } } - %button.button.dropdown-button{ data: { menu_button_target: 'button' } } += render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: {class: ['edit-identity-action']}, menu_options: { class:['edit-identity-content'] }) do |menu| + - menu.with_button_inner_html do = t("views.shared.dossiers.demande.my_identity") - #edit-identity-content.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } + - menu.with_form do - if dossier.procedure.for_individual = render partial: "shared/dossiers/identite_individual", locals: { individual: dossier.individual } diff --git a/app/views/users/dossiers/demande.html.haml b/app/views/users/dossiers/demande.html.haml index e9b3b7710..56e88e5df 100644 --- a/app/views/users/dossiers/demande.html.haml +++ b/app/views/users/dossiers/demande.html.haml @@ -10,5 +10,5 @@ .container - if !@dossier.read_only? - = link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'button accepted edit-form', title: "Modifier mon dossier tant qu'il n'est pas passé en instruction" + = link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'fr-btn fr-btn-sm', 'title'=> "Modifier mon dossier tant qu'il n'est pas passé en instruction" .clearfix diff --git a/app/views/users/dossiers/show/_header.html.haml b/app/views/users/dossiers/show/_header.html.haml index 602354561..6139f57ba 100644 --- a/app/views/users/dossiers/show/_header.html.haml +++ b/app/views/users/dossiers/show/_header.html.haml @@ -18,7 +18,7 @@ .header-actions = render partial: 'invites/dropdown', locals: { dossier: dossier } - if dossier.can_be_updated_by_user? && !current_page?(modifier_dossier_path(dossier)) - = link_to t('views.users.dossiers.show.header.edit_dossier'), modifier_dossier_path(dossier), class: 'button accepted edit-form', + = link_to t('views.users.dossiers.show.header.edit_dossier'), modifier_dossier_path(dossier), class: 'fr-btn fr-btn-sm', title: { label: t('views.users.dossiers.show.header.edit_dossier_title') } = render(partial: 'users/dossiers/show/print_dossier', locals: { dossier: dossier }) diff --git a/app/views/users/dossiers/show/_print_dossier.html.haml b/app/views/users/dossiers/show/_print_dossier.html.haml index 58e030960..1e2392431 100644 --- a/app/views/users/dossiers/show/_print_dossier.html.haml +++ b/app/views/users/dossiers/show/_print_dossier.html.haml @@ -1,6 +1 @@ -.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } - %button.button.dropdown-button.icon-only{ title: t('views.users.dossiers.show.header.print'), 'aria-label': 'imprimer', data: { menu_button_target: 'button' } } - %span.icon.printer - %ul#print-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } } - %li - = link_to t('views.users.dossiers.show.header.print_dossier'), dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link" += link_to t('views.users.dossiers.show.header.print'), dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", title: t('views.users.dossiers.show.header.print_dossier'), class: 'fr-btn fr-icon-printer-line fr-btn--tertiary' diff --git a/app/views/users/dossiers/update.turbo_stream.haml b/app/views/users/dossiers/update.turbo_stream.haml index 4963c0c17..ce7539ab1 100644 --- a/app/views/users/dossiers/update.turbo_stream.haml +++ b/app/views/users/dossiers/update.turbo_stream.haml @@ -4,7 +4,7 @@ = turbo_stream.hide_all(@to_hide) - @to_update.each do |champ| = fields_for champ.input_name, champ do |form| - = turbo_stream.morph champ.input_group_id do + = turbo_stream.replace champ.input_group_id do = render EditableChamp::EditableChampComponent.new champ:, form: = turbo_stream.remove_all(".editable-champ .spinner-removable"); diff --git a/config/locales/en.yml b/config/locales/en.yml index 6b70d5d3c..ed8ed14c7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -112,6 +112,9 @@ en: champ_remove: Remove champ_unavailable: Unavailable possible_values: + link: + title: All possible values + text: See all possible values title: Values text_html: A short text textarea_html: A long text @@ -314,7 +317,8 @@ en: demande: edit_dossier: "Edit file" search: - search_file: Search a file + placeholder: Search a file + search_file: Search index: dossiers: "Files" dossiers_list: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e69c9df4c..3c37f96b5 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -103,6 +103,9 @@ fr: champ_remove: Retirer champ_unavailable: Indisponible possible_values: + link: + title: Toutes les valeurs possibles + text: Voir toutes les valeurs possibles title: Valeurs text_html: Un texte court textarea_html: Un texte long @@ -310,7 +313,8 @@ fr: demande: edit_dossier: "Modifier le dossier" search: - search_file: Rechercher un dossier + placeholder: Rechercher un dossier + search_file: Rechercher index: dossiers: "Dossiers" dossiers_list: diff --git a/config/locales/views/support/en.yml b/config/locales/views/support/en.yml index 96fafee96..1b16ef2ed 100644 --- a/config/locales/views/support/en.yml +++ b/config/locales/views/support/en.yml @@ -2,7 +2,8 @@ en: support: index: contact: Contact - intro_html: Contact us via this form and we will answer you as quickly as possible.
Make sure you provide all the required information so we can help you in the best way. + intro_html: "

Contact us via this form and we will answer you as quickly as possible.

+

Make sure you provide all the required information so we can help you in the best way.

" your_question: Your question our_answer: 👉 Our answer notice_pj_product: A screenshot can help us identify the element to improve. @@ -10,35 +11,28 @@ en: procedure_info: question: I've encountered a problem while completing my application answer_html: "

Are you sure that all the mandatory fields ( * ) are properly filled? -

If you have questions about the information requested, contact the service in charge of the procedure.

-

Find more information

" +

If you have questions about the information requested, contact the service in charge of the procedure (FAQ).

" instruction_info: question: I have a question about the instruction of my application - answer_html: "

If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system.

-

Find more information

-
-

If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.

" + answer_html: "

If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system (FAQ).

+

If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.

" product: question: I have an idea to improve the website - answer_html: "

Got an idea? Please check our enhancement dashboard

+ answer_html: "

Got an idea? Please check our enhancement dashboard :

  • Vote for your priority improvements
  • -
  • Share your own ideas
-

➡ Access the enhancement dashboard

" +
  • Share your own ideas
  • " lost_user: question: I am having trouble finding the procedure I am looking for answer_html: "

    We invite you to contact the administration in charge of the procedure so they can provide you the link. - It should look like this: %{base_url}/commencer/NOM_DE_LA_DEMARCHE.

    -
    -

    You can find here the most popular procedures (licence, detr, etc.) :

    -

    %{link_lost_user}

    " + It should look like this: %{base_url}/commencer/NOM_DE_LA_DEMARCHE.

    +

    You can find here the most popular procedures (licence, detr, etc.).

    " other: question: Other topic admin: your_question: Your question admin_intro_html: "

    As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.

    -
    -

    Caution, this form is dedicated to public bodies only. - It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us here.

    " +

    Caution, this form is dedicated to public bodies only. + It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us here.

    " contact_team: Contact our team pro_phone_number: Professional phone number (direct line) pro_mail: Professional email address diff --git a/config/locales/views/support/fr.yml b/config/locales/views/support/fr.yml index 8b8632060..aebe2ca2e 100644 --- a/config/locales/views/support/fr.yml +++ b/config/locales/views/support/fr.yml @@ -2,7 +2,8 @@ fr: support: index: contact: Contact - intro_html: Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.
    Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux. + intro_html: "

    Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.

    +

    Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux.

    " your_question: Votre question our_answer: 👉 Notre réponse notice_pj_product: Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer. @@ -10,35 +11,28 @@ fr: procedure_info: question: J’ai un problème lors du remplissage de mon dossier answer_html: "

    Avez-vous bien vérifié que tous les champs obligatoires ( * ) sont remplis ? -

    Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche.

    -

    En savoir plus

    " +

    Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche (FAQ).

    " instruction_info: question: J’ai une question sur l’instruction de mon dossier - answer_html: "

    Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie.

    -

    En savoir plus

    -
    -

    Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur l’instruction de votre dossier.

    " + answer_html: "

    Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ).

    +

    Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur l’instruction de votre dossier.

    " product: question: J’ai une idée d’amélioration pour votre site - answer_html: "

    Une idée ? Pensez à consulter notre tableau de bord des améliorations

    -
    • Votez pour vos améliorations prioritaires;
    • -
    • Proposez votre propre idée.
    -

    ➡ Accéder au tableau des améliorations

    " + answer_html: "

    Une idée ? Pensez à consulter notre tableau de bord des améliorations :

    +
    • Votez pour vos améliorations prioritaires,
    • +
    • Proposez votre propre idée.
    " lost_user: question: Je ne trouve pas la démarche que je veux faire - answer_html: "

    Nous vous invitons à contacter l’administration en charge de votre démarche pour qu’elle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : %{base_url}/commencer/NOM_DE_LA_DEMARCHE.

    -
    -

    Vous pouvez aussi consulter ici la liste de nos démarches les plus frequentes (permis, detr etc) :

    -

    %{link_lost_user}

    " + answer_html: "

    Nous vous invitons à contacter l’administration en charge de votre démarche pour qu’elle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : %{base_url}/commencer/NOM_DE_LA_DEMARCHE.

    +

    Vous pouvez aussi consulter ici la liste de nos démarches les plus fréquentes (permis, detr etc).

    " other: question: Autre sujet admin: your_question: Votre question admin_intro_html: "

    En tant qu’administration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.

    -
    -

    Attention, ce formulaire est réservé uniquement aux organismes publics. - Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d’utilité publique). Si c'est votre cas, rendez-vous sur notre - formulaire de contact public.

    " +

    Attention, ce formulaire est réservé uniquement aux organismes publics. + Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d’utilité publique). Si c'est votre cas, rendez-vous sur notre + formulaire de contact public.

    " contact_team: Contactez notre équipe pro_phone_number: Numéro de téléphone professionnel (ligne directe) pro_mail: Adresse e-mail professionnelle diff --git a/db/migrate/20230126145329_add_reminded_at_to_avis.rb b/db/migrate/20230126145329_add_reminded_at_to_avis.rb new file mode 100644 index 000000000..3efce26ae --- /dev/null +++ b/db/migrate/20230126145329_add_reminded_at_to_avis.rb @@ -0,0 +1,5 @@ +class AddRemindedAtToAvis < ActiveRecord::Migration[6.1] + def change + add_column :avis, :reminded_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 6456963e5..3e62b05e0 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: 2023_01_17_094317) do +ActiveRecord::Schema.define(version: 2023_01_26_145329) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" @@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 2023_01_17_094317) do t.text "introduction" t.datetime "revoked_at" t.datetime "updated_at", null: false + t.datetime "reminded_at" t.index ["claimant_id"], name: "index_avis_on_claimant_id" t.index ["dossier_id"], name: "index_avis_on_dossier_id" t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id" diff --git a/lib/tasks/benchmarks.rake b/lib/tasks/benchmarks.rake index ec734a662..b33904134 100644 --- a/lib/tasks/benchmarks.rake +++ b/lib/tasks/benchmarks.rake @@ -32,25 +32,10 @@ namespace :benchmarks do desc 'Attestation Template parser' task attestation_template_parser: :environment do - progress = ProgressReport.new(AttestationTemplate.count) - AttestationTemplate.find_each do |template| - parsed = TagsSubstitutionConcern::TagsParser.parse(template.body) - serialized = parsed.map do |token| - case token - in { tag: tag } - "--#{tag}--" - in { text: text } - text - end - end.join('') - if serialized != template.body - throw "Template '#{serialized}' is not eq '#{template.body}' with attestation template #{template.id}" - end - progress.inc - rescue => e - pp "Error with attestation template #{template.id}" - throw e + procedure = Procedure.find(68139) + Benchmark.bm do |x| + x.report("Empty") { TagsSubstitutionConcern::TagsParser.parse('') } + x.report("Démarche 68139") { TagsSubstitutionConcern::TagsParser.parse(procedure.attestation_template.body) } end - progress.finish end end diff --git a/lib/tasks/deployment/20230131132616_fix_dossier_transfer_with_uppercase.rake b/lib/tasks/deployment/20230131132616_fix_dossier_transfer_with_uppercase.rake new file mode 100644 index 000000000..55db50e00 --- /dev/null +++ b/lib/tasks/deployment/20230131132616_fix_dossier_transfer_with_uppercase.rake @@ -0,0 +1,20 @@ +namespace :after_party do + desc 'Deployment task: fix_dossier_transfer_with_uppercase' + task fix_dossier_transfer_with_uppercase: :environment do + puts "Running deploy task 'fix_dossier_transfer_with_uppercase'" + # in production, about 1000, no need to track progress + + DossierTransfer.all.find_each do |dt| + if /A-Z/.match?(dt.email) + dt.email = dt.email.downcase + dt.save + end + end + # Put your task implementation HERE. + + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 8058657fb..ab5873235 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -865,6 +865,10 @@ describe Instructeurs::DossiersController, type: :controller do } end + before do + allow(PiecesJustificativesService).to receive(:generate_dossier_export).with([dossier], include_infos_administration: true).and_call_original + end + it 'includes an attachment' do expect(subject.headers['Content-Disposition']).to start_with('attachment; ') end diff --git a/spec/controllers/users/transfers_controller_spec.rb b/spec/controllers/users/transfers_controller_spec.rb index 9c8809063..d5f80ae0f 100644 --- a/spec/controllers/users/transfers_controller_spec.rb +++ b/spec/controllers/users/transfers_controller_spec.rb @@ -26,6 +26,11 @@ describe Users::TransfersController, type: :controller do it { expect(DossierTransfer.last.dossiers).to eq([dossier]) } end + context 'with upper case email' do + let(:email) { "Test@rspec.net" } + it { expect(DossierTransfer.last.email).to eq(email.strip.downcase) } + end + shared_examples 'email error' do it { expect { subject }.not_to change { DossierTransfer.count } } it { expect(flash.alert).to match([/invalide/]) } diff --git a/spec/lib/active_storage/downloadable_file_spec.rb b/spec/lib/active_storage/downloadable_file_spec.rb index 93fcccc98..93ae1378a 100644 --- a/spec/lib/active_storage/downloadable_file_spec.rb +++ b/spec/lib/active_storage/downloadable_file_spec.rb @@ -1,7 +1,7 @@ describe ActiveStorage::DownloadableFile do let(:dossier) { create(:dossier, :en_construction) } - subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id)) } + subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), with_bills: true, with_champs_private: true) } describe 'create_list_from_dossiers' do context 'when no piece_justificative is present' do diff --git a/spec/models/champs/date_champ_spec.rb b/spec/models/champs/date_champ_spec.rb index 9579a43eb..5578943c5 100644 --- a/spec/models/champs/date_champ_spec.rb +++ b/spec/models/champs/date_champ_spec.rb @@ -1,5 +1,5 @@ describe Champs::DateChamp do - let(:date_champ) { build(:champ_date) } + let(:date_champ) { create(:champ_date) } describe '#convert_to_iso8601' do it 'preserves nil' do @@ -37,6 +37,12 @@ describe Champs::DateChamp do champ.save expect(champ.reload.value).to eq("2023-12-21") end + + it 'converts to nil if false iso' do + champ = champ_with_value("2023-27-02") + champ.save + expect(champ.reload.value).to eq(nil) + end end def champ_with_value(number) diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb index 23cdf4ccb..421af6cdd 100644 --- a/spec/models/concern/tags_substitution_concern_spec.rb +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -516,7 +516,7 @@ describe TagsSubstitutionConcern, type: :model do describe 'parser' do it do - tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text") + tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text --- et encore du text\n--tag--") expect(tokens).to eq([ { text: "hello world " }, { tag: "public" }, @@ -524,14 +524,15 @@ describe TagsSubstitutionConcern, type: :model do { tag: "numéro du dossier" }, { text: ", un test" }, { tag: "yolo" }, - { text: " encore du text\n" + "---\n" + " encore du text" } + { text: " encore du text\n" + "---\n" + " encore du text --- et encore du text\n" }, + { tag: "tag" } ]) end it 'allow for - before tag' do - tokens = TagsSubstitutionConcern::TagsParser.parse("hello --yolo-- -- before-- --after -- -- around -- world ---numéro-du - dossier--") + tokens = TagsSubstitutionConcern::TagsParser.parse("-----------------\nhello --yolo-- -- before-- --after -- -- around -- world ---numéro-du - dossier--") expect(tokens).to eq([ - { text: "hello " }, + { text: "-----------------\nhello " }, { tag: "yolo" }, { text: " " }, { tag: "before" }, diff --git a/spec/models/dossier_rebase_concern_spec.rb b/spec/models/dossier_rebase_concern_spec.rb index 1e5183412..afeef5926 100644 --- a/spec/models/dossier_rebase_concern_spec.rb +++ b/spec/models/dossier_rebase_concern_spec.rb @@ -239,7 +239,7 @@ describe Dossier do end describe "#rebase" do - let(:procedure) { create(:procedure, :with_type_de_champ_mandatory, :with_yes_no, :with_repetition, :with_datetime) } + let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text, mandatory: true }, { type: :repetition, children: [{ type: :text }] }, { type: :datetime }, { type: :yes_no }, { type: :integer_number }]) } let(:dossier) { create(:dossier, procedure: procedure) } let(:yes_no_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } } @@ -248,6 +248,8 @@ describe Dossier do let(:text_champ) { dossier.champs_public.find(&:mandatory?) } let(:rebased_text_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:text) } } + let(:rebased_number_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:integer_number) } } + let(:datetime_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } } let(:datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } } let(:rebased_datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:date) } } @@ -287,7 +289,7 @@ describe Dossier do libelle = text_type_de_champ.libelle expect(dossier.revision).to eq(procedure.published_revision) - expect(dossier.champs_public.size).to eq(4) + expect(dossier.champs_public.size).to eq(5) expect(repetition_champ.rows.size).to eq(2) expect(repetition_champ.rows[0].size).to eq(1) expect(repetition_champ.rows[1].size).to eq(1) @@ -299,7 +301,7 @@ describe Dossier do expect(procedure.revisions.size).to eq(3) expect(dossier.revision).to eq(procedure.published_revision) - expect(dossier.champs_public.size).to eq(4) + expect(dossier.champs_public.size).to eq(5) expect(rebased_text_champ.value).to eq(text_champ.value) expect(rebased_text_champ.type_de_champ_id).not_to eq(text_champ.type_de_champ_id) expect(rebased_datetime_champ.type_champ).to eq(TypeDeChamp.type_champs.fetch(:date)) @@ -309,6 +311,7 @@ describe Dossier do expect(rebased_repetition_champ.rows[1].size).to eq(2) expect(rebased_text_champ.rebased_at).not_to be_nil expect(rebased_datetime_champ.rebased_at).not_to be_nil + expect(rebased_number_champ.rebased_at).to be_nil end end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 56faa1b48..9a684dcb7 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1405,6 +1405,46 @@ describe Dossier do end end + describe '#geo_data' do + let(:dossier) { create(:dossier) } + let(:type_de_champ_carte) { create(:type_de_champ_carte, procedure: dossier.procedure) } + let(:geo_area) { create(:geo_area) } + let(:champ_carte) { create(:champ_carte, type_de_champ: type_de_champ_carte, geo_areas: [geo_area]) } + + context "without data" do + it { expect(dossier.geo_data?).to be_falsey } + end + + context "with geo data in public champ" do + before do + dossier.champs_public << champ_carte + end + + it { expect(dossier.geo_data?).to be_truthy } + end + + context "with geo data in private champ" do + before do + dossier.champs_private << champ_carte + end + + it { expect(dossier.geo_data?).to be_truthy } + end + + it "should solve N+1 problem" do + dossier.champs_public << create_list(:champ_carte, 3, type_de_champ: type_de_champ_carte, geo_areas: [create(:geo_area)]) + + count = 0 + + callback = lambda { |*_args| count += 1 } + ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + dossier.geo_data? + end + + expect(count).to eq(1) + end + end + describe 'dossier_operation_log after dossier deletion' do let(:dossier) { create(:dossier) } let(:dossier_operation_log) { create(:dossier_operation_log, dossier: dossier) } @@ -1853,6 +1893,19 @@ describe Dossier do before { dossier.champs_public << champ_piece_justificative } it { expect(Champs::PieceJustificativeChamp.where(dossier: new_dossier).first.piece_justificative_file.first.blob).to eq(champ_piece_justificative.piece_justificative_file.first.blob) } end + + context 'for Champs::AddressChamp, original_champ.data is duped' do + let(:dossier) { create(:dossier) } + let(:type_de_champs_adress) { create(:type_de_champ_address, procedure: dossier.procedure) } + let(:etablissement) { create(:etablissement) } + let(:champ_address) { create(:champ_address, type_de_champ: type_de_champs_adress, external_id: 'Address', data: { city_code: '75019' }) } + before { dossier.champs_public << champ_address } + + it { expect(Champs::AddressChamp.where(dossier: dossier).first.data).not_to be_nil } + it { expect(Champs::AddressChamp.where(dossier: dossier).first.external_id).not_to be_nil } + it { expect(Champs::AddressChamp.where(dossier: new_dossier).first.external_id).to eq(champ_address.external_id) } + it { expect(Champs::AddressChamp.where(dossier: new_dossier).first.data).to eq(champ_address.data) } + end end context 'private are renewd' do diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 2e7168e21..63d489532 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -854,4 +854,15 @@ describe ProcedurePresentation do it { is_expected.to eq(sorted_ids) } end end + + describe '#field_enum' do + context "field is groupe_instructeur" do + let!(:gi_2) { instructeur.groupe_instructeurs.create(label: 'gi2', procedure:) } + let!(:gi_3) { instructeur.groupe_instructeurs.create(label: 'gi3', procedure: create(:procedure)) } + + subject { procedure_presentation.field_enum('groupe_instructeur/id') } + + it { is_expected.to eq([['défaut', procedure.defaut_groupe_instructeur.id], ['gi2', gi_2.id]]) } + end + end end diff --git a/spec/models/procedure_revision_spec.rb b/spec/models/procedure_revision_spec.rb index b394fa2d0..9f3277267 100644 --- a/spec/models/procedure_revision_spec.rb +++ b/spec/models/procedure_revision_spec.rb @@ -872,4 +872,21 @@ describe ProcedureRevision do it { expect(draft.dependent_conditions(first_champ)).to eq([second_champ]) } it { expect(draft.dependent_conditions(second_champ)).to eq([]) } end + + describe 'only_present_on_draft?' do + let(:procedure) { create(:procedure, types_de_champ_public: [{ libelle: 'Un champ texte' }]) } + let(:type_de_champ) { procedure.draft_revision.types_de_champ_public.first } + + it { + expect(type_de_champ.only_present_on_draft?).to be_truthy + procedure.publish! + expect(type_de_champ.only_present_on_draft?).to be_falsey + procedure.draft_revision.remove_type_de_champ(type_de_champ.stable_id) + expect(type_de_champ.only_present_on_draft?).to be_falsey + expect(type_de_champ.revisions.count).to eq(1) + procedure.publish_revision! + expect(type_de_champ.only_present_on_draft?).to be_falsey + expect(type_de_champ.revisions.count).to eq(1) + } + end end diff --git a/spec/services/pieces_justificatives_service_spec.rb b/spec/services/pieces_justificatives_service_spec.rb index fb58bcc54..bdfbda7a3 100644 --- a/spec/services/pieces_justificatives_service_spec.rb +++ b/spec/services/pieces_justificatives_service_spec.rb @@ -1,10 +1,11 @@ describe PiecesJustificativesService do describe '.liste_documents' do - let(:for_expert) { false } + let(:with_champs_private) { true } + let(:with_bills) { true } subject do PiecesJustificativesService - .liste_documents(Dossier.where(id: dossier.id), for_expert) + .liste_documents(Dossier.where(id: dossier.id), with_bills:, with_champs_private:) .map(&:first) end @@ -59,8 +60,8 @@ describe PiecesJustificativesService do it { expect(subject).to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) } - context 'for expert' do - let(:for_expert) { true } + context 'without private champ' do + let(:with_champs_private) { false } it { expect(subject).to be_empty } end @@ -171,8 +172,8 @@ describe PiecesJustificativesService do expect(subject).to match_array([dossier_bs.serialized.attachment, dossier_bs.signature.attachment]) end - context 'for expert' do - let(:for_expert) { true } + context 'without bills' do + let(:with_bills) { false } it { expect(subject).to be_empty } end @@ -192,8 +193,8 @@ describe PiecesJustificativesService do it { expect(subject).to match_array(dol.serialized.attachment) } - context 'for expert' do - let(:for_expert) { true } + context 'without bills' do + let(:with_bills) { false } it { expect(subject).to be_empty } end diff --git a/spec/system/administrateurs/procedure_cloning_spec.rb b/spec/system/administrateurs/procedure_cloning_spec.rb index 945cb514e..c8e301253 100644 --- a/spec/system/administrateurs/procedure_cloning_spec.rb +++ b/spec/system/administrateurs/procedure_cloning_spec.rb @@ -18,7 +18,7 @@ describe 'As an administrateur I wanna clone a procedure', js: true do scenario do visit admin_procedures_path expect(page.find_by_id('procedures')['data-item-count']).to eq('1') - page.all('.procedures-actions-btn').first.click + page.all('.admin-procedures-list-row .dropdown .fr-btn').first.click page.all('.clone-btn').first.click visit admin_procedures_path(statut: "brouillons") expect(page.find_by_id('procedures')['data-item-count']).to eq('1') diff --git a/spec/system/experts/expert_spec.rb b/spec/system/experts/expert_spec.rb index b02abc1d2..f5e67a2b2 100644 --- a/spec/system/experts/expert_spec.rb +++ b/spec/system/experts/expert_spec.rb @@ -116,7 +116,6 @@ describe 'Inviting an expert:' do click_on '1 avis à donner' click_on avis.dossier.user.email - find(:css, '[aria-controls=print-pj-menu]').click click_on 'Télécharger le dossier et toutes ses pièces jointes' # For some reason, clicking the download link does not trigger the download in the headless browser ; # So we need to go to the download link directly diff --git a/spec/system/users/dropdown_spec.rb b/spec/system/users/dropdown_spec.rb index 8b288dab3..dc5cb0735 100644 --- a/spec/system/users/dropdown_spec.rb +++ b/spec/system/users/dropdown_spec.rb @@ -26,6 +26,21 @@ describe 'dropdown list with other option activated', js: true do find('.radios').find('label:last-child').find('input').select_option expect(page).to have_selector('.drop_down_other', visible: true) end + + scenario "Getting back from other save the new option", js: true do + fill_individual + + choose "Autre" + fill_in("Veuillez saisir votre autre choix", with: "My choice") + + wait_until { user_dossier.champs_public.first.value == "My choice" } + expect(user_dossier.champs_public.first.value).to eq("My choice") + + choose "Secondary 1.1" + + wait_until { user_dossier.champs_public.first.value == "Secondary 1.1" } + expect(user_dossier.champs_public.first.value).to eq("Secondary 1.1") + end end context 'with select' do diff --git a/spec/views/users/dossiers/show/_header.html.haml_spec.rb b/spec/views/users/dossiers/show/_header.html.haml_spec.rb index 033d1eb20..bc931b537 100644 --- a/spec/views/users/dossiers/show/_header.html.haml_spec.rb +++ b/spec/views/users/dossiers/show/_header.html.haml_spec.rb @@ -27,7 +27,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do end it 'can download the dossier' do - expect(rendered).to have_text("Tout le dossier") + expect(rendered).to have_selector('a[title="Tout le dossier"]') end end @@ -45,7 +45,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do end it 'can download the dossier' do - expect(rendered).to have_text("Tout le dossier") + expect(rendered).to have_selector('a[title="Tout le dossier"]') end it 'does not display a new procedure link' do @@ -68,7 +68,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do end it 'can download the dossier' do - expect(rendered).to have_text("Tout le dossier") + expect(rendered).to have_selector('a[title="Tout le dossier"]') end it 'displays a new procedure link' do @@ -105,7 +105,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do end it 'can download the dossier' do - expect(rendered).to have_text("Tout le dossier") + expect(rendered).to have_selector('a[title="Tout le dossier"]') end end @@ -124,7 +124,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do end it 'can not download the dossier' do - expect(rendered).not_to have_text("Tout le dossier") + expect(rendered).not_to have_selector('a[title="Tout le dossier"]') end end end From e1748a26663212e0db2ca0099f19c5bad1a84639 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 31 Jan 2023 16:48:38 +0100 Subject: [PATCH 005/202] Fix "see more" link for prefillable fields --- app/helpers/application_helper.rb | 2 +- app/models/type_de_champ.rb | 1 + app/models/types_de_champ/prefill_repetition_type_de_champ.rb | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 70b116d78..03e393d1d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -142,7 +142,7 @@ module ApplicationHelper end def new_tab_suffix(title) - "#{title} — #{t('utils.new_tab')}" + "#{title} — #{I18n.t('utils.new_tab')}" end def download_details(attachment) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 098a7e8c0..60308633c 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -129,6 +129,7 @@ class TypeDeChamp < ApplicationRecord delegate :estimated_fill_duration, :estimated_read_duration, :tags_for_template, :libelle_for_export, to: :dynamic_type delegate :active_revision, to: :procedure, prefix: true + delegate :path, to: :procedure class WithIndifferentAccess def self.load(options) diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index cdf435f6e..b46809f3e 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -1,10 +1,11 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp include ActionView::Helpers::UrlHelper + include ApplicationHelper def possible_values prefillable_subchamps.map do |prefill_type_de_champ| if prefill_type_de_champ.too_many_possible_values? - link = link_to "Voir toutes les valeurs possibles", Rails.application.routes.url_helpers.prefill_type_de_champ_path("piece-jointe", prefill_type_de_champ) + link = link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(prefill_type_de_champ.path, prefill_type_de_champ), title: ActionController::Base.helpers.sanitize(new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title"))), **external_link_attributes "#{prefill_type_de_champ.libelle}: #{link}" else "#{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values_sentence}" From 4a5e866974b8246c92acf35a0608eee9403c114b Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 31 Jan 2023 17:01:38 +0100 Subject: [PATCH 006/202] Fix problem schema --- db/schema.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 8055c3aea..0d5ffd1d9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -165,7 +165,6 @@ ActiveRecord::Schema.define(version: 2023_01_26_145329) do t.datetime "reminded_at" t.datetime "revoked_at" t.datetime "updated_at", null: false - t.datetime "reminded_at" t.index ["claimant_id"], name: "index_avis_on_claimant_id" t.index ["dossier_id"], name: "index_avis_on_dossier_id" t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id" From 5a7b740f5873c280b198051550abbb1318698433 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 31 Jan 2023 17:06:00 +0100 Subject: [PATCH 007/202] Clean rubocop alerts --- app/models/type_de_champ.rb | 2 +- app/models/types_de_champ/prefill_repetition_type_de_champ.rb | 2 +- app/models/types_de_champ/prefill_type_de_champ.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 60308633c..46293894e 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -272,7 +272,7 @@ class TypeDeChamp < ApplicationRecord TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:drop_down_list), TypeDeChamp.type_champs.fetch(:regions), - TypeDeChamp.type_champs.fetch(:repetition), + TypeDeChamp.type_champs.fetch(:repetition) ]) end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index b46809f3e..e72ce1709 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -14,7 +14,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh end def possible_values_sentence - "#{I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")}
    #{possible_values.join("
    ")}".html_safe + "#{I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")}
    #{possible_values.join("
    ")}".html_safe # rubocop:disable Rails/OutputSafety end def example_value diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 6e481cd88..99017da47 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -38,7 +38,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator def possible_values_sentence if too_many_possible_values? - I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe + I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe # rubocop:disable Rails/OutputSafety else possible_values.to_sentence end From a4645855d3c19539764bf772b6830777d46930fa Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 1 Feb 2023 12:02:09 +0100 Subject: [PATCH 008/202] Fix tests prefillable repetition --- spec/models/type_de_champ_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index e1ca7bac6..5de887616 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -250,6 +250,7 @@ describe TypeDeChamp do it_behaves_like "a prefillable type de champ", :type_de_champ_checkbox it_behaves_like "a prefillable type de champ", :type_de_champ_drop_down_list it_behaves_like "a prefillable type de champ", :type_de_champ_regions + it_behaves_like "a prefillable type de champ", :type_de_champ_repetition it_behaves_like "a non-prefillable type de champ", :type_de_champ_number it_behaves_like "a non-prefillable type de champ", :type_de_champ_communes @@ -260,7 +261,6 @@ describe TypeDeChamp do it_behaves_like "a non-prefillable type de champ", :type_de_champ_header_section it_behaves_like "a non-prefillable type de champ", :type_de_champ_explication it_behaves_like "a non-prefillable type de champ", :type_de_champ_piece_justificative - it_behaves_like "a non-prefillable type de champ", :type_de_champ_repetition it_behaves_like "a non-prefillable type de champ", :type_de_champ_cnaf it_behaves_like "a non-prefillable type de champ", :type_de_champ_dgfip it_behaves_like "a non-prefillable type de champ", :type_de_champ_pole_emploi From e7c78321d96ef680413b147ffbb6dd1e4c8b8a85 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 1 Feb 2023 13:35:32 +0100 Subject: [PATCH 009/202] Fix new failing tests --- app/models/prefill_params.rb | 2 ++ spec/models/types_de_champ/prefill_type_de_champ_spec.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 6f16c1e2e..335cd005b 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -75,6 +75,8 @@ class PrefillParams end def repeatable_hashes + return [] unless value.is_a?(Array) + value.map.with_index do |repetition, index| row = champ.rows[index] || champ.add_row(champ.dossier_revision) JSON.parse(repetition).map do |key, value| diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index af0e3cfc9..f75fc8f1b 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -50,7 +50,7 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do context 'when the type de champ is prefillable' do let(:type_de_champ) { build(:type_de_champ_email) } - it { expect(possible_values).to match([]) } + it { expect(possible_values).to match(["Une adresse email"]) } end end From 63e7c17fdaef0e91c5e838a9ffd3965d0b752bba Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Fri, 3 Feb 2023 12:14:18 +0100 Subject: [PATCH 010/202] First tests for prefill repetition --- .../concerns/dossier_prefillable_concern.rb | 2 +- spec/factories/type_de_champ.rb | 1 + spec/models/champ_spec.rb | 4 ++-- spec/models/dossier_spec.rb | 2 +- .../prefill_type_de_champ_spec.rb | 6 +++++ .../shared_examples_for_prefilled_dossier.rb | 3 +++ spec/system/users/dossier_prefill_get_spec.rb | 22 +++++++++++++++++-- .../system/users/dossier_prefill_post_spec.rb | 14 +++++++++++- 8 files changed, 47 insertions(+), 7 deletions(-) diff --git a/app/models/concerns/dossier_prefillable_concern.rb b/app/models/concerns/dossier_prefillable_concern.rb index 7537a7357..3ecbe7a04 100644 --- a/app/models/concerns/dossier_prefillable_concern.rb +++ b/app/models/concerns/dossier_prefillable_concern.rb @@ -7,7 +7,7 @@ module DossierPrefillableConcern return unless champs_public_attributes.any? attr = { prefilled: true } - attr[:champs_public_all_attributes] = champs_public_attributes.compact.map { |h| h.merge(prefilled: true) } + attr[:champs_public_all_attributes] = champs_public_attributes.map { |h| h.merge(prefilled: true) } assign_attributes(attr) save(validate: false) diff --git a/spec/factories/type_de_champ.rb b/spec/factories/type_de_champ.rb index 1b90fa19f..c67b2f7e3 100644 --- a/spec/factories/type_de_champ.rb +++ b/spec/factories/type_de_champ.rb @@ -198,6 +198,7 @@ FactoryBot.define do parent = revision.revision_types_de_champ.find { |rtdc| rtdc.type_de_champ == type_de_champ_repetition } build(:type_de_champ, procedure: evaluator.procedure, libelle: 'sub type de champ', parent: parent, position: 0) + build(:type_de_champ, type_champ: TypeDeChamp.type_champs.fetch(:integer_number), procedure: evaluator.procedure, libelle: 'sub type de champ2', parent: parent, position: 1) end end end diff --git a/spec/models/champ_spec.rb b/spec/models/champ_spec.rb index 1fe22200f..84bbbbc6d 100644 --- a/spec/models/champ_spec.rb +++ b/spec/models/champ_spec.rb @@ -545,12 +545,12 @@ describe Champ do champ.champs << champ_integer first_row = champ.reload.rows.first - expect(first_row.size).to eq(2) + expect(first_row.size).to eq(3) expect(first_row.second).to eq(champ_integer) champ.champs << champ_text first_row = champ.reload.rows.first - expect(first_row.size).to eq(2) + expect(first_row.size).to eq(3) expect(first_row.first).to eq(champ_text) expect(champ.rows.size).to eq(2) diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 9a684dcb7..f4d94a27b 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1861,7 +1861,7 @@ describe Dossier do let(:champ_repetition) { create(:champ_repetition, type_de_champ: type_de_champ_repetition, dossier: dossier) } before { dossier.champs_public << champ_repetition } - it { expect(Champs::RepetitionChamp.where(dossier: new_dossier).first.champs.count).to eq(2) } + it { expect(Champs::RepetitionChamp.where(dossier: new_dossier).first.champs.count).to eq(4) } it { expect(Champs::RepetitionChamp.where(dossier: new_dossier).first.champs.ids).not_to eq(champ_repetition.champs.ids) } end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index f75fc8f1b..c870b61a9 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -22,6 +22,12 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(built).to be_kind_of(TypesDeChamp::PrefillRegionTypeDeChamp) } end + context 'when the type de champ is a repetition' do + let(:type_de_champ) { build(:type_de_champ_repetition) } + + it { expect(built).to be_kind_of(TypesDeChamp::PrefillRepetitionTypeDeChamp) } + end + context 'when any other type de champ' do let(:type_de_champ) { build(:type_de_champ_date) } diff --git a/spec/support/shared_examples_for_prefilled_dossier.rb b/spec/support/shared_examples_for_prefilled_dossier.rb index 274588a75..78fcf74c7 100644 --- a/spec/support/shared_examples_for_prefilled_dossier.rb +++ b/spec/support/shared_examples_for_prefilled_dossier.rb @@ -18,5 +18,8 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do expect(page).to have_field(type_de_champ_text.libelle, with: text_value) expect(page).to have_field(type_de_champ_phone.libelle, with: phone_value) expect(page).to have_css('label', text: type_de_champ_phone.libelle) + expect(page).to have_css('h3', text: type_de_champ_repetition.libelle) + expect(page).to have_field(text_repetition_libelle, with: text_repetition_value) + expect(page).to have_field(integer_repetition_libelle, with: integer_repetition_value) end end diff --git a/spec/system/users/dossier_prefill_get_spec.rb b/spec/system/users/dossier_prefill_get_spec.rb index d7b9eed35..7be6f739e 100644 --- a/spec/system/users/dossier_prefill_get_spec.rb +++ b/spec/system/users/dossier_prefill_get_spec.rb @@ -6,8 +6,14 @@ describe 'Prefilling a dossier (with a GET request):' do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_phone) { create(:type_de_champ_phone, procedure: procedure) } + let(:type_de_champ_repetition) { create(:type_de_champ_repetition, :with_types_de_champ, procedure: procedure) } let(:text_value) { "My Neighbor Totoro is the best movie ever" } let(:phone_value) { "invalid phone value" } + let(:sub_type_de_champs_repetition) { type_de_champ_repetition.active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ) } + let(:text_repetition_libelle) { sub_type_de_champs_repetition.first.libelle } + let(:integer_repetition_libelle) { sub_type_de_champs_repetition.second.libelle } + let(:text_repetition_value) { "First repetition text" } + let(:integer_repetition_value) { "42" } context 'when authenticated' do it_behaves_like "the user has got a prefilled dossier, owned by themselves" do @@ -20,7 +26,13 @@ describe 'Prefilling a dossier (with a GET request):' do visit commencer_path( path: procedure.path, "champ_#{type_de_champ_text.to_typed_id}" => text_value, - "champ_#{type_de_champ_phone.to_typed_id}" => phone_value + "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, + "champ_#{type_de_champ_repetition.to_typed_id}" => [ + "{ + \"#{sub_type_de_champs_repetition.first.libelle}\": \"#{text_repetition_value}\", + \"#{sub_type_de_champs_repetition.second.libelle}\": \"#{integer_repetition_value}\" + }" + ] ) click_on "Commencer la démarche" @@ -33,7 +45,13 @@ describe 'Prefilling a dossier (with a GET request):' do visit commencer_path( path: procedure.path, "champ_#{type_de_champ_text.to_typed_id}" => text_value, - "champ_#{type_de_champ_phone.to_typed_id}" => phone_value + "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, + "champ_#{type_de_champ_repetition.to_typed_id}" => [ + "{ + \"#{sub_type_de_champs_repetition.first.libelle}\": \"#{text_repetition_value}\", + \"#{sub_type_de_champs_repetition.second.libelle}\": \"#{integer_repetition_value}\" + }" + ] ) end diff --git a/spec/system/users/dossier_prefill_post_spec.rb b/spec/system/users/dossier_prefill_post_spec.rb index 8b495da8d..d95371365 100644 --- a/spec/system/users/dossier_prefill_post_spec.rb +++ b/spec/system/users/dossier_prefill_post_spec.rb @@ -6,8 +6,14 @@ describe 'Prefilling a dossier (with a POST request):' do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_phone) { create(:type_de_champ_phone, procedure: procedure) } + let(:type_de_champ_repetition) { create(:type_de_champ_repetition, :with_types_de_champ, procedure: procedure) } let(:text_value) { "My Neighbor Totoro is the best movie ever" } let(:phone_value) { "invalid phone value" } + let(:sub_type_de_champs_repetition) { type_de_champ_repetition.active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ) } + let(:text_repetition_libelle) { sub_type_de_champs_repetition.first.libelle } + let(:integer_repetition_libelle) { sub_type_de_champs_repetition.second.libelle } + let(:text_repetition_value) { "First repetition text" } + let(:integer_repetition_value) { "42" } scenario "the user get the URL of a prefilled orphan brouillon dossier" do dossier_url = create_and_prefill_dossier_with_post_request @@ -95,7 +101,13 @@ describe 'Prefilling a dossier (with a POST request):' do headers: { "Content-Type" => "application/json" }, params: { "champ_#{type_de_champ_text.to_typed_id}" => text_value, - "champ_#{type_de_champ_phone.to_typed_id}" => phone_value + "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, + "champ_#{type_de_champ_repetition.to_typed_id}" => [ + "{ + \"#{sub_type_de_champs_repetition.first.libelle}\": \"#{text_repetition_value}\", + \"#{sub_type_de_champs_repetition.second.libelle}\": \"#{integer_repetition_value}\" + }" + ] }.to_json JSON.parse(session.response.body)["dossier_url"].gsub("http://www.example.com", "") end From b9c2d1251b5256996016d48d3739b2bd740d0672 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Mon, 6 Feb 2023 17:38:59 +0100 Subject: [PATCH 011/202] Add missing tests repetition prefill --- config/locales/en.yml | 1 + config/locales/fr.yml | 1 + spec/factories/type_de_champ.rb | 9 ++++ spec/models/prefill_description_spec.rb | 20 +++++---- spec/models/prefill_params_spec.rb | 2 +- .../prefill_repetition_type_de_champ_spec.rb | 41 +++++++++++++++++++ .../prefill_type_de_champ_spec.rb | 23 +++++++++++ 7 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb diff --git a/config/locales/en.yml b/config/locales/en.yml index ed8ed14c7..b47b1f5e8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -128,6 +128,7 @@ en: checkbox_html: '"true" to check, "false" to uncheck' pays_html: An ISO 3166-2 country code regions_html: An INSEE region code + drop_down_list_html: A choice among those selected when the procedure was created date_html: ISO8601 date datetime_html: ISO8601 datetime drop_down_list_other_html: Any value diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 3c37f96b5..5d6b6e3ef 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -119,6 +119,7 @@ fr: checkbox_html: '"true" pour coché, "false" pour décoché' pays_html: Un code pays ISO 3166-2 regions_html: Un code INSEE de région + drop_down_list_html: Un choix parmi ceux sélectionnés à la création de la procédure datetime_html: Datetime au format ISO8601 date_html: Date au format ISO8601 drop_down_list_other_html: Toute valeur diff --git a/spec/factories/type_de_champ.rb b/spec/factories/type_de_champ.rb index c67b2f7e3..f538ef10f 100644 --- a/spec/factories/type_de_champ.rb +++ b/spec/factories/type_de_champ.rb @@ -201,6 +201,15 @@ FactoryBot.define do build(:type_de_champ, type_champ: TypeDeChamp.type_champs.fetch(:integer_number), procedure: evaluator.procedure, libelle: 'sub type de champ2', parent: parent, position: 1) end end + + trait :with_region_types_de_champ do + after(:build) do |type_de_champ_repetition, evaluator| + revision = evaluator.procedure.active_revision + parent = revision.revision_types_de_champ.find { |rtdc| rtdc.type_de_champ == type_de_champ_repetition } + + build(:type_de_champ, type_champ: TypeDeChamp.type_champs.fetch(:regions), procedure: evaluator.procedure, libelle: 'region sub_champ', parent: parent, position: 10) + end + end end end end diff --git a/spec/models/prefill_description_spec.rb b/spec/models/prefill_description_spec.rb index 8029d2834..362a13c85 100644 --- a/spec/models/prefill_description_spec.rb +++ b/spec/models/prefill_description_spec.rb @@ -88,36 +88,42 @@ RSpec.describe PrefillDescription, type: :model do end end - describe '#prefill_link' do + describe '#prefill_link', vcr: { cassette_name: 'api_geo_regions' } do let(:procedure) { create(:procedure) } let(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) } + let(:type_de_champ_repetition) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } + let(:region_repetition) { prefillable_subchamps.third } let(:prefill_description) { described_class.new(procedure) } - before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id]) } + before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id, type_de_champ_repetition.id]) } it "builds the URL to create a new prefilled dossier" do expect(prefill_description.prefill_link).to eq( commencer_url( path: procedure.path, - "champ_#{type_de_champ.to_typed_id}" => I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}") + "champ_#{type_de_champ.to_typed_id}" => I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}"), + "champ_#{type_de_champ_repetition.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value ) ) end end - describe '#prefill_query' do + describe '#prefill_query', vcr: { cassette_name: 'api_geo_regions' } do let(:procedure) { create(:procedure) } let(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) } + let(:type_de_champ_repetition) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } + let(:region_repetition) { prefillable_subchamps.third } let(:prefill_description) { described_class.new(procedure) } let(:expected_query) do <<~TEXT curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ --header 'Content-Type: application/json' \\ - --data '{"champ_#{type_de_champ.to_typed_id}": "#{I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")}"}' + --data '{"champ_#{type_de_champ.to_typed_id}": "#{I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")}", "champ_#{type_de_champ_repetition.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value}}' TEXT end - - before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id]) } + before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id, type_de_champ_repetition.id]) } it "builds the query to create a new prefilled dossier" do expect(prefill_description.prefill_query).to eq(expected_query) diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 84c99a26b..5be3f1db8 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -144,6 +144,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :checkbox, "false" it_behaves_like "a champ private value that is authorized", :drop_down_list, "value" it_behaves_like "a champ private value that is authorized", :regions, "93" + it_behaves_like "a champ public value that is unauthorized", :repetition, "[{\"name\":\"value\"}, {\"name\":\"value2\"}]" it_behaves_like "a champ public value that is unauthorized", :decimal_number, "non decimal string" it_behaves_like "a champ public value that is unauthorized", :integer_number, "non integer string" @@ -160,7 +161,6 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is unauthorized", :header_section, "value" it_behaves_like "a champ public value that is unauthorized", :explication, "value" it_behaves_like "a champ public value that is unauthorized", :piece_justificative, "value" - it_behaves_like "a champ public value that is unauthorized", :repetition, "value" it_behaves_like "a champ public value that is unauthorized", :cnaf, "value" it_behaves_like "a champ public value that is unauthorized", :dgfip, "value" it_behaves_like "a champ public value that is unauthorized", :pole_emploi, "value" diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb new file mode 100644 index 000000000..cf3b8886b --- /dev/null +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { cassette_name: 'api_geo_regions' } do + let(:procedure) { build(:procedure) } + let(:type_de_champ) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ).send(:prefillable_subchamps) } + let(:region_repetition) { prefillable_subchamps.third } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + + describe 'ancestors' do + subject { described_class.build(type_de_champ) } + + it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } + end + + describe '#possible_values' do + subject(:possible_values) { described_class.new(type_de_champ).possible_values } + let(:expected_value) { ["sub type de champ: Un texte court", "sub type de champ2: Un nombre entier", "region sub_champ: Voir toutes les valeurs possibles"] } + + it { expect(possible_values).to eq(expected_value) } + end + + describe '#example_value' do + subject(:example_value) { described_class.new(type_de_champ).example_value } + let(:expected_value) { ["{\"sub type de champ\":\"Texte court\", \"sub type de champ2\":\"42\", \"region sub_champ\":\"53\"}", "{\"sub type de champ\":\"Texte court\", \"sub type de champ2\":\"42\", \"region sub_champ\":\"53\"}"] } + + it { expect(example_value).to eq(expected_value) } + end + + describe '#possible_values_sentence' do + subject(:possible_values_sentence) { described_class.new(type_de_champ).possible_values_sentence } + let(:expected_value) { "Un array de hash avec les valeurs possibles pour chaque champ de la répétition.
    sub type de champ: Un texte court
    sub type de champ2: Un nombre entier
    region sub_champ: Voir toutes les valeurs possibles" } + + it { expect(possible_values_sentence).to eq(expected_value) } + end +end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index c870b61a9..9c38b49e1 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -60,6 +60,29 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do end end + describe '#possible_values_sentence' do + subject(:possible_values_sentence) { described_class.build(type_de_champ).possible_values_sentence } + + context 'when the type de champ is not prefillable' do + let(:type_de_champ) { build(:type_de_champ_mesri) } + + it { expect(possible_values_sentence).to be_empty } + end + + context 'when there is too many possible values' do + let(:type_de_champ) { build(:type_de_champ_drop_down_list) } + before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD + 1).map(&:to_s) } + + it { expect(possible_values_sentence).to match("Un choix parmi ceux sélectionnés à la création de la procédure") } + end + + context 'when the type de champ is prefillable' do + let(:type_de_champ) { build(:type_de_champ_email) } + + it { expect(possible_values_sentence).to match("Une adresse email") } + end + end + describe '#example_value' do subject(:example_value) { described_class.build(type_de_champ).example_value } From a51ed0094bc0a30b3efe62ee8673aa8a079453d6 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 7 Feb 2023 00:28:21 +0100 Subject: [PATCH 012/202] Fix tests --- .../api/v1/dossiers_controller_spec.rb | 6 ++-- ...0220705164551_remove_unused_champs_spec.rb | 4 +-- spec/models/champ_spec.rb | 10 ++---- spec/models/procedure_revision_spec.rb | 35 ++++++++++--------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/spec/controllers/api/v1/dossiers_controller_spec.rb b/spec/controllers/api/v1/dossiers_controller_spec.rb index af705addd..23ba99781 100644 --- a/spec/controllers/api/v1/dossiers_controller_spec.rb +++ b/spec/controllers/api/v1/dossiers_controller_spec.rb @@ -279,9 +279,9 @@ describe API::V1::DossiersController do it 'should have rows' do expect(subject.size).to eq(2) expect(subject[0][:id]).to eq(1) - expect(subject[0][:champs].size).to eq(1) - expect(subject[0][:champs].map { |c| c[:value] }).to eq(['text']) - expect(subject[0][:champs].map { |c| c[:type_de_champ][:type_champ] }).to eq(['text']) + expect(subject[0][:champs].size).to eq(2) + expect(subject[0][:champs].map { |c| c[:value] }).to eq(['text', 42]) + expect(subject[0][:champs].map { |c| c[:type_de_champ][:type_champ] }).to eq(['text', 'integer_number']) end end end diff --git a/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb b/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb index bd0180fd7..29161bc24 100644 --- a/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb +++ b/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb @@ -21,9 +21,9 @@ describe '20220705164551_remove_unused_champs' do describe 'remove_unused_champs', vcr: { cassette_name: 'api_geo_all' } do it "with bad champs" do - expect(Champ.where(dossier: dossier).count).to eq(38) + expect(Champ.where(dossier: dossier).count).to eq(40) run_task - expect(Champ.where(dossier: dossier).count).to eq(37) + expect(Champ.where(dossier: dossier).count).to eq(39) end end end diff --git a/spec/models/champ_spec.rb b/spec/models/champ_spec.rb index 84bbbbc6d..622d62cb4 100644 --- a/spec/models/champ_spec.rb +++ b/spec/models/champ_spec.rb @@ -510,10 +510,6 @@ describe Champ do let(:champ_integer) { champ.champs.find { |c| c.type_champ == 'integer_number' } } let(:champ_text_attrs) { attributes_for(:champ_text, type_de_champ: tdc_text, row_id: ULID.generate) } - before do - procedure.active_revision.add_type_de_champ(libelle: 'sub integer', type_champ: 'integer_number', parent_stable_id: tdc_repetition.stable_id) - end - context 'when creating the model directly' do let(:champ_text_row_1) { create(:champ_text, type_de_champ: tdc_text, row_id: ULID.generate, parent: champ, dossier: nil) } @@ -539,18 +535,18 @@ describe Champ do expect(dossier.champs_public.size).to eq(2) expect(champ.rows.size).to eq(2) - second_row = champ.rows.second + second_row = champ.reload.rows.second expect(second_row.size).to eq(1) expect(second_row.first.dossier).to eq(dossier) champ.champs << champ_integer first_row = champ.reload.rows.first - expect(first_row.size).to eq(3) + expect(first_row.size).to eq(2) expect(first_row.second).to eq(champ_integer) champ.champs << champ_text first_row = champ.reload.rows.first - expect(first_row.size).to eq(3) + expect(first_row.size).to eq(2) expect(first_row.first).to eq(champ_text) expect(champ.rows.size).to eq(2) diff --git a/spec/models/procedure_revision_spec.rb b/spec/models/procedure_revision_spec.rb index 9f3277267..7b85c0d39 100644 --- a/spec/models/procedure_revision_spec.rb +++ b/spec/models/procedure_revision_spec.rb @@ -134,15 +134,15 @@ describe ProcedureRevision do end it 'move down' do - expect(draft.children_of(type_de_champ_repetition).index(second_child)).to eq(1) - - draft.move_type_de_champ(second_child.stable_id, 2) - expect(draft.children_of(type_de_champ_repetition).index(second_child)).to eq(2) + + draft.move_type_de_champ(second_child.stable_id, 3) + + expect(draft.children_of(type_de_champ_repetition).index(second_child)).to eq(3) end it 'move up' do - expect(draft.children_of(type_de_champ_repetition).index(last_child)).to eq(2) + expect(draft.children_of(type_de_champ_repetition).index(last_child)).to eq(3) draft.move_type_de_champ(last_child.stable_id, 0) @@ -205,13 +205,13 @@ describe ProcedureRevision do it 'reorders' do children = draft.children_of(type_de_champ_repetition) - expect(children.pluck(:position)).to eq([0, 1, 2]) + expect(children.pluck(:position)).to eq([0, 1, 2, 3]) draft.remove_type_de_champ(children[1].stable_id) children.reload - expect(children.pluck(:position)).to eq([0, 1]) + expect(children.pluck(:position)).to eq([0, 1, 2]) end end end @@ -242,8 +242,8 @@ describe ProcedureRevision do new_draft.remove_type_de_champ(child.stable_id) expect { child.reload }.not_to raise_error - expect(draft.children_of(type_de_champ_repetition).size).to eq(1) - expect(new_draft.children_of(type_de_champ_repetition)).to be_empty + expect(draft.children_of(type_de_champ_repetition).size).to eq(2) + expect(new_draft.children_of(type_de_champ_repetition).size).to eq(1) end it 'can remove the parent only in the new revision' do @@ -291,7 +291,7 @@ describe ProcedureRevision do let(:procedure) { create(:procedure, :with_repetition) } it 'should have the same tdcs with different links' do - expect(new_draft.types_de_champ.count).to eq(2) + expect(new_draft.types_de_champ.count).to eq(3) expect(new_draft.types_de_champ).to eq(draft.types_de_champ) new_repetition, new_child = new_draft.types_de_champ.partition(&:repetition?).map(&:first) @@ -320,7 +320,7 @@ describe ProcedureRevision do it do expect(procedure.revisions.size).to eq(2) - expect(draft.revision_types_de_champ.where.not(parent_id: nil).size).to eq(1) + expect(draft.revision_types_de_champ.where.not(parent_id: nil).size).to eq(2) end end end @@ -639,9 +639,10 @@ describe ProcedureRevision do context 'with a repetition tdc' do let(:procedure) { create(:procedure, :with_repetition) } let!(:parent) { draft.types_de_champ.find(&:repetition?) } - let!(:child) { draft.types_de_champ.reject(&:repetition?).first } + let!(:first_child) { draft.types_de_champ.reject(&:repetition?).first } + let!(:second_child) { draft.types_de_champ.reject(&:repetition?).second } - it { expect(draft.children_of(parent)).to match([child]) } + it { expect(draft.children_of(parent)).to match([first_child, second_child]) } context 'with multiple child' do let(:child_position_2) { create(:type_de_champ_text) } @@ -654,7 +655,7 @@ describe ProcedureRevision do end it 'returns the children in order' do - expect(draft.children_of(parent)).to eq([child, child_position_1, child_position_2]) + expect(draft.children_of(parent)).to eq([first_child, second_child, child_position_1, child_position_2]) end end @@ -668,13 +669,13 @@ describe ProcedureRevision do before do new_draft .revision_types_de_champ - .where(type_de_champ: child) + .where(type_de_champ: first_child) .update(type_de_champ: new_child) end it 'returns the children regarding the revision' do - expect(draft.children_of(parent)).to match([child]) - expect(new_draft.children_of(parent)).to match([new_child]) + expect(draft.children_of(parent)).to match([first_child, second_child]) + expect(new_draft.children_of(parent)).to match([new_child, second_child]) end end end From c5f1f80d25b20b73669a558359e7fa2ce94a9d5f Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 8 Feb 2023 17:38:51 +0100 Subject: [PATCH 013/202] Add prefill params tests repetition --- app/models/prefill_params.rb | 2 +- spec/models/prefill_params_spec.rb | 53 +++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 335cd005b..591b25fdf 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -83,7 +83,7 @@ class PrefillParams id = row.find { |champ| champ.libelle == key }&.id next unless id { id: id, value: value } - end + end.compact rescue JSON::ParserError end.compact end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 5be3f1db8..dc3108efe 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -127,6 +127,20 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is authorized", :drop_down_list, "value" it_behaves_like "a champ public value that is authorized", :regions, "03" + context "when the public type de champ is authorized (repetition)" do + let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :text }] }] } + let(:type_de_champ) { procedure.published_revision.types_de_champ_public.first } + let(:type_de_champ_child) { procedure.published_revision.children_of(type_de_champ).first } + let(:type_de_champ_child_value) { "value" } + let(:type_de_champ_child_value2) { "value2" } + + let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value}\"}", "{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value2}\"}"] } } + + it "builds an array of hash(id, value) matching the given params" do + expect(prefill_params_array).to match([{ id: type_de_champ_child.champ.first.id, value: type_de_champ_child_value }, { id: type_de_champ_child.champ.second.id, value: type_de_champ_child_value2 }]) + end + end + it_behaves_like "a champ private value that is authorized", :text, "value" it_behaves_like "a champ private value that is authorized", :textarea, "value" it_behaves_like "a champ private value that is authorized", :decimal_number, "3.14" @@ -144,7 +158,20 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :checkbox, "false" it_behaves_like "a champ private value that is authorized", :drop_down_list, "value" it_behaves_like "a champ private value that is authorized", :regions, "93" - it_behaves_like "a champ public value that is unauthorized", :repetition, "[{\"name\":\"value\"}, {\"name\":\"value2\"}]" + + context "when the private type de champ is authorized (repetition)" do + let(:types_de_champ_private) { [{ type: :repetition, children: [{ type: :text }] }] } + let(:type_de_champ) { procedure.published_revision.types_de_champ_private.first } + let(:type_de_champ_child) { procedure.published_revision.children_of(type_de_champ).first } + let(:type_de_champ_child_value) { "value" } + let(:type_de_champ_child_value2) { "value2" } + + let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value}\"}", "{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value2}\"}"] } } + + it "builds an array of hash(id, value) matching the given params" do + expect(prefill_params_array).to match([{ id: type_de_champ_child.champ.first.id, value: type_de_champ_child_value }, { id: type_de_champ_child.champ.second.id, value: type_de_champ_child_value2 }]) + end + end it_behaves_like "a champ public value that is unauthorized", :decimal_number, "non decimal string" it_behaves_like "a champ public value that is unauthorized", :integer_number, "non integer string" @@ -173,6 +200,30 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is unauthorized", :siret, "value" it_behaves_like "a champ public value that is unauthorized", :rna, "value" it_behaves_like "a champ public value that is unauthorized", :annuaire_education, "value" + + context "when the public type de champ is unauthorized because of wrong value format (repetition)" do + let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :text }] }] } + let(:type_de_champ) { procedure.published_revision.types_de_champ_public.first } + let(:type_de_champ_child) { procedure.published_revision.children_of(type_de_champ).first } + + let(:params) { { "champ_#{type_de_champ.to_typed_id}" => "value" } } + + it "builds an array of hash(id, value) matching the given params" do + expect(prefill_params_array).to match([]) + end + end + + context "when the public type de champ is unauthorized because of wrong value libelle (repetition)" do + let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :text }] }] } + let(:type_de_champ) { procedure.published_revision.types_de_champ_public.first } + let(:type_de_champ_child) { procedure.published_revision.children_of(type_de_champ).first } + + let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"wrong\":\"value\"}", "{\"wrong\":\"value2\"}"] } } + + it "builds an array of hash(id, value) matching the given params" do + expect(prefill_params_array).to match([]) + end + end end private From 5526cb5173d1c4cd70c5a459e9d66f90471ddf4c Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 9 Feb 2023 17:39:56 +0100 Subject: [PATCH 014/202] fix(mail): limit generated subjects to 100 characters --- app/mailers/notification_mailer.rb | 3 ++- config/locales/en.yml | 13 +++++++++++++ config/locales/fr.yml | 13 +++++++++++++ spec/mailers/notification_mailer_spec.rb | 17 +++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 89f79c1db..8f8bb6c57 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -7,6 +7,7 @@ # class NotificationMailer < ApplicationMailer include ActionView::Helpers::SanitizeHelper + include ActionView::Helpers::TextHelper before_action :set_dossier before_action :set_services_publics_plus, only: :send_notification @@ -67,7 +68,7 @@ class NotificationMailer < ApplicationMailer mail_template = @dossier.procedure.mail_template_for(params[:state]) @email = @dossier.user_email_for(:notification) - @subject = mail_template.subject_for_dossier(@dossier) + @subject = truncate(mail_template.subject_for_dossier(@dossier), length: 100) @body = mail_template.body_for_dossier(@dossier) @actions = mail_template.actions_for_dossier(@dossier) @attachment = mail_template.attachment_for_dossier(@dossier) diff --git a/config/locales/en.yml b/config/locales/en.yml index 17d80a709..dc8be05ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -395,6 +395,19 @@ en: zone: This procedure is run by champs: value: Value + default_mail_attributes: &default_mail_attributes + hints: + subject: The generated subject will be truncated if it exceeds 100 characters. + mails/closed_mail: + << : *default_mail_attributes + mails/initiated_mail: + << : *default_mail_attributes + mails/received_mail: + << : *default_mail_attributes + mails/refused_mail: + << : *default_mail_attributes + mails/without_continuation_mail: + << : *default_mail_attributes errors: messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index efd5e4d63..d4b864c28 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -392,6 +392,19 @@ fr: zone: La démarche est mise en œuvre par champs: value: Valeur du champ + default_mail_attributes: &default_mail_attributes + hints: + subject: "L’objet généré sera tronqué s’il dépasse 100 caractères." + mails/closed_mail: + << : *default_mail_attributes + mails/initiated_mail: + << : *default_mail_attributes + mails/received_mail: + << : *default_mail_attributes + mails/refused_mail: + << : *default_mail_attributes + mails/without_continuation_mail: + << : *default_mail_attributes errors: messages: diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index 005db343c..4fd8ae6cd 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -101,4 +101,21 @@ RSpec.describe NotificationMailer, type: :mailer do end end end + + describe 'subject length' do + let(:procedure) { create(:simple_procedure, libelle: "My super long title " + ("xo " * 100)) } + let(:dossier) { create(:dossier, :en_instruction, :with_individual, :with_service, user: user, procedure: procedure) } + let(:email_template) { create(:closed_mail, subject:, body: 'Your dossier was accepted. Thanks.') } + + before do + dossier.procedure.closed_mail = email_template + end + + subject(:mail) { described_class.send_accepte_notification(dossier) } + + context "subject is too long" do + let(:subject) { 'Un long libellé --libellé démarche--' } + it { expect(mail.subject.length).to be <= 100 } + end + end end From aeb602a63d5fe716a004f205f7ca83e17274ed16 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 9 Feb 2023 17:49:43 +0100 Subject: [PATCH 015/202] fix(mail): default subject fallback when subject template is empty Closes #8448 --- app/models/concerns/mail_template_concern.rb | 2 +- spec/mailers/notification_mailer_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/mail_template_concern.rb b/app/models/concerns/mail_template_concern.rb index e778c681d..a5035bbd8 100644 --- a/app/models/concerns/mail_template_concern.rb +++ b/app/models/concerns/mail_template_concern.rb @@ -10,7 +10,7 @@ module MailTemplateConcern end def subject_for_dossier(dossier) - replace_tags(subject, dossier) + replace_tags(subject, dossier).presence || replace_tags(self.class::DEFAULT_SUBJECT, dossier) end def body_for_dossier(dossier) diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index 4fd8ae6cd..25e77eb4d 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -117,5 +117,11 @@ RSpec.describe NotificationMailer, type: :mailer do let(:subject) { 'Un long libellé --libellé démarche--' } it { expect(mail.subject.length).to be <= 100 } end + + context "subject should fallback to default" do + let(:subject) { "" } + it { expect(mail.subject).to match(/^Votre dossier .+ a été accepté \(My super long title/) } + it { expect(mail.subject.length).to be <= 100 } + end end end From a4d707f942814d17c0f5a2827e22e4134bb67cf9 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Thu, 9 Feb 2023 18:08:51 +0100 Subject: [PATCH 016/202] Improve format prefill possible_values (tests missing) --- .../prefill_drop_down_list_type_de_champ.rb | 4 +- .../prefill_pays_type_de_champ.rb | 2 +- .../prefill_region_type_de_champ.rb | 2 +- .../prefill_repetition_type_de_champ.rb | 26 +++++++------ .../types_de_champ/prefill_type_de_champ.rb | 23 +++++++++-- .../_types_de_champs.html.haml | 5 +-- .../prefill_type_de_champs/show.html.haml | 2 +- config/locales/fr.yml | 2 +- .../prefill_repetition_type_de_champ_spec.rb | 2 +- .../prefill_type_de_champ_spec.rb | 39 ++----------------- 10 files changed, 45 insertions(+), 62 deletions(-) diff --git a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb index 7e3a4ad22..648f2d17e 100644 --- a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values + def possible_values_list if drop_down_other? drop_down_list_enabled_non_empty_options.insert( 0, @@ -11,6 +11,6 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDe end def example_value - possible_values.first + possible_values_list.first end end diff --git a/app/models/types_de_champ/prefill_pays_type_de_champ.rb b/app/models/types_de_champ/prefill_pays_type_de_champ.rb index 143f041e7..a5e6aad21 100644 --- a/app/models/types_de_champ/prefill_pays_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_pays_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillPaysTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values + def possible_values_list countries.map { |country| "#{country[:code]} (#{country[:name]})" } end diff --git a/app/models/types_de_champ/prefill_region_type_de_champ.rb b/app/models/types_de_champ/prefill_region_type_de_champ.rb index 3084206a1..7d52601b4 100644 --- a/app/models/types_de_champ/prefill_region_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_region_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillRegionTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values + def possible_values_list regions.map { |region| "#{region[:code]} (#{region[:name]})" } end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index e72ce1709..906451d79 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -3,24 +3,26 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh include ApplicationHelper def possible_values - prefillable_subchamps.map do |prefill_type_de_champ| - if prefill_type_de_champ.too_many_possible_values? - link = link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(prefill_type_de_champ.path, prefill_type_de_champ), title: ActionController::Base.helpers.sanitize(new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title"))), **external_link_attributes - "#{prefill_type_de_champ.libelle}: #{link}" - else - "#{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values_sentence}" - end - end + [ + I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html"), + subchamps_possible_values_list + ].join("
    ").html_safe # rubocop:disable Rails/OutputSafety end - def possible_values_sentence - "#{I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")}
    #{possible_values.join("
    ")}".html_safe # rubocop:disable Rails/OutputSafety + def subchamps_possible_values_list + "
      " + prefillable_subchamps.map do |prefill_type_de_champ| + "
    • #{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values}
    • " + end.join + "
    " end def example_value [row_values_format, row_values_format].map { |row| row.to_s.gsub("=>", ":") } end + def too_many_possible_values? + false + end + private def row_values_format @@ -33,6 +35,8 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh def prefillable_subchamps return [] unless active_revision_type_de_champ - TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ).filter(&:prefillable?)) + @prefillable_subchamps ||= + TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ).filter(&:prefillable?)) end + end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 99017da47..cc3776880 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -1,5 +1,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator - POSSIBLE_VALUES_THRESHOLD = 5 + include ActionView::Helpers::UrlHelper + include ApplicationHelper + + POSSIBLE_VALUES_THRESHOLD = 1 def self.build(type_de_champ) case type_de_champ.type_champ @@ -21,11 +24,21 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator end def possible_values + [possible_values_list_display, link_to_all_possible_values].compact.join('
    ').html_safe + end + + def possible_values_list return [] unless prefillable? [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] end + def link_to_all_possible_values + return unless too_many_possible_values? + + link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes + end + def example_value return nil unless prefillable? @@ -33,14 +46,16 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator end def too_many_possible_values? - possible_values.count > POSSIBLE_VALUES_THRESHOLD + possible_values_list.count > POSSIBLE_VALUES_THRESHOLD end - def possible_values_sentence + private + + def possible_values_list_display if too_many_possible_values? I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe # rubocop:disable Rails/OutputSafety else - possible_values.to_sentence + possible_values_list.to_sentence end end end diff --git a/app/views/prefill_descriptions/_types_de_champs.html.haml b/app/views/prefill_descriptions/_types_de_champs.html.haml index b668fc17d..c31804d54 100644 --- a/app/views/prefill_descriptions/_types_de_champs.html.haml +++ b/app/views/prefill_descriptions/_types_de_champs.html.haml @@ -39,10 +39,7 @@ %th = t("views.prefill_descriptions.edit.possible_values.title") %td - = type_de_champ.possible_values_sentence - %br - - if type_de_champ.too_many_possible_values? - = link_to t("views.prefill_descriptions.edit.possible_values.link.text"), prefill_type_de_champ_path(prefill_description.path, type_de_champ), title: new_tab_suffix(t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes + = type_de_champ.possible_values %tr{ class: prefillable ? "" : "fr-text-mention--grey" } %th = t("views.prefill_descriptions.edit.examples.title") diff --git a/app/views/prefill_type_de_champs/show.html.haml b/app/views/prefill_type_de_champs/show.html.haml index bad0c9deb..8a1610340 100644 --- a/app/views/prefill_type_de_champs/show.html.haml +++ b/app/views/prefill_type_de_champs/show.html.haml @@ -26,7 +26,7 @@ = t("views.prefill_descriptions.edit.possible_values.title") %td .fr-grid-row.fr-grid-row--gutters.fr-py-5w - - @type_de_champ.possible_values.each do |possible_value| + - @type_de_champ.possible_values_list.each do |possible_value| .fr-col-lg-3.fr-col-md-4.fr-col-sm-6.fr-col-12 = possible_value %tr diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5d6b6e3ef..f1cc424c7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -123,7 +123,7 @@ fr: datetime_html: Datetime au format ISO8601 date_html: Date au format ISO8601 drop_down_list_other_html: Toute valeur - repetition_html: Un array de hash avec les valeurs possibles pour chaque champ de la répétition. + repetition_html: Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition. examples: title: Exemple text: Texte court diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index cf3b8886b..0598e0740 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -34,7 +34,7 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { describe '#possible_values_sentence' do subject(:possible_values_sentence) { described_class.new(type_de_champ).possible_values_sentence } - let(:expected_value) { "Un array de hash avec les valeurs possibles pour chaque champ de la répétition.
    sub type de champ: Un texte court
    sub type de champ2: Un nombre entier
    region sub_champ: Voir toutes les valeurs possibles" } + let(:expected_value) { "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
    sub type de champ: Un texte court
    sub type de champ2: Un nombre entier
    region sub_champ: Voir toutes les valeurs possibles" } it { expect(possible_values_sentence).to eq(expected_value) } end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 9c38b49e1..3a8686c6c 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -53,33 +53,17 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(possible_values).to be_empty } end - context 'when the type de champ is prefillable' do - let(:type_de_champ) { build(:type_de_champ_email) } - - it { expect(possible_values).to match(["Une adresse email"]) } - end - end - - describe '#possible_values_sentence' do - subject(:possible_values_sentence) { described_class.build(type_de_champ).possible_values_sentence } - - context 'when the type de champ is not prefillable' do - let(:type_de_champ) { build(:type_de_champ_mesri) } - - it { expect(possible_values_sentence).to be_empty } - end - context 'when there is too many possible values' do - let(:type_de_champ) { build(:type_de_champ_drop_down_list) } + let(:type_de_champ) { create(:type_de_champ_drop_down_list) } before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD + 1).map(&:to_s) } - it { expect(possible_values_sentence).to match("Un choix parmi ceux sélectionnés à la création de la procédure") } + it { expect(possible_values).to match("Un choix parmi ceux sélectionnés à la création de la procédure") } end context 'when the type de champ is prefillable' do let(:type_de_champ) { build(:type_de_champ_email) } - it { expect(possible_values_sentence).to match("Une adresse email") } + it { expect(possible_values).to match("Une adresse email") } end end @@ -98,21 +82,4 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(example_value).to eq(I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")) } end end - - describe '#too_many_possible_values?' do - let(:type_de_champ) { build(:type_de_champ_drop_down_list) } - subject(:too_many_possible_values) { described_class.build(type_de_champ).too_many_possible_values? } - - context 'when there are too many possible values' do - before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD + 1).map(&:to_s) } - - it { expect(too_many_possible_values).to eq(true) } - end - - context 'when there are not too many possible values' do - before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD).map(&:to_s) } - - it { expect(too_many_possible_values).to eq(false) } - end - end end From d5f40cfd964f707b95544ce9b0b5da1871eb134f Mon Sep 17 00:00:00 2001 From: Fabrice Gangler Date: Fri, 10 Feb 2023 12:40:38 +0100 Subject: [PATCH 017/202] docs: add DATAGOUV_STATISTICS_DATASET environment variable to env.example.optional file Refs: #8615 --- config/env.example.optional | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/config/env.example.optional b/config/env.example.optional index c3924f529..63d32c047 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -134,14 +134,16 @@ VITE_LEGACY="" # around july 2022, we changed the duree_conservation_dossiers_dans_ds, allow instances to choose their own duration NEW_MAX_DUREE_CONSERVATION=12 -# -OPENDATA_ENABLED="enabled" -# Publish to datagouv +# Open data +OPENDATA_ENABLED="enabled" # disabled by default if `OPENDATA_ENABLED` not set + +# Open data, publish to data.gouv.fr DATAGOUV_API_KEY="thisisasecret" DATAGOUV_API_URL="https://www.data.gouv.fr/api/1" -DATAGOUV_DESCRIPTIF_DEMARCHES_DATASET="datasetid" -DATAGOUV_DESCRIPTIF_DEMARCHES_RESOURCE="resourceid" +DATAGOUV_STATISTICS_DATASET="dataset-id1" +DATAGOUV_DESCRIPTIF_DEMARCHES_DATASET="dataset-id2" +DATAGOUV_DESCRIPTIF_DEMARCHES_RESOURCE="resource-id-of-dataset-id2" # Zonage ZONAGE_ENABLED='enabled' # zonage disabled by default if `ZONAGE_ENABLED` not set From 2a3ba283438a72e3f7a9ff725f4eb64b3171b8d2 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Sat, 11 Feb 2023 22:27:16 +0100 Subject: [PATCH 018/202] Fix tests possible value prefill --- .../prefill_drop_down_list_type_de_champ.rb | 10 +++--- .../prefill_pays_type_de_champ.rb | 8 ++--- .../prefill_region_type_de_champ.rb | 4 +-- .../prefill_repetition_type_de_champ.rb | 13 ++++---- .../types_de_champ/prefill_type_de_champ.rb | 32 +++++++++---------- ...efill_drop_down_list_type_de_champ_spec.rb | 11 ++++--- .../prefill_pays_type_de_champ_spec.rb | 11 +++++-- .../prefill_region_type_de_champ_spec.rb | 11 +++++-- .../prefill_repetition_type_de_champ_spec.rb | 15 ++++----- .../prefill_type_de_champ_spec.rb | 8 ++--- 10 files changed, 68 insertions(+), 55 deletions(-) diff --git a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb index 648f2d17e..b5505151f 100644 --- a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb @@ -1,4 +1,10 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + def example_value + possible_values_list.first + end + + private + def possible_values_list if drop_down_other? drop_down_list_enabled_non_empty_options.insert( @@ -9,8 +15,4 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDe drop_down_list_enabled_non_empty_options end end - - def example_value - possible_values_list.first - end end diff --git a/app/models/types_de_champ/prefill_pays_type_de_champ.rb b/app/models/types_de_champ/prefill_pays_type_de_champ.rb index a5e6aad21..cf0fa0278 100644 --- a/app/models/types_de_champ/prefill_pays_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_pays_type_de_champ.rb @@ -1,14 +1,14 @@ class TypesDeChamp::PrefillPaysTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values_list - countries.map { |country| "#{country[:code]} (#{country[:name]})" } - end - def example_value countries.pick(:code) end private + def possible_values_list + countries.map { |country| "#{country[:code]} (#{country[:name]})" } + end + def countries @countries ||= APIGeoService.countries.sort_by { |country| country[:code] } end diff --git a/app/models/types_de_champ/prefill_region_type_de_champ.rb b/app/models/types_de_champ/prefill_region_type_de_champ.rb index 7d52601b4..6362fccac 100644 --- a/app/models/types_de_champ/prefill_region_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_region_type_de_champ.rb @@ -1,10 +1,10 @@ class TypesDeChamp::PrefillRegionTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + private + def possible_values_list regions.map { |region| "#{region[:code]} (#{region[:name]})" } end - private - def regions @regions ||= APIGeoService.regions.sort_by { |region| region[:code] } end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 906451d79..ad809b1ee 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -9,21 +9,22 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh ].join("
    ").html_safe # rubocop:disable Rails/OutputSafety end - def subchamps_possible_values_list - "
      " + prefillable_subchamps.map do |prefill_type_de_champ| - "
    • #{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values}
    • " - end.join + "
    " - end def example_value [row_values_format, row_values_format].map { |row| row.to_s.gsub("=>", ":") } end + private + def too_many_possible_values? false end - private + def subchamps_possible_values_list + "
      " + prefillable_subchamps.map do |prefill_type_de_champ| + "
    • #{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values}
    • " + end.join + "
    " + end def row_values_format @row_example_value ||= diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index cc3776880..35a8fd955 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -2,7 +2,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator include ActionView::Helpers::UrlHelper include ApplicationHelper - POSSIBLE_VALUES_THRESHOLD = 1 + POSSIBLE_VALUES_THRESHOLD = 5 def self.build(type_de_champ) case type_de_champ.type_champ @@ -24,19 +24,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator end def possible_values - [possible_values_list_display, link_to_all_possible_values].compact.join('
    ').html_safe - end - - def possible_values_list - return [] unless prefillable? - - [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] - end - - def link_to_all_possible_values - return unless too_many_possible_values? - - link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes + [possible_values_list_display, link_to_all_possible_values].compact.join('
    ').html_safe # rubocop:disable Rails/OutputSafety end def example_value @@ -45,12 +33,24 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator I18n.t("views.prefill_descriptions.edit.examples.#{type_champ}") end + private + + def possible_values_list + return [] unless prefillable? + + [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] + end + + def link_to_all_possible_values + return unless too_many_possible_values? && prefillable? + + link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes + end + def too_many_possible_values? possible_values_list.count > POSSIBLE_VALUES_THRESHOLD end - private - def possible_values_list_display if too_many_possible_values? I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe # rubocop:disable Rails/OutputSafety diff --git a/spec/models/types_de_champ/prefill_drop_down_list_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_drop_down_list_type_de_champ_spec.rb index fc7d4e573..499ce5b3f 100644 --- a/spec/models/types_de_champ/prefill_drop_down_list_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_drop_down_list_type_de_champ_spec.rb @@ -2,22 +2,25 @@ RSpec.describe TypesDeChamp::PrefillDropDownListTypeDeChamp do describe '#possible_values' do + let(:procedure) { create(:procedure) } subject(:possible_values) { described_class.new(type_de_champ).possible_values } + before { type_de_champ.reload } + context "when the drop down list accepts 'other'" do - let(:type_de_champ) { build(:type_de_champ_drop_down_list, :with_other) } + let(:type_de_champ) { build(:type_de_champ_drop_down_list, :with_other, procedure: procedure) } it { expect(possible_values).to match( - [I18n.t("views.prefill_descriptions.edit.possible_values.drop_down_list_other_html")] + type_de_champ.drop_down_list_enabled_non_empty_options + ([I18n.t("views.prefill_descriptions.edit.possible_values.drop_down_list_other_html")] + type_de_champ.drop_down_list_enabled_non_empty_options).to_sentence ) } end context "when the drop down list does not accept 'other'" do - let(:type_de_champ) { build(:type_de_champ_drop_down_list) } + let(:type_de_champ) { build(:type_de_champ_drop_down_list, procedure:) } - it { expect(possible_values).to match(type_de_champ.drop_down_list_enabled_non_empty_options) } + it { expect(possible_values).to match(type_de_champ.drop_down_list_enabled_non_empty_options.to_sentence) } end end diff --git a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb index f651d3d60..baffe29dc 100644 --- a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb @@ -1,12 +1,17 @@ RSpec.describe TypesDeChamp::PrefillPaysTypeDeChamp, type: :model do - let(:type_de_champ) { build(:type_de_champ_pays) } + let(:procedure) { create(:procedure) } + let(:type_de_champ) { build(:type_de_champ_pays, procedure: procedure) } describe '#possible_values' do - let(:expected_values) { APIGeoService.countries.sort_by { |country| country[:code] }.map { |country| "#{country[:code]} (#{country[:name]})" } } + let(:expected_values) { "Un code pays ISO 3166-2
    Voir toutes les valeurs possibles" } subject(:possible_values) { described_class.new(type_de_champ).possible_values } - it { expect(possible_values).to match(expected_values) } + before { type_de_champ.reload } + + it { + expect(possible_values).to match(expected_values) + } end describe '#example_value' do diff --git a/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb index 39fd75fb9..31d7b7a86 100644 --- a/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true RSpec.describe TypesDeChamp::PrefillRegionTypeDeChamp, type: :model do - let(:type_de_champ) { build(:type_de_champ_regions) } + let(:procedure) { create(:procedure) } + let(:type_de_champ) { create(:type_de_champ_regions, procedure: procedure) } let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } before do @@ -16,9 +17,13 @@ RSpec.describe TypesDeChamp::PrefillRegionTypeDeChamp, type: :model do end describe '#possible_values', vcr: { cassette_name: 'api_geo_regions' } do - let(:expected_values) { APIGeoService.regions.sort_by { |region| region[:code] }.map { |region| "#{region[:code]} (#{region[:name]})" } } + let(:expected_values) { "Un code INSEE de région
    Voir toutes les valeurs possibles" } subject(:possible_values) { described_class.new(type_de_champ).possible_values } - it { expect(possible_values).to match(expected_values) } + before { type_de_champ.reload } + + it { + expect(possible_values).to eq(expected_values) + } end end diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index 0598e0740..a613b5c86 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -20,9 +20,13 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { describe '#possible_values' do subject(:possible_values) { described_class.new(type_de_champ).possible_values } - let(:expected_value) { ["sub type de champ: Un texte court", "sub type de champ2: Un nombre entier", "region sub_champ: Voir toutes les valeurs possibles"] } + let(:expected_value) { + "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
    " + } - it { expect(possible_values).to eq(expected_value) } + it { + expect(possible_values).to eq(expected_value) + } end describe '#example_value' do @@ -31,11 +35,4 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { it { expect(example_value).to eq(expected_value) } end - - describe '#possible_values_sentence' do - subject(:possible_values_sentence) { described_class.new(type_de_champ).possible_values_sentence } - let(:expected_value) { "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
    sub type de champ: Un texte court
    sub type de champ2: Un nombre entier
    region sub_champ: Voir toutes les valeurs possibles" } - - it { expect(possible_values_sentence).to eq(expected_value) } - end end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 3a8686c6c..34f407e0b 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -45,7 +45,7 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do end describe '#possible_values' do - subject(:possible_values) { described_class.build(type_de_champ).possible_values } + subject(:possible_values) { described_class.new(type_de_champ).possible_values } context 'when the type de champ is not prefillable' do let(:type_de_champ) { build(:type_de_champ_mesri) } @@ -54,16 +54,16 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do end context 'when there is too many possible values' do - let(:type_de_champ) { create(:type_de_champ_drop_down_list) } + let(:type_de_champ) { create(:type_de_champ_drop_down_list, procedure: create(:procedure)) } before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD + 1).map(&:to_s) } - it { expect(possible_values).to match("Un choix parmi ceux sélectionnés à la création de la procédure") } + it { expect(possible_values).to eq("Un choix parmi ceux sélectionnés à la création de la procédure") } end context 'when the type de champ is prefillable' do let(:type_de_champ) { build(:type_de_champ_email) } - it { expect(possible_values).to match("Une adresse email") } + it { expect(possible_values).to eq("Une adresse email") } end end From 4a6841c0099eba4fe231677751c30220f3884300 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Sat, 11 Feb 2023 22:51:37 +0100 Subject: [PATCH 019/202] Fix linter --- app/models/types_de_champ/prefill_repetition_type_de_champ.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index ad809b1ee..39d1c4f9c 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -9,7 +9,6 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh ].join("
    ").html_safe # rubocop:disable Rails/OutputSafety end - def example_value [row_values_format, row_values_format].map { |row| row.to_s.gsub("=>", ":") } end @@ -39,5 +38,4 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh @prefillable_subchamps ||= TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ).filter(&:prefillable?)) end - end From fc94aaaa2133a373f668005064dc0d7db6c27a27 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Sun, 12 Feb 2023 11:55:28 +0100 Subject: [PATCH 020/202] Use "to_assignable_attributes" --- app/models/prefill_description.rb | 6 +--- app/models/prefill_params.rb | 31 ++++--------------- .../prefill_repetition_type_de_champ.rb | 16 ++++++++++ .../types_de_champ/prefill_type_de_champ.rb | 8 +++++ spec/models/prefill_params_spec.rb | 14 ++++++--- .../prefill_type_de_champ_spec.rb | 9 ++++++ 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/app/models/prefill_description.rb b/app/models/prefill_description.rb index 2198f5412..36dd6de2f 100644 --- a/app/models/prefill_description.rb +++ b/app/models/prefill_description.rb @@ -51,11 +51,7 @@ class PrefillDescription < SimpleDelegator def prefilled_champs_for_query prefilled_champs.map do |type_de_champ| - if type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:repetition) - "\"champ_#{type_de_champ.to_typed_id}\": #{type_de_champ.example_value}" - else - "\"champ_#{type_de_champ.to_typed_id}\": \"#{type_de_champ.example_value}\"" - end + "\"champ_#{type_de_champ.to_typed_id}\": #{type_de_champ.formatted_example_value}" end.join(', ') end diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 591b25fdf..5f3f58710 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -5,7 +5,7 @@ class PrefillParams end def to_a - build_prefill_values.filter(&:prefillable?).map(&:to_h).flatten + build_prefill_values.filter(&:prefillable?).map(&:champ_attributes).flatten end private @@ -54,15 +54,10 @@ class PrefillParams champ.prefillable? && valid? end - def to_h - if champ.type_champ == TypeDeChamp.type_champs.fetch(:repetition) - repeatable_hashes - else - { - id: champ.id, - value: value - } - end + def champ_attributes + TypesDeChamp::PrefillTypeDeChamp + .build(champ.type_de_champ) + .to_assignable_attributes(champ, value) end private @@ -70,22 +65,8 @@ class PrefillParams def valid? return true unless NEED_VALIDATION_TYPES_DE_CHAMPS.include?(champ.type_champ) - champ.value = value + champ.assign_attributes(champ_attributes) champ.valid?(:prefill) end - - def repeatable_hashes - return [] unless value.is_a?(Array) - - value.map.with_index do |repetition, index| - row = champ.rows[index] || champ.add_row(champ.dossier_revision) - JSON.parse(repetition).map do |key, value| - id = row.find { |champ| champ.libelle == key }&.id - next unless id - { id: id, value: value } - end.compact - rescue JSON::ParserError - end.compact - end end end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 39d1c4f9c..a99b38b2d 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -13,6 +13,22 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh [row_values_format, row_values_format].map { |row| row.to_s.gsub("=>", ":") } end + alias_method :formatted_example_value, :example_value + + def to_assignable_attributes(champ, value) + return [] unless value.is_a?(Array) + + value.map.with_index do |repetition, index| + row = champ.rows[index] || champ.add_row(champ.dossier_revision) + JSON.parse(repetition).map do |key, value| + id = row.find { |champ| champ.libelle == key }&.id + next unless id + { id: id, value: value } + end.compact + rescue JSON::ParserError + end.compact + end + private def too_many_possible_values? diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 35a8fd955..8b6c0bbba 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -33,6 +33,14 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator I18n.t("views.prefill_descriptions.edit.examples.#{type_champ}") end + def formatted_example_value + "\"#{example_value}\"" if example_value.present? + end + + def to_assignable_attributes(champ, value) + { id: champ.id, value: value } + end + private def possible_values_list diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index dc3108efe..cbd6d4300 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -72,12 +72,12 @@ RSpec.describe PrefillParams do context "when the type de champ is authorized (#{type_de_champ_type})" do let(:types_de_champ_public) { [{ type: type_de_champ_type }] } let(:type_de_champ) { procedure.published_revision.types_de_champ_public.first } - let(:champ_id) { find_champ_by_stable_id(dossier, type_de_champ.stable_id).id } + let(:champ) { find_champ_by_stable_id(dossier, type_de_champ.stable_id) } let(:params) { { "champ_#{type_de_champ.to_typed_id}" => value } } it "builds an array of hash(id, value) matching the given params" do - expect(prefill_params_array).to match([{ id: champ_id, value: value }]) + expect(prefill_params_array).to match([{ id: champ.id }.merge(attributes(champ, value))]) end end end @@ -86,12 +86,12 @@ RSpec.describe PrefillParams do context "when the type de champ is authorized (#{type_de_champ_type})" do let(:types_de_champ_private) { [{ type: type_de_champ_type }] } let(:type_de_champ) { procedure.published_revision.types_de_champ_private.first } - let(:champ_id) { find_champ_by_stable_id(dossier, type_de_champ.stable_id).id } + let(:champ) { find_champ_by_stable_id(dossier, type_de_champ.stable_id) } let(:params) { { "champ_#{type_de_champ.to_typed_id}" => value } } it "builds an array of hash(id, value) matching the given params" do - expect(prefill_params_array).to match([{ id: champ_id, value: value }]) + expect(prefill_params_array).to match([{ id: champ.id }.merge(attributes(champ, value))]) end end end @@ -231,4 +231,10 @@ RSpec.describe PrefillParams do def find_champ_by_stable_id(dossier, stable_id) dossier.champs.joins(:type_de_champ).find_by(types_de_champ: { stable_id: stable_id }) end + + def attributes(champ, value) + TypesDeChamp::PrefillTypeDeChamp + .build(champ.type_de_champ) + .to_assignable_attributes(champ, value) + end end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 34f407e0b..d5e322155 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -82,4 +82,13 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(example_value).to eq(I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")) } end end + + describe '#to_assignable_attributes' do + let(:type_de_champ) { build(:type_de_champ_email) } + let(:champ) { build(:champ, type_de_champ: type_de_champ) } + let(:value) { "any@email.org" } + subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } + + it { is_expected.to match({ id: champ.id, value: value }) } + end end From ea126612a1aa094734fbb0b9f4ea054735eb4eee Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Sun, 12 Feb 2023 13:02:30 +0100 Subject: [PATCH 021/202] Refacto to_assignable_attributes for prefill_repetition --- .../prefill_repetition_type_de_champ.rb | 26 +++++++++++---- .../prefill_repetition_type_de_champ_spec.rb | 33 +++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index a99b38b2d..d95b03dd7 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -19,12 +19,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh return [] unless value.is_a?(Array) value.map.with_index do |repetition, index| - row = champ.rows[index] || champ.add_row(champ.dossier_revision) - JSON.parse(repetition).map do |key, value| - id = row.find { |champ| champ.libelle == key }&.id - next unless id - { id: id, value: value } - end.compact + PrefillRepetitionRow.new(champ, repetition, index).to_assignable_attributes rescue JSON::ParserError end.compact end @@ -54,4 +49,23 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh @prefillable_subchamps ||= TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ).filter(&:prefillable?)) end + + class PrefillRepetitionRow + def initialize(champ, repetition, index) + @champ = champ + @repetition = repetition + @index = index + end + + def to_assignable_attributes + row = @champ.rows[@index] || @champ.add_row(@champ.dossier_revision) + + JSON.parse(@repetition).map do |key, value| + subchamp = row.find { |champ| champ.libelle == key } + return unless subchamp + + TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ).to_assignable_attributes(subchamp, value) + end.compact + end + end end diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index a613b5c86..4912cfe71 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -3,6 +3,7 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { cassette_name: 'api_geo_regions' } do let(:procedure) { build(:procedure) } let(:type_de_champ) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } + let(:champ) { create(:champ_repetition, type_de_champ: type_de_champ) } let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ).send(:prefillable_subchamps) } let(:region_repetition) { prefillable_subchamps.third } let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } @@ -35,4 +36,36 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { it { expect(example_value).to eq(expected_value) } end + + describe '#to_assignable_attributes' do + subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } + let(:type_de_champ_child) { champ.rows.first.first.type_de_champ } + + context 'when the value is nil' do + let(:value) { nil } + it { is_expected.to match([]) } + end + + context 'when the value is empty' do + let(:value) { '' } + it { is_expected.to match([]) } + end + + context 'when the value is a string' do + let(:value) { 'hello' } + it { is_expected.to match([]) } + end + + context 'when the value is an array with wrong keys' do + let(:value) { ["{\"blabla\":\"value\"}", "{\"blabla\":\"value2\"}"] } + + it { is_expected.to match([]) } + end + + context 'when the value is an array with right keys' do + let(:value) { ["{\"#{type_de_champ_child.libelle}\":\"value\"}", "{\"#{type_de_champ_child.libelle}\":\"value2\"}"] } + + it { is_expected.to match([[{ id: type_de_champ_child.champ.first.id, value: "value" }], [{ id: type_de_champ_child.champ.second.id, value: "value2" }]]) } + end + end end From 0a386994e55260a7a6fe938d8334f993b5b79356 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 13 Feb 2023 11:08:54 +0100 Subject: [PATCH 022/202] correctif(messagerie): autorise l'usage des balises dans la messagerie quand les messages viennent de l'administration --- app/components/dossiers/message_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/dossiers/message_component.rb b/app/components/dossiers/message_component.rb index a09f0ef78..1dceaf4ff 100644 --- a/app/components/dossiers/message_component.rb +++ b/app/components/dossiers/message_component.rb @@ -60,7 +60,7 @@ class Dossiers::MessageComponent < ApplicationComponent t('.deleted_body') else body_formatted = commentaire.sent_by_system? ? commentaire.body : simple_format(commentaire.body) - sanitize(body_formatted) + sanitize(body_formatted, commentaire.sent_by_system? ? { scrubber: Sanitizers::MailScrubber.new } : {}) end end From 899c8a65895bfded364b9df46ac28c432a04e9d4 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Mon, 13 Feb 2023 14:17:53 +0100 Subject: [PATCH 023/202] Add documentation json parse rescue repetition --- .../types_de_champ/prefill_repetition_type_de_champ.rb | 2 +- .../types_de_champ/prefill_repetition_type_de_champ_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index d95b03dd7..8b729dc25 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -20,7 +20,6 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh value.map.with_index do |repetition, index| PrefillRepetitionRow.new(champ, repetition, index).to_assignable_attributes - rescue JSON::ParserError end.compact end @@ -65,6 +64,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh return unless subchamp TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ).to_assignable_attributes(subchamp, value) + rescue JSON::ParserError # On ignore les valeurs qu'on n'arrive pas à parser end.compact end end diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index 4912cfe71..33197c381 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -62,6 +62,12 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { it { is_expected.to match([]) } end + context 'when the value is an array with some wrong keys' do + let(:value) { ["{\"#{type_de_champ_child.libelle}\":\"value\"}", "{\"blabla\":\"value2\"}"] } + + it { is_expected.to match([[{ id: type_de_champ_child.champ.first.id, value: "value" }]]) } + end + context 'when the value is an array with right keys' do let(:value) { ["{\"#{type_de_champ_child.libelle}\":\"value\"}", "{\"#{type_de_champ_child.libelle}\":\"value2\"}"] } From f0ffae83203c8b234e7aa340845f57b350b3b7c1 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Wed, 8 Feb 2023 09:52:32 +0100 Subject: [PATCH 024/202] migrate(champs): normalize departements --- ...departements_with_empty_external_id_job.rb | 24 + ...e_departements_with_nil_external_id_job.rb | 22 + ...partements_with_present_external_id_job.rb | 15 + ...20230208084036_normalize_departements.rake | 32 ++ ...208084036_normalize_departements_spec.rake | 409 ++++++++++++++++++ 5 files changed, 502 insertions(+) create mode 100644 app/jobs/migrations/normalize_departements_with_empty_external_id_job.rb create mode 100644 app/jobs/migrations/normalize_departements_with_nil_external_id_job.rb create mode 100644 app/jobs/migrations/normalize_departements_with_present_external_id_job.rb create mode 100644 lib/tasks/deployment/20230208084036_normalize_departements.rake create mode 100644 spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake diff --git a/app/jobs/migrations/normalize_departements_with_empty_external_id_job.rb b/app/jobs/migrations/normalize_departements_with_empty_external_id_job.rb new file mode 100644 index 000000000..00a8c9e5c --- /dev/null +++ b/app/jobs/migrations/normalize_departements_with_empty_external_id_job.rb @@ -0,0 +1,24 @@ +class Migrations::NormalizeDepartementsWithEmptyExternalIdJob < ApplicationJob + def perform(ids) + Champs::DepartementChamp.where(id: ids).find_each do |champ| + next unless champ.external_id == '' + + if champ.value.nil? + champ.update_columns(external_id: nil) + elsif champ.value == '' + champ.update_columns(external_id: nil, value: nil) + elsif champ.value == '85' + champ.update_columns(external_id: '85', value: 'Vendée') + elsif champ.value.present? + match = champ.value.match(/^(\w{2,3}) - (.+)/) + if match + code = match[1] + name = APIGeoService.departement_name(code) + champ.update_columns(external_id: code, value: name) + else + champ.update_columns(external_id: APIGeoService.departement_code(champ.value)) + end + end + end + end +end diff --git a/app/jobs/migrations/normalize_departements_with_nil_external_id_job.rb b/app/jobs/migrations/normalize_departements_with_nil_external_id_job.rb new file mode 100644 index 000000000..109ca6c9a --- /dev/null +++ b/app/jobs/migrations/normalize_departements_with_nil_external_id_job.rb @@ -0,0 +1,22 @@ +class Migrations::NormalizeDepartementsWithNilExternalIdJob < ApplicationJob + def perform(ids) + Champs::DepartementChamp.where(id: ids).find_each do |champ| + next unless champ.external_id.nil? + + if champ.value == '' + champ.update_columns(value: nil) + elsif champ.value == '85' + champ.update_columns(external_id: '85', value: 'Vendée') + elsif champ.value.present? + match = champ.value.match(/^(\w{2,3}) - (.+)/) + if match + code = match[1] + name = APIGeoService.departement_name(code) + champ.update_columns(external_id: code, value: name) + else + champ.update_columns(external_id: APIGeoService.departement_code(champ.value)) + end + end + end + end +end diff --git a/app/jobs/migrations/normalize_departements_with_present_external_id_job.rb b/app/jobs/migrations/normalize_departements_with_present_external_id_job.rb new file mode 100644 index 000000000..a024582d2 --- /dev/null +++ b/app/jobs/migrations/normalize_departements_with_present_external_id_job.rb @@ -0,0 +1,15 @@ +class Migrations::NormalizeDepartementsWithPresentExternalIdJob < ApplicationJob + def perform(ids) + Champs::DepartementChamp.where(id: ids).find_each do |champ| + next if champ.external_id.blank? + + if champ.value.blank? + champ.update_columns(value: APIGeoService.departement_name(champ.external_id)) + elsif (match = champ.value.match(/^(\w{2,3}) - (.+)/)) + code = match[1] + name = APIGeoService.departement_name(code) + champ.update_columns(external_id: code, value: name) + end + end + end +end diff --git a/lib/tasks/deployment/20230208084036_normalize_departements.rake b/lib/tasks/deployment/20230208084036_normalize_departements.rake new file mode 100644 index 000000000..8ed162bd0 --- /dev/null +++ b/lib/tasks/deployment/20230208084036_normalize_departements.rake @@ -0,0 +1,32 @@ +namespace :after_party do + desc 'Deployment task: normalize_departements' + task normalize_departements: :environment do + puts "Running deploy task 'normalize_departements'" + + scope_external_id_nil = Champs::DepartementChamp.where(external_id: nil) + scope_external_id_empty = Champs::DepartementChamp.where(external_id: '') + scope_external_id_present = Champs::DepartementChamp.where.not(external_id: [nil, '']) + + progress = ProgressReport.new(scope_external_id_nil.count + scope_external_id_empty.count + scope_external_id_present.count) + + normalize_asynchronously(scope_external_id_nil, progress, Migrations::NormalizeDepartementsWithNilExternalIdJob) + normalize_asynchronously(scope_external_id_empty, progress, Migrations::NormalizeDepartementsWithEmptyExternalIdJob) + normalize_asynchronously(scope_external_id_present, progress, Migrations::NormalizeDepartementsWithPresentExternalIdJob) + + progress.finish + + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end + + private + + def normalize_asynchronously(scope, progress, job) + scope.in_batches(of: 10_000) do |batch| + progress.inc(batch.count) + job.perform_later(batch.pluck(:id)) + end + end +end diff --git a/spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake b/spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake new file mode 100644 index 000000000..968f37fb3 --- /dev/null +++ b/spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake @@ -0,0 +1,409 @@ +# frozen_string_literal: true + +RSpec.describe '20230208084036_normalize_departements', vcr: { cassette_name: 'api_geo_departements' } do + let(:champ) { create(:champ_departements) } + let(:rake_task) { Rake::Task['after_party:normalize_departements'] } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + subject(:run_task) { perform_enqueued_jobs { rake_task.invoke } } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + + after { rake_task.reenable } + + shared_examples "a non-changer" do |external_id, value| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.not_to change { champ.reload.external_id } } + + it { expect { run_task }.not_to change { champ.reload.value } } + end + + shared_examples "an external_id nullifier" do |external_id, value| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(nil) } + + it { expect { run_task }.not_to change { champ.reload.value } } + end + + shared_examples "a value nullifier" do |external_id, value| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.not_to change { champ.reload.external_id } } + + it { expect { run_task }.to change { champ.reload.value }.from(value).to(nil) } + end + + shared_examples "an external_id and value nullifier" do |external_id, value| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(nil) } + + it { expect { run_task }.to change { champ.reload.value }.from(value).to(nil) } + end + + shared_examples "an external_id updater" do |external_id, value, expected_external_id| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(expected_external_id) } + + it { expect { run_task }.not_to change { champ.reload.value } } + end + + shared_examples "a value updater" do |external_id, value, expected_value| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.not_to change { champ.reload.external_id } } + + it { expect { run_task }.to change { champ.reload.value }.from(value).to(expected_value) } + end + + shared_examples "an external_id and value updater" do |external_id, value, expected_external_id, expected_value| + before { champ.update_columns(external_id:, value:) } + + it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(expected_external_id) } + + it { expect { run_task }.to change { champ.reload.value }.from(value).to(expected_value) } + end + + shared_examples "a result checker" do |external_id, value, expected_external_id, expected_value| + before do + champ.update_columns(external_id:, value:) + run_task + end + + it { expect(champ.reload.external_id).to eq(expected_external_id) } + + it { expect(champ.reload.value).to eq(expected_value) } + end + + it_behaves_like "a non-changer", nil, nil + it_behaves_like "an external_id nullifier", '', nil + it_behaves_like "a value nullifier", nil, '' + it_behaves_like "an external_id and value nullifier", '', '' + it_behaves_like "an external_id updater", nil, 'Ain', '01' + it_behaves_like "an external_id updater", '', 'Ain', '01' + it_behaves_like "a value updater", '01', nil, 'Ain' + it_behaves_like "a value updater", '01', '', 'Ain' + it_behaves_like "an external_id and value updater", nil, '01 - Ain', '01', 'Ain' + it_behaves_like "an external_id and value updater", '', '01 - Ain', '01', 'Ain' + + # Integrity data check: + it_behaves_like "a result checker", "972", nil, "972", "Martinique" + it_behaves_like "a result checker", "92", nil, "92", "Hauts-de-Seine" + it_behaves_like "a result checker", "01", nil, "01", "Ain" + it_behaves_like "a result checker", "82", nil, "82", "Tarn-et-Garonne" + it_behaves_like "a result checker", "01", "01 - Ain", "01", "Ain" + it_behaves_like "a result checker", '', "01 - Ain", "01", "Ain" + it_behaves_like "a result checker", nil, "01 - Ain", "01", "Ain" + it_behaves_like "a result checker", "02", "02 - Aisne", "02", "Aisne" + it_behaves_like "a result checker", nil, "02 - Aisne", "02", "Aisne" + it_behaves_like "a result checker", '', "02 - Aisne", "02", "Aisne" + it_behaves_like "a result checker", "03", "03 - Allier", "03", "Allier" + it_behaves_like "a result checker", nil, "03 - Allier", "03", "Allier" + it_behaves_like "a result checker", '', "03 - Allier", "03", "Allier" + it_behaves_like "a result checker", "04", "04 - Alpes-de-Haute-Provence", "04", "Alpes-de-Haute-Provence" + it_behaves_like "a result checker", nil, "04 - Alpes-de-Haute-Provence", "04", "Alpes-de-Haute-Provence" + it_behaves_like "a result checker", '', "04 - Alpes-de-Haute-Provence", "04", "Alpes-de-Haute-Provence" + it_behaves_like "a result checker", "05", "05 - Hautes-Alpes", "05", "Hautes-Alpes" + it_behaves_like "a result checker", nil, "05 - Hautes-Alpes", "05", "Hautes-Alpes" + it_behaves_like "a result checker", '', "05 - Hautes-Alpes", "05", "Hautes-Alpes" + it_behaves_like "a result checker", nil, "06 - Alpes-Maritimes", "06", "Alpes-Maritimes" + it_behaves_like "a result checker", "06", "06 - Alpes-Maritimes", "06", "Alpes-Maritimes" + it_behaves_like "a result checker", '', "06 - Alpes-Maritimes", "06", "Alpes-Maritimes" + it_behaves_like "a result checker", "07", "07 - Ardèche", "07", "Ardèche" + it_behaves_like "a result checker", nil, "07 - Ardèche", "07", "Ardèche" + it_behaves_like "a result checker", '', "07 - Ardèche", "07", "Ardèche" + it_behaves_like "a result checker", "08", "08 - Ardennes", "08", "Ardennes" + it_behaves_like "a result checker", nil, "08 - Ardennes", "08", "Ardennes" + it_behaves_like "a result checker", '', "08 - Ardennes", "08", "Ardennes" + it_behaves_like "a result checker", "09", "09 - Ariège", "09", "Ariège" + it_behaves_like "a result checker", nil, "09 - Ariège", "09", "Ariège" + it_behaves_like "a result checker", '', "09 - Ariège", "09", "Ariège" + it_behaves_like "a result checker", nil, "10 - Aube", "10", "Aube" + it_behaves_like "a result checker", "10", "10 - Aube", "10", "Aube" + it_behaves_like "a result checker", '', "10 - Aube", "10", "Aube" + it_behaves_like "a result checker", "11", "11 - Aude", "11", "Aude" + it_behaves_like "a result checker", nil, "11 - Aude", "11", "Aude" + it_behaves_like "a result checker", '', "11 - Aude", "11", "Aude" + it_behaves_like "a result checker", "12", "12 - Aveyron", "12", "Aveyron" + it_behaves_like "a result checker", nil, "12 - Aveyron", "12", "Aveyron" + it_behaves_like "a result checker", '', "12 - Aveyron", "12", "Aveyron" + it_behaves_like "a result checker", "13", "13 - Bouches-du-Rhône", "13", "Bouches-du-Rhône" + it_behaves_like "a result checker", nil, "13 - Bouches-du-Rhône", "13", "Bouches-du-Rhône" + it_behaves_like "a result checker", '', "13 - Bouches-du-Rhône", "13", "Bouches-du-Rhône" + it_behaves_like "a result checker", "14", "14 - Calvados", "14", "Calvados" + it_behaves_like "a result checker", nil, "14 - Calvados", "14", "Calvados" + it_behaves_like "a result checker", '', "14 - Calvados", "14", "Calvados" + it_behaves_like "a result checker", "15", "15 - Cantal", "15", "Cantal" + it_behaves_like "a result checker", nil, "15 - Cantal", "15", "Cantal" + it_behaves_like "a result checker", '', "15 - Cantal", "15", "Cantal" + it_behaves_like "a result checker", "16", "16 - Charente", "16", "Charente" + it_behaves_like "a result checker", nil, "16 - Charente", "16", "Charente" + it_behaves_like "a result checker", '', "16 - Charente", "16", "Charente" + it_behaves_like "a result checker", "17", "17 - Charente-Maritime", "17", "Charente-Maritime" + it_behaves_like "a result checker", nil, "17 - Charente-Maritime", "17", "Charente-Maritime" + it_behaves_like "a result checker", '', "17 - Charente-Maritime", "17", "Charente-Maritime" + it_behaves_like "a result checker", "18", "18 - Cher", "18", "Cher" + it_behaves_like "a result checker", nil, "18 - Cher", "18", "Cher" + it_behaves_like "a result checker", '', "18 - Cher", "18", "Cher" + it_behaves_like "a result checker", "19", "19 - Corrèze", "19", "Corrèze" + it_behaves_like "a result checker", nil, "19 - Corrèze", "19", "Corrèze" + it_behaves_like "a result checker", '', "19 - Corrèze", "19", "Corrèze" + it_behaves_like "a result checker", "21", "21 - Côte-d’Or", "21", "Côte-d’Or" + it_behaves_like "a result checker", '', "21 - Côte-d’Or", "21", "Côte-d’Or" + it_behaves_like "a result checker", nil, "21 - Côte-d’Or", "21", "Côte-d’Or" + it_behaves_like "a result checker", "22", "22 - Côtes-d’Armor", "22", "Côtes-d’Armor" + it_behaves_like "a result checker", nil, "22 - Côtes-d’Armor", "22", "Côtes-d’Armor" + it_behaves_like "a result checker", '', "22 - Côtes-d’Armor", "22", "Côtes-d’Armor" + it_behaves_like "a result checker", "23", "23 - Creuse", "23", "Creuse" + it_behaves_like "a result checker", nil, "23 - Creuse", "23", "Creuse" + it_behaves_like "a result checker", '', "23 - Creuse", "23", "Creuse" + it_behaves_like "a result checker", "24", "24 - Dordogne", "24", "Dordogne" + it_behaves_like "a result checker", nil, "24 - Dordogne", "24", "Dordogne" + it_behaves_like "a result checker", '', "24 - Dordogne", "24", "Dordogne" + it_behaves_like "a result checker", "25", "25 - Doubs", "25", "Doubs" + it_behaves_like "a result checker", nil, "25 - Doubs", "25", "Doubs" + it_behaves_like "a result checker", '', "25 - Doubs", "25", "Doubs" + it_behaves_like "a result checker", "26", "26 - Drôme", "26", "Drôme" + it_behaves_like "a result checker", nil, "26 - Drôme", "26", "Drôme" + it_behaves_like "a result checker", '', "26 - Drôme", "26", "Drôme" + it_behaves_like "a result checker", "27", "27 - Eure", "27", "Eure" + it_behaves_like "a result checker", nil, "27 - Eure", "27", "Eure" + it_behaves_like "a result checker", '', "27 - Eure", "27", "Eure" + it_behaves_like "a result checker", "28", "28 - Eure-et-Loir", "28", "Eure-et-Loir" + it_behaves_like "a result checker", nil, "28 - Eure-et-Loir", "28", "Eure-et-Loir" + it_behaves_like "a result checker", '', "28 - Eure-et-Loir", "28", "Eure-et-Loir" + it_behaves_like "a result checker", "29", "29 - Finistère", "29", "Finistère" + it_behaves_like "a result checker", nil, "29 - Finistère", "29", "Finistère" + it_behaves_like "a result checker", '', "29 - Finistère", "29", "Finistère" + it_behaves_like "a result checker", "2A", "2A - Corse-du-Sud", "2A", "Corse-du-Sud" + it_behaves_like "a result checker", nil, "2A - Corse-du-Sud", "2A", "Corse-du-Sud" + it_behaves_like "a result checker", '', "2A - Corse-du-Sud", "2A", "Corse-du-Sud" + it_behaves_like "a result checker", "2B", "2B - Haute-Corse", "2B", "Haute-Corse" + it_behaves_like "a result checker", nil, "2B - Haute-Corse", "2B", "Haute-Corse" + it_behaves_like "a result checker", '', "2B - Haute-Corse", "2B", "Haute-Corse" + it_behaves_like "a result checker", "30", "30 - Gard", "30", "Gard" + it_behaves_like "a result checker", nil, "30 - Gard", "30", "Gard" + it_behaves_like "a result checker", '', "30 - Gard", "30", "Gard" + it_behaves_like "a result checker", "31", "31 - Haute-Garonne", "31", "Haute-Garonne" + it_behaves_like "a result checker", nil, "31 - Haute-Garonne", "31", "Haute-Garonne" + it_behaves_like "a result checker", '', "31 - Haute-Garonne", "31", "Haute-Garonne" + it_behaves_like "a result checker", "32", "32 - Gers", "32", "Gers" + it_behaves_like "a result checker", nil, "32 - Gers", "32", "Gers" + it_behaves_like "a result checker", '', "32 - Gers", "32", "Gers" + it_behaves_like "a result checker", "33", "33 - Gironde", "33", "Gironde" + it_behaves_like "a result checker", nil, "33 - Gironde", "33", "Gironde" + it_behaves_like "a result checker", '', "33 - Gironde", "33", "Gironde" + it_behaves_like "a result checker", "34", "34 - Hérault", "34", "Hérault" + it_behaves_like "a result checker", nil, "34 - Hérault", "34", "Hérault" + it_behaves_like "a result checker", '', "34 - Hérault", "34", "Hérault" + it_behaves_like "a result checker", "35", "35 - Ille-et-Vilaine", "35", "Ille-et-Vilaine" + it_behaves_like "a result checker", nil, "35 - Ille-et-Vilaine", "35", "Ille-et-Vilaine" + it_behaves_like "a result checker", '', "35 - Ille-et-Vilaine", "35", "Ille-et-Vilaine" + it_behaves_like "a result checker", "36", "36 - Indre", "36", "Indre" + it_behaves_like "a result checker", nil, "36 - Indre", "36", "Indre" + it_behaves_like "a result checker", '', "36 - Indre", "36", "Indre" + it_behaves_like "a result checker", "37", "37 - Indre-et-Loire", "37", "Indre-et-Loire" + it_behaves_like "a result checker", nil, "37 - Indre-et-Loire", "37", "Indre-et-Loire" + it_behaves_like "a result checker", '', "37 - Indre-et-Loire", "37", "Indre-et-Loire" + it_behaves_like "a result checker", "38", "38 - Isère", "38", "Isère" + it_behaves_like "a result checker", nil, "38 - Isère", "38", "Isère" + it_behaves_like "a result checker", '', "38 - Isère", "38", "Isère" + it_behaves_like "a result checker", "39", "39 - Jura", "39", "Jura" + it_behaves_like "a result checker", nil, "39 - Jura", "39", "Jura" + it_behaves_like "a result checker", '', "39 - Jura", "39", "Jura" + it_behaves_like "a result checker", "40", "40 - Landes", "40", "Landes" + it_behaves_like "a result checker", nil, "40 - Landes", "40", "Landes" + it_behaves_like "a result checker", '', "40 - Landes", "40", "Landes" + it_behaves_like "a result checker", "41", "41 - Loir-et-Cher", "41", "Loir-et-Cher" + it_behaves_like "a result checker", nil, "41 - Loir-et-Cher", "41", "Loir-et-Cher" + it_behaves_like "a result checker", '', "41 - Loir-et-Cher", "41", "Loir-et-Cher" + it_behaves_like "a result checker", "42", "42 - Loire", "42", "Loire" + it_behaves_like "a result checker", nil, "42 - Loire", "42", "Loire" + it_behaves_like "a result checker", '', "42 - Loire", "42", "Loire" + it_behaves_like "a result checker", "43", "43 - Haute-Loire", "43", "Haute-Loire" + it_behaves_like "a result checker", nil, "43 - Haute-Loire", "43", "Haute-Loire" + it_behaves_like "a result checker", '', "43 - Haute-Loire", "43", "Haute-Loire" + it_behaves_like "a result checker", "44", "44 - Loire-Atlantique", "44", "Loire-Atlantique" + it_behaves_like "a result checker", nil, "44 - Loire-Atlantique", "44", "Loire-Atlantique" + it_behaves_like "a result checker", '', "44 - Loire-Atlantique", "44", "Loire-Atlantique" + it_behaves_like "a result checker", "45", "45 - Loiret", "45", "Loiret" + it_behaves_like "a result checker", nil, "45 - Loiret", "45", "Loiret" + it_behaves_like "a result checker", '', "45 - Loiret", "45", "Loiret" + it_behaves_like "a result checker", "46", "46 - Lot", "46", "Lot" + it_behaves_like "a result checker", nil, "46 - Lot", "46", "Lot" + it_behaves_like "a result checker", '', "46 - Lot", "46", "Lot" + it_behaves_like "a result checker", "47", "47 - Lot-et-Garonne", "47", "Lot-et-Garonne" + it_behaves_like "a result checker", nil, "47 - Lot-et-Garonne", "47", "Lot-et-Garonne" + it_behaves_like "a result checker", '', "47 - Lot-et-Garonne", "47", "Lot-et-Garonne" + it_behaves_like "a result checker", "48", "48 - Lozère", "48", "Lozère" + it_behaves_like "a result checker", nil, "48 - Lozère", "48", "Lozère" + it_behaves_like "a result checker", '', "48 - Lozère", "48", "Lozère" + it_behaves_like "a result checker", "49", "49 - Maine-et-Loire", "49", "Maine-et-Loire" + it_behaves_like "a result checker", nil, "49 - Maine-et-Loire", "49", "Maine-et-Loire" + it_behaves_like "a result checker", '', "49 - Maine-et-Loire", "49", "Maine-et-Loire" + it_behaves_like "a result checker", "50", "50 - Manche", "50", "Manche" + it_behaves_like "a result checker", nil, "50 - Manche", "50", "Manche" + it_behaves_like "a result checker", '', "50 - Manche", "50", "Manche" + it_behaves_like "a result checker", "51", "51 - Marne", "51", "Marne" + it_behaves_like "a result checker", '', "51 - Marne", "51", "Marne" + it_behaves_like "a result checker", nil, "51 - Marne", "51", "Marne" + it_behaves_like "a result checker", "52", "52 - Haute-Marne", "52", "Haute-Marne" + it_behaves_like "a result checker", nil, "52 - Haute-Marne", "52", "Haute-Marne" + it_behaves_like "a result checker", '', "52 - Haute-Marne", "52", "Haute-Marne" + it_behaves_like "a result checker", "53", "53 - Mayenne", "53", "Mayenne" + it_behaves_like "a result checker", nil, "53 - Mayenne", "53", "Mayenne" + it_behaves_like "a result checker", '', "53 - Mayenne", "53", "Mayenne" + it_behaves_like "a result checker", "54", "54 - Meurthe-et-Moselle", "54", "Meurthe-et-Moselle" + it_behaves_like "a result checker", '', "54 - Meurthe-et-Moselle", "54", "Meurthe-et-Moselle" + it_behaves_like "a result checker", nil, "54 - Meurthe-et-Moselle", "54", "Meurthe-et-Moselle" + it_behaves_like "a result checker", nil, "55 - Meuse", "55", "Meuse" + it_behaves_like "a result checker", "55", "55 - Meuse", "55", "Meuse" + it_behaves_like "a result checker", '', "55 - Meuse", "55", "Meuse" + it_behaves_like "a result checker", "56", "56 - Morbihan", "56", "Morbihan" + it_behaves_like "a result checker", nil, "56 - Morbihan", "56", "Morbihan" + it_behaves_like "a result checker", '', "56 - Morbihan", "56", "Morbihan" + it_behaves_like "a result checker", "57", "57 - Moselle", "57", "Moselle" + it_behaves_like "a result checker", nil, "57 - Moselle", "57", "Moselle" + it_behaves_like "a result checker", '', "57 - Moselle", "57", "Moselle" + it_behaves_like "a result checker", "58", "58 - Nièvre", "58", "Nièvre" + it_behaves_like "a result checker", nil, "58 - Nièvre", "58", "Nièvre" + it_behaves_like "a result checker", '', "58 - Nièvre", "58", "Nièvre" + it_behaves_like "a result checker", "59", "59 - Nord", "59", "Nord" + it_behaves_like "a result checker", nil, "59 - Nord", "59", "Nord" + it_behaves_like "a result checker", '', "59 - Nord", "59", "Nord" + it_behaves_like "a result checker", "60", "60 - Oise", "60", "Oise" + it_behaves_like "a result checker", nil, "60 - Oise", "60", "Oise" + it_behaves_like "a result checker", '', "60 - Oise", "60", "Oise" + it_behaves_like "a result checker", "61", "61 - Orne", "61", "Orne" + it_behaves_like "a result checker", nil, "61 - Orne", "61", "Orne" + it_behaves_like "a result checker", '', "61 - Orne", "61", "Orne" + it_behaves_like "a result checker", nil, "62 - Pas-de-Calais", "62", "Pas-de-Calais" + it_behaves_like "a result checker", "62", "62 - Pas-de-Calais", "62", "Pas-de-Calais" + it_behaves_like "a result checker", '', "62 - Pas-de-Calais", "62", "Pas-de-Calais" + it_behaves_like "a result checker", "63", "63 - Puy-de-Dôme", "63", "Puy-de-Dôme" + it_behaves_like "a result checker", nil, "63 - Puy-de-Dôme", "63", "Puy-de-Dôme" + it_behaves_like "a result checker", '', "63 - Puy-de-Dôme", "63", "Puy-de-Dôme" + it_behaves_like "a result checker", "64", "64 - Pyrénées-Atlantiques", "64", "Pyrénées-Atlantiques" + it_behaves_like "a result checker", nil, "64 - Pyrénées-Atlantiques", "64", "Pyrénées-Atlantiques" + it_behaves_like "a result checker", '', "64 - Pyrénées-Atlantiques", "64", "Pyrénées-Atlantiques" + it_behaves_like "a result checker", "65", "65 - Hautes-Pyrénées", "65", "Hautes-Pyrénées" + it_behaves_like "a result checker", nil, "65 - Hautes-Pyrénées", "65", "Hautes-Pyrénées" + it_behaves_like "a result checker", '', "65 - Hautes-Pyrénées", "65", "Hautes-Pyrénées" + it_behaves_like "a result checker", "66", "66 - Pyrénées-Orientales", "66", "Pyrénées-Orientales" + it_behaves_like "a result checker", nil, "66 - Pyrénées-Orientales", "66", "Pyrénées-Orientales" + it_behaves_like "a result checker", '', "66 - Pyrénées-Orientales", "66", "Pyrénées-Orientales" + it_behaves_like "a result checker", "67", "67 - Bas-Rhin", "67", "Bas-Rhin" + it_behaves_like "a result checker", nil, "67 - Bas-Rhin", "67", "Bas-Rhin" + it_behaves_like "a result checker", '', "67 - Bas-Rhin", "67", "Bas-Rhin" + it_behaves_like "a result checker", "68", "68 - Haut-Rhin", "68", "Haut-Rhin" + it_behaves_like "a result checker", nil, "68 - Haut-Rhin", "68", "Haut-Rhin" + it_behaves_like "a result checker", '', "68 - Haut-Rhin", "68", "Haut-Rhin" + it_behaves_like "a result checker", "69", "69 - Rhône", "69", "Rhône" + it_behaves_like "a result checker", nil, "69 - Rhône", "69", "Rhône" + it_behaves_like "a result checker", '', "69 - Rhône", "69", "Rhône" + it_behaves_like "a result checker", "70", "70 - Haute-Saône", "70", "Haute-Saône" + it_behaves_like "a result checker", nil, "70 - Haute-Saône", "70", "Haute-Saône" + it_behaves_like "a result checker", '', "70 - Haute-Saône", "70", "Haute-Saône" + it_behaves_like "a result checker", "71", "71 - Saône-et-Loire", "71", "Saône-et-Loire" + it_behaves_like "a result checker", nil, "71 - Saône-et-Loire", "71", "Saône-et-Loire" + it_behaves_like "a result checker", '', "71 - Saône-et-Loire", "71", "Saône-et-Loire" + it_behaves_like "a result checker", "72", "72 - Sarthe", "72", "Sarthe" + it_behaves_like "a result checker", nil, "72 - Sarthe", "72", "Sarthe" + it_behaves_like "a result checker", '', "72 - Sarthe", "72", "Sarthe" + it_behaves_like "a result checker", "73", "73 - Savoie", "73", "Savoie" + it_behaves_like "a result checker", nil, "73 - Savoie", "73", "Savoie" + it_behaves_like "a result checker", '', "73 - Savoie", "73", "Savoie" + it_behaves_like "a result checker", "74", "74 - Haute-Savoie", "74", "Haute-Savoie" + it_behaves_like "a result checker", nil, "74 - Haute-Savoie", "74", "Haute-Savoie" + it_behaves_like "a result checker", '', "74 - Haute-Savoie", "74", "Haute-Savoie" + it_behaves_like "a result checker", "75", "75 - Paris", "75", "Paris" + it_behaves_like "a result checker", nil, "75 - Paris", "75", "Paris" + it_behaves_like "a result checker", '', "75 - Paris", "75", "Paris" + it_behaves_like "a result checker", "76", "76 - Seine-Maritime", "76", "Seine-Maritime" + it_behaves_like "a result checker", nil, "76 - Seine-Maritime", "76", "Seine-Maritime" + it_behaves_like "a result checker", '', "76 - Seine-Maritime", "76", "Seine-Maritime" + it_behaves_like "a result checker", "77", "77 - Seine-et-Marne", "77", "Seine-et-Marne" + it_behaves_like "a result checker", nil, "77 - Seine-et-Marne", "77", "Seine-et-Marne" + it_behaves_like "a result checker", '', "77 - Seine-et-Marne", "77", "Seine-et-Marne" + it_behaves_like "a result checker", "78", "78 - Yvelines", "78", "Yvelines" + it_behaves_like "a result checker", '', "78 - Yvelines", "78", "Yvelines" + it_behaves_like "a result checker", nil, "78 - Yvelines", "78", "Yvelines" + it_behaves_like "a result checker", "79", "79 - Deux-Sèvres", "79", "Deux-Sèvres" + it_behaves_like "a result checker", nil, "79 - Deux-Sèvres", "79", "Deux-Sèvres" + it_behaves_like "a result checker", '', "79 - Deux-Sèvres", "79", "Deux-Sèvres" + it_behaves_like "a result checker", "80", "80 - Somme", "80", "Somme" + it_behaves_like "a result checker", nil, "80 - Somme", "80", "Somme" + it_behaves_like "a result checker", '', "80 - Somme", "80", "Somme" + it_behaves_like "a result checker", "81", "81 - Tarn", "81", "Tarn" + it_behaves_like "a result checker", '', "81 - Tarn", "81", "Tarn" + it_behaves_like "a result checker", nil, "81 - Tarn", "81", "Tarn" + it_behaves_like "a result checker", "82", "82 - Tarn-et-Garonne", "82", "Tarn-et-Garonne" + it_behaves_like "a result checker", nil, "82 - Tarn-et-Garonne", "82", "Tarn-et-Garonne" + it_behaves_like "a result checker", '', "82 - Tarn-et-Garonne", "82", "Tarn-et-Garonne" + it_behaves_like "a result checker", "83", "83 - Var", "83", "Var" + it_behaves_like "a result checker", nil, "83 - Var", "83", "Var" + it_behaves_like "a result checker", '', "83 - Var", "83", "Var" + it_behaves_like "a result checker", "84", "84 - Vaucluse", "84", "Vaucluse" + it_behaves_like "a result checker", nil, "84 - Vaucluse", "84", "Vaucluse" + it_behaves_like "a result checker", '', "84 - Vaucluse", "84", "Vaucluse" + it_behaves_like "a result checker", nil, "85", "85", "Vendée" + it_behaves_like "a result checker", "85", "85 - Vendée", "85", "Vendée" + it_behaves_like "a result checker", nil, "85 - Vendée", "85", "Vendée" + it_behaves_like "a result checker", '', "85 - Vendée", "85", "Vendée" + it_behaves_like "a result checker", "86", "86 - Vienne", "86", "Vienne" + it_behaves_like "a result checker", nil, "86 - Vienne", "86", "Vienne" + it_behaves_like "a result checker", '', "86 - Vienne", "86", "Vienne" + it_behaves_like "a result checker", "87", "87 - Haute-Vienne", "87", "Haute-Vienne" + it_behaves_like "a result checker", nil, "87 - Haute-Vienne", "87", "Haute-Vienne" + it_behaves_like "a result checker", '', "87 - Haute-Vienne", "87", "Haute-Vienne" + it_behaves_like "a result checker", "88", "88 - Vosges", "88", "Vosges" + it_behaves_like "a result checker", nil, "88 - Vosges", "88", "Vosges" + it_behaves_like "a result checker", '', "88 - Vosges", "88", "Vosges" + it_behaves_like "a result checker", "89", "89 - Yonne", "89", "Yonne" + it_behaves_like "a result checker", nil, "89 - Yonne", "89", "Yonne" + it_behaves_like "a result checker", '', "89 - Yonne", "89", "Yonne" + it_behaves_like "a result checker", "90", "90 - Territoire de Belfort", "90", "Territoire de Belfort" + it_behaves_like "a result checker", nil, "90 - Territoire de Belfort", "90", "Territoire de Belfort" + it_behaves_like "a result checker", '', "90 - Territoire de Belfort", "90", "Territoire de Belfort" + it_behaves_like "a result checker", "91", "91 - Essonne", "91", "Essonne" + it_behaves_like "a result checker", nil, "91 - Essonne", "91", "Essonne" + it_behaves_like "a result checker", '', "91 - Essonne", "91", "Essonne" + it_behaves_like "a result checker", "92", "92 - Hauts-de-Seine", "92", "Hauts-de-Seine" + it_behaves_like "a result checker", nil, "92 - Hauts-de-Seine", "92", "Hauts-de-Seine" + it_behaves_like "a result checker", '', "92 - Hauts-de-Seine", "92", "Hauts-de-Seine" + it_behaves_like "a result checker", "93", "93 - Seine-Saint-Denis", "93", "Seine-Saint-Denis" + it_behaves_like "a result checker", nil, "93 - Seine-Saint-Denis", "93", "Seine-Saint-Denis" + it_behaves_like "a result checker", '', "93 - Seine-Saint-Denis", "93", "Seine-Saint-Denis" + it_behaves_like "a result checker", "94", "94 - Val-de-Marne", "94", "Val-de-Marne" + it_behaves_like "a result checker", nil, "94 - Val-de-Marne", "94", "Val-de-Marne" + it_behaves_like "a result checker", '', "94 - Val-de-Marne", "94", "Val-de-Marne" + it_behaves_like "a result checker", "95", "95 - Val-d’Oise", "95", "Val-d’Oise" + it_behaves_like "a result checker", '', "95 - Val-d’Oise", "95", "Val-d’Oise" + it_behaves_like "a result checker", nil, "95 - Val-d’Oise", "95", "Val-d’Oise" + it_behaves_like "a result checker", "971", "971 - Guadeloupe", "971", "Guadeloupe" + it_behaves_like "a result checker", nil, "971 - Guadeloupe", "971", "Guadeloupe" + it_behaves_like "a result checker", '', "971 - Guadeloupe", "971", "Guadeloupe" + it_behaves_like "a result checker", "972", "972 - Martinique", "972", "Martinique" + it_behaves_like "a result checker", nil, "972 - Martinique", "972", "Martinique" + it_behaves_like "a result checker", '', "972 - Martinique", "972", "Martinique" + it_behaves_like "a result checker", "973", "973 - Guyane", "973", "Guyane" + it_behaves_like "a result checker", nil, "973 - Guyane", "973", "Guyane" + it_behaves_like "a result checker", '', "973 - Guyane", "973", "Guyane" + it_behaves_like "a result checker", "974", "974 - La Réunion", "974", "La Réunion" + it_behaves_like "a result checker", nil, "974 - La Réunion", "974", "La Réunion" + it_behaves_like "a result checker", '', "974 - La Réunion", "974", "La Réunion" + it_behaves_like "a result checker", "976", "976 - Mayotte", "976", "Mayotte" + it_behaves_like "a result checker", nil, "976 - Mayotte", "976", "Mayotte" + it_behaves_like "a result checker", '', "976 - Mayotte", "976", "Mayotte" + it_behaves_like "a result checker", "99", "99 - Etranger", "99", "Etranger" + it_behaves_like "a result checker", nil, "99 - Etranger", "99", "Etranger" + it_behaves_like "a result checker", '', "99 - Etranger", "99", "Etranger" + it_behaves_like "a result checker", '', "99 - Étranger", "99", "Etranger" + it_behaves_like "a result checker", nil, "99 - Étranger", "99", "Etranger" +end From daa7e17e706a33e5d46999c68be8ba883728af5b Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 13 Feb 2023 16:32:07 +0100 Subject: [PATCH 025/202] review: avoid in_batches --- .../deployment/20230208084036_normalize_departements.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tasks/deployment/20230208084036_normalize_departements.rake b/lib/tasks/deployment/20230208084036_normalize_departements.rake index 8ed162bd0..78c992b30 100644 --- a/lib/tasks/deployment/20230208084036_normalize_departements.rake +++ b/lib/tasks/deployment/20230208084036_normalize_departements.rake @@ -24,9 +24,9 @@ namespace :after_party do private def normalize_asynchronously(scope, progress, job) - scope.in_batches(of: 10_000) do |batch| - progress.inc(batch.count) - job.perform_later(batch.pluck(:id)) + scope.pluck(:id).in_groups_of(10_000, false) do |champ_ids| + job.perform_later(champ_ids) + progress.inc(champ_ids.count) end end end From cd3a72ddce91a54506762197028eb61e7e485a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Carceles?= Date: Fri, 20 Jan 2023 10:38:42 +0100 Subject: [PATCH 026/202] make departements champ prefillable --- app/models/type_de_champ.rb | 3 ++- spec/models/prefill_params_spec.rb | 4 +++- spec/models/type_de_champ_spec.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index afa6a821a..05091ec70 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -267,7 +267,8 @@ class TypeDeChamp < ApplicationRecord TypeDeChamp.type_champs.fetch(:yes_no), TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:drop_down_list), - TypeDeChamp.type_champs.fetch(:regions) + TypeDeChamp.type_champs.fetch(:regions), + TypeDeChamp.type_champs.fetch(:departements) ]) end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 84c99a26b..680a7beca 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -126,6 +126,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is authorized", :checkbox, "false" it_behaves_like "a champ public value that is authorized", :drop_down_list, "value" it_behaves_like "a champ public value that is authorized", :regions, "03" + it_behaves_like "a champ public value that is authorized", :departements, "03" it_behaves_like "a champ private value that is authorized", :text, "value" it_behaves_like "a champ private value that is authorized", :textarea, "value" @@ -144,6 +145,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :checkbox, "false" it_behaves_like "a champ private value that is authorized", :drop_down_list, "value" it_behaves_like "a champ private value that is authorized", :regions, "93" + it_behaves_like "a champ public value that is authorized", :departements, "03" it_behaves_like "a champ public value that is unauthorized", :decimal_number, "non decimal string" it_behaves_like "a champ public value that is unauthorized", :integer_number, "non integer string" @@ -169,7 +171,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is unauthorized", :address, "value" it_behaves_like "a champ public value that is unauthorized", :pays, "value" it_behaves_like "a champ public value that is unauthorized", :regions, "value" - it_behaves_like "a champ public value that is unauthorized", :departements, "value" + # TODO: SEB add validation it_behaves_like "a champ public value that is unauthorized", :departements, "value" it_behaves_like "a champ public value that is unauthorized", :siret, "value" it_behaves_like "a champ public value that is unauthorized", :rna, "value" it_behaves_like "a champ public value that is unauthorized", :annuaire_education, "value" diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index e1ca7bac6..9d891f5e1 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -250,6 +250,7 @@ describe TypeDeChamp do it_behaves_like "a prefillable type de champ", :type_de_champ_checkbox it_behaves_like "a prefillable type de champ", :type_de_champ_drop_down_list it_behaves_like "a prefillable type de champ", :type_de_champ_regions + it_behaves_like "a prefillable type de champ", :type_de_champ_departements it_behaves_like "a non-prefillable type de champ", :type_de_champ_number it_behaves_like "a non-prefillable type de champ", :type_de_champ_communes @@ -267,7 +268,6 @@ describe TypeDeChamp do it_behaves_like "a non-prefillable type de champ", :type_de_champ_mesri it_behaves_like "a non-prefillable type de champ", :type_de_champ_carte it_behaves_like "a non-prefillable type de champ", :type_de_champ_address - it_behaves_like "a non-prefillable type de champ", :type_de_champ_departements it_behaves_like "a non-prefillable type de champ", :type_de_champ_siret it_behaves_like "a non-prefillable type de champ", :type_de_champ_rna it_behaves_like "a non-prefillable type de champ", :type_de_champ_annuaire_education From a235829ddd4c088086e2beac1a68d2fd0f5a2a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Carceles?= Date: Fri, 20 Jan 2023 10:55:07 +0100 Subject: [PATCH 027/202] add possible and example values --- .../prefill_departement_type_de_champ.rb | 15 +++++++++++ .../types_de_champ/prefill_type_de_champ.rb | 2 ++ .../prefill_departement_type_de_champ_spec.rb | 26 +++++++++++++++++++ .../prefill_type_de_champ_spec.rb | 6 +++++ 4 files changed, 49 insertions(+) create mode 100644 app/models/types_de_champ/prefill_departement_type_de_champ.rb create mode 100644 spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb diff --git a/app/models/types_de_champ/prefill_departement_type_de_champ.rb b/app/models/types_de_champ/prefill_departement_type_de_champ.rb new file mode 100644 index 000000000..9a6fa807a --- /dev/null +++ b/app/models/types_de_champ/prefill_departement_type_de_champ.rb @@ -0,0 +1,15 @@ +class TypesDeChamp::PrefillDepartementTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + def possible_values + departements.map { |departement| "#{departement[:code]} (#{departement[:name]})" } + end + + def example_value + departements.pick(:code) + end + + private + + def departements + @departements ||= APIGeoService.departements.sort_by { |departement| departement[:code] } + end +end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 296c04c48..5e9e55380 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -9,6 +9,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator TypesDeChamp::PrefillPaysTypeDeChamp.new(type_de_champ) when TypeDeChamp.type_champs.fetch(:regions) TypesDeChamp::PrefillRegionTypeDeChamp.new(type_de_champ) + when TypeDeChamp.type_champs.fetch(:departements) + TypesDeChamp::PrefillDepartementTypeDeChamp.new(type_de_champ) else new(type_de_champ) end diff --git a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb new file mode 100644 index 000000000..bba1b47a7 --- /dev/null +++ b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do + let(:type_de_champ) { build(:type_de_champ_departements) } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + + describe '#possible_values', vcr: { cassette_name: 'api_geo_departements' } do + let(:expected_values) { + APIGeoService.departements.sort_by { |departement| departement[:code] }.map { |departement| "#{departement[:code]} (#{departement[:name]})" } + } + subject(:possible_values) { described_class.new(type_de_champ).possible_values } + + it { expect(possible_values).to match(expected_values) } + end + + describe '#example_value', vcr: { cassette_name: 'api_geo_departements' } do + subject(:example_value) { described_class.new(type_de_champ).example_value } + + it { expect(example_value).to eq(APIGeoService.departements.sort_by { |departement| departement[:code] }.first[:code]) } + end +end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index af0e3cfc9..52508be91 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -22,6 +22,12 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(built).to be_kind_of(TypesDeChamp::PrefillRegionTypeDeChamp) } end + context 'when the type de champ is a departements' do + let(:type_de_champ) { build(:type_de_champ_departements) } + + it { expect(built).to be_kind_of(TypesDeChamp::PrefillDepartementTypeDeChamp) } + end + context 'when any other type de champ' do let(:type_de_champ) { build(:type_de_champ_date) } From 12abf9045b53ea917456035e333ec5c0571e0f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Carceles?= Date: Fri, 20 Jan 2023 11:23:52 +0100 Subject: [PATCH 028/202] add external_id and value validation --- app/models/champs/departement_champ.rb | 18 ++++++ app/models/prefill_params.rb | 3 +- config/locales/en.yml | 6 ++ config/locales/fr.yml | 6 ++ .../api/v1/dossiers_controller_spec.rb | 9 ++- .../api/v2/graphql_controller_spec.rb | 3 +- spec/models/champs/departement_champ_spec.rb | 62 ++++++++++++++++++- spec/models/prefill_params_spec.rb | 4 +- 8 files changed, 104 insertions(+), 7 deletions(-) diff --git a/app/models/champs/departement_champ.rb b/app/models/champs/departement_champ.rb index 13edb4bb4..5a99ce8ad 100644 --- a/app/models/champs/departement_champ.rb +++ b/app/models/champs/departement_champ.rb @@ -21,6 +21,9 @@ # type_de_champ_id :integer # class Champs::DepartementChamp < Champs::TextChamp + validate :value_in_departement_names, unless: -> { value.nil? } + validate :external_id_in_departement_codes, unless: -> { external_id.nil? } + def for_export [name, code] end @@ -65,6 +68,9 @@ class Champs::DepartementChamp < Champs::TextChamp elsif code.blank? self.external_id = nil super(nil) + else + self.external_id = APIGeoService.departement_code(code) + super(code) end end @@ -73,4 +79,16 @@ class Champs::DepartementChamp < Champs::TextChamp def formatted_value blank? ? "" : "#{code} – #{name}" end + + def value_in_departement_names + return if value.in?(APIGeoService.departements.pluck(:name)) + + errors.add(:value, :not_in_departement_names) + end + + def external_id_in_departement_codes + return if external_id.in?(APIGeoService.departements.pluck(:code)) + + errors.add(:external_id, :not_in_departement_codes) + end end diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index e4ded1975..33261a8ac 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -40,7 +40,8 @@ class PrefillParams TypeDeChamp.type_champs.fetch(:yes_no), TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:pays), - TypeDeChamp.type_champs.fetch(:regions) + TypeDeChamp.type_champs.fetch(:regions), + TypeDeChamp.type_champs.fetch(:departements) ] attr_reader :champ, :value diff --git a/config/locales/en.yml b/config/locales/en.yml index dc8be05ec..b6ebae365 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -475,6 +475,12 @@ en: not_in_region_names: "must be a valid region name" external_id: not_in_region_codes: "must be a valid region code" + "champs/departement_champ": + attributes: + value: + not_in_departement_names: "must be a valid departement name" + external_id: + not_in_departement_codes: "must be a valid departement code" errors: format: "Field « %{attribute} » %{message}" messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d4b864c28..7ff6a3e7f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -470,6 +470,12 @@ fr: not_in_region_names: "doit être un nom de région valide" external_id: not_in_region_codes: "doit être un code de région valide" + "champs/departement_champ": + attributes: + value: + not_in_departement_names: "doit être un nom de département valide" + external_id: + not_in_departement_codes: "doit être un code de département valide" errors: format: "Le champ « %{attribute} » %{message}" messages: diff --git a/spec/controllers/api/v1/dossiers_controller_spec.rb b/spec/controllers/api/v1/dossiers_controller_spec.rb index af705addd..9c7ef5fe3 100644 --- a/spec/controllers/api/v1/dossiers_controller_spec.rb +++ b/spec/controllers/api/v1/dossiers_controller_spec.rb @@ -4,6 +4,13 @@ describe API::V1::DossiersController do let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, administrateur: admin) } let(:wrong_procedure) { create(:procedure) } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + it { expect(described_class).to be < APIController } describe 'GET index (with bearer token)' do @@ -258,7 +265,7 @@ describe API::V1::DossiersController do end end - describe 'departement' do + describe 'departement', vcr: { cassette_name: 'api_geo_departements' } do let(:procedure) { create(:procedure, :with_departement, administrateur: admin) } let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure: procedure) } diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index acfe61304..6f70743cd 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -42,7 +42,6 @@ describe API::V2::GraphqlController do allow(Rails).to receive(:cache).and_return(memory_store) Rails.cache.clear - allow(APIGeoService).to receive(:departement_name).with('01').and_return('Ain') instructeur.assign_to_procedure(procedure) end @@ -397,7 +396,7 @@ describe API::V2::GraphqlController do dossier end - context "for individual", vcr: { cassette_name: 'api_geo_regions' } do + context "for individual", vcr: { cassette_name: 'api_geo_all' } do let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_champs, :with_all_annotations, administrateurs: [admin]) } let(:query) do "{ diff --git a/spec/models/champs/departement_champ_spec.rb b/spec/models/champs/departement_champ_spec.rb index 35353f865..1a70439d5 100644 --- a/spec/models/champs/departement_champ_spec.rb +++ b/spec/models/champs/departement_champ_spec.rb @@ -6,9 +6,69 @@ describe Champs::DepartementChamp, type: :model do Rails.cache.clear end - let(:champ) { described_class.new } + describe 'validations', vcr: { cassette_name: 'api_geo_departements' } do + describe 'external link' do + subject { build(:champ_departements, external_id: external_id) } + + context 'when nil' do + let(:external_id) { nil } + + it { is_expected.to be_valid } + end + + context 'when blank' do + let(:external_id) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when included in the departement codes' do + let(:external_id) { "01" } + + it { is_expected.to be_valid } + end + + context 'when not included in the departement codes' do + let(:external_id) { "totoro" } + + it { is_expected.not_to be_valid } + end + end + + describe 'value' do + subject { create(:champ_departements) } + + before { subject.update_columns(value: value) } + + context 'when nil' do + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when blank' do + let(:value) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when included in the departement names' do + let(:value) { "Ain" } + + it { is_expected.to be_valid } + end + + context 'when not included in the departement names' do + let(:value) { "totoro" } + + it { is_expected.not_to be_valid } + end + end + end describe 'value', vcr: { cassette_name: 'api_geo_departements' } do + let(:champ) { described_class.new } + it 'with code having 2 chars' do champ.value = '01' expect(champ.external_id).to eq('01') diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 680a7beca..2c9ff077a 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -1,5 +1,5 @@ RSpec.describe PrefillParams do - describe "#to_a", vcr: { cassette_name: 'api_geo_regions' } do + describe "#to_a", vcr: { cassette_name: 'api_geo_all' } do let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } let(:procedure) { create(:procedure, :published, types_de_champ_public:, types_de_champ_private:) } @@ -171,7 +171,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is unauthorized", :address, "value" it_behaves_like "a champ public value that is unauthorized", :pays, "value" it_behaves_like "a champ public value that is unauthorized", :regions, "value" - # TODO: SEB add validation it_behaves_like "a champ public value that is unauthorized", :departements, "value" + it_behaves_like "a champ public value that is unauthorized", :departements, "value" it_behaves_like "a champ public value that is unauthorized", :siret, "value" it_behaves_like "a champ public value that is unauthorized", :rna, "value" it_behaves_like "a champ public value that is unauthorized", :annuaire_education, "value" From a56efe22e5c2081832cd2d9dd0fbe1e36a1dba4b Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 24 Jan 2023 08:25:03 +0100 Subject: [PATCH 029/202] review: link about department numbers --- config/locales/en.yml | 5 +++-- config/locales/fr.yml | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index b6ebae365..4994f9127 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -130,6 +130,7 @@ en: yes_no_html: '"true" for Yes, "false" pour No' checkbox_html: '"true" to check, "false" to uncheck' pays_html: An ISO 3166-2 country code + departements_html: A department number regions_html: An INSEE region code date_html: ISO8601 date datetime_html: ISO8601 datetime @@ -478,9 +479,9 @@ en: "champs/departement_champ": attributes: value: - not_in_departement_names: "must be a valid departement name" + not_in_departement_names: "must be a valid department name" external_id: - not_in_departement_codes: "must be a valid departement code" + not_in_departement_codes: "must be a valid department code" errors: format: "Field « %{attribute} » %{message}" messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 7ff6a3e7f..b8402dfb2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -121,6 +121,7 @@ fr: yes_no_html: '"true" pour Oui, "false" pour Non' checkbox_html: '"true" pour coché, "false" pour décoché' pays_html: Un code pays ISO 3166-2 + departements_html: Un numéro de département regions_html: Un code INSEE de région datetime_html: Datetime au format ISO8601 date_html: Date au format ISO8601 From 67ef3a16faabc71565cae4d43a1473c5e8dc7900 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 24 Jan 2023 08:31:19 +0100 Subject: [PATCH 030/202] review: put example values in translation yamls --- .../prefill_departement_type_de_champ.rb | 4 ---- .../types_de_champ/prefill_pays_type_de_champ.rb | 4 ---- config/locales/en.yml | 1 + config/locales/fr.yml | 1 + .../prefill_departement_type_de_champ_spec.rb | 12 ++++++------ .../prefill_pays_type_de_champ_spec.rb | 12 ++++++------ 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/app/models/types_de_champ/prefill_departement_type_de_champ.rb b/app/models/types_de_champ/prefill_departement_type_de_champ.rb index 9a6fa807a..2ec1d1d11 100644 --- a/app/models/types_de_champ/prefill_departement_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_departement_type_de_champ.rb @@ -3,10 +3,6 @@ class TypesDeChamp::PrefillDepartementTypeDeChamp < TypesDeChamp::PrefillTypeDeC departements.map { |departement| "#{departement[:code]} (#{departement[:name]})" } end - def example_value - departements.pick(:code) - end - private def departements diff --git a/app/models/types_de_champ/prefill_pays_type_de_champ.rb b/app/models/types_de_champ/prefill_pays_type_de_champ.rb index 143f041e7..6e5f9952c 100644 --- a/app/models/types_de_champ/prefill_pays_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_pays_type_de_champ.rb @@ -3,10 +3,6 @@ class TypesDeChamp::PrefillPaysTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp countries.map { |country| "#{country[:code]} (#{country[:name]})" } end - def example_value - countries.pick(:code) - end - private def countries diff --git a/config/locales/en.yml b/config/locales/en.yml index 4994f9127..c9f4af19e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -146,6 +146,7 @@ en: iban: FR7611315000011234567890138 yes_no: "true" pays: "FR" + departements: "56" regions: "53" date: "2023-02-01" datetime: "2023-02-01T10:30" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b8402dfb2..b530475fc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -138,6 +138,7 @@ fr: yes_no: "true" civilite: "M." pays: "FR" + departements: "56" regions: "53" date: "2023-02-01" datetime: "2023-02-01T10:30" diff --git a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb index bba1b47a7..f43a07393 100644 --- a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb @@ -9,6 +9,12 @@ RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do Rails.cache.clear end + describe 'ancestors' do + subject { described_class.build(type_de_champ) } + + it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } + end + describe '#possible_values', vcr: { cassette_name: 'api_geo_departements' } do let(:expected_values) { APIGeoService.departements.sort_by { |departement| departement[:code] }.map { |departement| "#{departement[:code]} (#{departement[:name]})" } @@ -17,10 +23,4 @@ RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do it { expect(possible_values).to match(expected_values) } end - - describe '#example_value', vcr: { cassette_name: 'api_geo_departements' } do - subject(:example_value) { described_class.new(type_de_champ).example_value } - - it { expect(example_value).to eq(APIGeoService.departements.sort_by { |departement| departement[:code] }.first[:code]) } - end end diff --git a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb index f651d3d60..67789ba9e 100644 --- a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb @@ -2,16 +2,16 @@ RSpec.describe TypesDeChamp::PrefillPaysTypeDeChamp, type: :model do let(:type_de_champ) { build(:type_de_champ_pays) } + describe 'ancestors' do + subject { described_class.build(type_de_champ) } + + it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } + end + describe '#possible_values' do let(:expected_values) { APIGeoService.countries.sort_by { |country| country[:code] }.map { |country| "#{country[:code]} (#{country[:name]})" } } subject(:possible_values) { described_class.new(type_de_champ).possible_values } it { expect(possible_values).to match(expected_values) } end - - describe '#example_value' do - subject(:example_value) { described_class.new(type_de_champ).example_value } - - it { expect(example_value).to eq(APIGeoService.countries.sort_by { |country| country[:code] }.first[:code]) } - end end From 81bc898cfb9296658941af8630cf03262fb9e06b Mon Sep 17 00:00:00 2001 From: Julie Salha Date: Thu, 9 Feb 2023 11:12:04 +0100 Subject: [PATCH 031/202] optimization burger menu dsfr --- app/views/layouts/_header.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.haml b/app/views/layouts/_header.haml index e7916f90e..41c23b0e5 100644 --- a/app/views/layouts/_header.haml +++ b/app/views/layouts/_header.haml @@ -72,7 +72,7 @@ = render partial: 'layouts/search_dossiers_form', locals: { search_endpoint: recherche_dossiers_path } - has_header = [is_instructeur_context, is_expert_context, is_user_context] - #burger-menu.fr-header__menu.fr-modal{ "aria-label" => t('layouts.header.label_modal') } + #burger-menu.fr-header__menu.fr-modal .fr-container %button#burger_button.fr-btn--close.fr-btn{ "aria-controls" => "burger-menu", :title => t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header]) .fr-header__menu-links From ee009a2c7e7048a74fafe39f7db57b6a712cb121 Mon Sep 17 00:00:00 2001 From: Julie Salha Date: Thu, 9 Feb 2023 11:18:39 +0100 Subject: [PATCH 032/202] fix PR: delete translations --- config/locales/en.yml | 1 - config/locales/fr.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index c9f4af19e..d1a6b9638 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -68,7 +68,6 @@ en: are_you_new: First time on %{app_name}? my_account: My account header: - label_modal: "Burger menu" close_modal: 'Close' back: "Back" back_title: "Revenir sur le site de mon administration" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b530475fc..13aa78315 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -59,7 +59,6 @@ fr: are_you_new: Vous êtes nouveau sur %{app_name} ? my_account: Mon compte header: - label_modal: "Menu en-tête de page" close_modal: 'Fermer' back: "Revenir en arrière" back_title: "Revenir sur le site de mon administration" From a77a0e2c7f7a65b5e1c3585855ed1bb1ae18514f Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 14 Feb 2023 11:35:35 +0100 Subject: [PATCH 033/202] :coffin: dead code is dead --- app/models/types_de_champ/prefill_repetition_type_de_champ.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 8b729dc25..c1a879c5c 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -25,10 +25,6 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh private - def too_many_possible_values? - false - end - def subchamps_possible_values_list "
      " + prefillable_subchamps.map do |prefill_type_de_champ| "
    • #{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values}
    • " From 0159a1096930aa69edff7c83646e5d02cda87cfa Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 14 Feb 2023 11:41:35 +0100 Subject: [PATCH 034/202] :bug: make possible_values_list method public --- .../prefill_drop_down_list_type_de_champ.rb | 10 ++++------ .../types_de_champ/prefill_pays_type_de_champ.rb | 8 ++++---- .../types_de_champ/prefill_region_type_de_champ.rb | 4 ++-- app/models/types_de_champ/prefill_type_de_champ.rb | 12 ++++++------ .../prefill_type_de_champs_controller_spec.rb | 2 ++ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb index b5505151f..648f2d17e 100644 --- a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb @@ -1,10 +1,4 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def example_value - possible_values_list.first - end - - private - def possible_values_list if drop_down_other? drop_down_list_enabled_non_empty_options.insert( @@ -15,4 +9,8 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDe drop_down_list_enabled_non_empty_options end end + + def example_value + possible_values_list.first + end end diff --git a/app/models/types_de_champ/prefill_pays_type_de_champ.rb b/app/models/types_de_champ/prefill_pays_type_de_champ.rb index cf0fa0278..a5e6aad21 100644 --- a/app/models/types_de_champ/prefill_pays_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_pays_type_de_champ.rb @@ -1,14 +1,14 @@ class TypesDeChamp::PrefillPaysTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + def possible_values_list + countries.map { |country| "#{country[:code]} (#{country[:name]})" } + end + def example_value countries.pick(:code) end private - def possible_values_list - countries.map { |country| "#{country[:code]} (#{country[:name]})" } - end - def countries @countries ||= APIGeoService.countries.sort_by { |country| country[:code] } end diff --git a/app/models/types_de_champ/prefill_region_type_de_champ.rb b/app/models/types_de_champ/prefill_region_type_de_champ.rb index 6362fccac..7d52601b4 100644 --- a/app/models/types_de_champ/prefill_region_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_region_type_de_champ.rb @@ -1,10 +1,10 @@ class TypesDeChamp::PrefillRegionTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - private - def possible_values_list regions.map { |region| "#{region[:code]} (#{region[:name]})" } end + private + def regions @regions ||= APIGeoService.regions.sort_by { |region| region[:code] } end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 8b6c0bbba..3ab56de61 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -27,6 +27,12 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator [possible_values_list_display, link_to_all_possible_values].compact.join('
      ').html_safe # rubocop:disable Rails/OutputSafety end + def possible_values_list + return [] unless prefillable? + + [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] + end + def example_value return nil unless prefillable? @@ -43,12 +49,6 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator private - def possible_values_list - return [] unless prefillable? - - [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] - end - def link_to_all_possible_values return unless too_many_possible_values? && prefillable? diff --git a/spec/controllers/prefill_type_de_champs_controller_spec.rb b/spec/controllers/prefill_type_de_champs_controller_spec.rb index a4b61f19c..5e13be629 100644 --- a/spec/controllers/prefill_type_de_champs_controller_spec.rb +++ b/spec/controllers/prefill_type_de_champs_controller_spec.rb @@ -8,6 +8,8 @@ RSpec.describe PrefillTypeDeChampsController, type: :controller do context 'when the procedure is found' do context 'when the procedure is publiee' do context 'when the procedure is opendata' do + render_views + let(:procedure) { create(:procedure, :published, opendata: true) } it { expect(show_request).to render_template(:show) } From a07446da6ce6d470bccdb3200d756a59c48ae98b Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 14 Feb 2023 11:45:39 +0100 Subject: [PATCH 035/202] :recycle: rename method for clarity --- .../types_de_champ/prefill_drop_down_list_type_de_champ.rb | 4 ++-- app/models/types_de_champ/prefill_pays_type_de_champ.rb | 2 +- app/models/types_de_champ/prefill_region_type_de_champ.rb | 2 +- .../types_de_champ/prefill_repetition_type_de_champ.rb | 4 ++-- app/models/types_de_champ/prefill_type_de_champ.rb | 6 +++--- app/views/prefill_type_de_champs/show.html.haml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb index 648f2d17e..6930905e7 100644 --- a/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_drop_down_list_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values_list + def all_possible_values if drop_down_other? drop_down_list_enabled_non_empty_options.insert( 0, @@ -11,6 +11,6 @@ class TypesDeChamp::PrefillDropDownListTypeDeChamp < TypesDeChamp::PrefillTypeDe end def example_value - possible_values_list.first + all_possible_values.first end end diff --git a/app/models/types_de_champ/prefill_pays_type_de_champ.rb b/app/models/types_de_champ/prefill_pays_type_de_champ.rb index a5e6aad21..9279b2f97 100644 --- a/app/models/types_de_champ/prefill_pays_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_pays_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillPaysTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values_list + def all_possible_values countries.map { |country| "#{country[:code]} (#{country[:name]})" } end diff --git a/app/models/types_de_champ/prefill_region_type_de_champ.rb b/app/models/types_de_champ/prefill_region_type_de_champ.rb index 7d52601b4..ae9d0501a 100644 --- a/app/models/types_de_champ/prefill_region_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_region_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillRegionTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values_list + def all_possible_values regions.map { |region| "#{region[:code]} (#{region[:name]})" } end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index c1a879c5c..6cf1f5153 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -5,7 +5,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh def possible_values [ I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html"), - subchamps_possible_values_list + subchamps_all_possible_values ].join("
      ").html_safe # rubocop:disable Rails/OutputSafety end @@ -25,7 +25,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh private - def subchamps_possible_values_list + def subchamps_all_possible_values "
        " + prefillable_subchamps.map do |prefill_type_de_champ| "
      • #{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values}
      • " end.join + "
      " diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 3ab56de61..31422be58 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -27,7 +27,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator [possible_values_list_display, link_to_all_possible_values].compact.join('
      ').html_safe # rubocop:disable Rails/OutputSafety end - def possible_values_list + def all_possible_values return [] unless prefillable? [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] @@ -56,14 +56,14 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator end def too_many_possible_values? - possible_values_list.count > POSSIBLE_VALUES_THRESHOLD + all_possible_values.count > POSSIBLE_VALUES_THRESHOLD end def possible_values_list_display if too_many_possible_values? I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe # rubocop:disable Rails/OutputSafety else - possible_values_list.to_sentence + all_possible_values.to_sentence end end end diff --git a/app/views/prefill_type_de_champs/show.html.haml b/app/views/prefill_type_de_champs/show.html.haml index 8a1610340..67b52ec66 100644 --- a/app/views/prefill_type_de_champs/show.html.haml +++ b/app/views/prefill_type_de_champs/show.html.haml @@ -26,7 +26,7 @@ = t("views.prefill_descriptions.edit.possible_values.title") %td .fr-grid-row.fr-grid-row--gutters.fr-py-5w - - @type_de_champ.possible_values_list.each do |possible_value| + - @type_de_champ.all_possible_values.each do |possible_value| .fr-col-lg-3.fr-col-md-4.fr-col-sm-6.fr-col-12 = possible_value %tr From 4fee6f3626143aea4369a3dc10c789479510f9c3 Mon Sep 17 00:00:00 2001 From: Julie Salha Date: Tue, 14 Feb 2023 12:17:44 +0100 Subject: [PATCH 036/202] update label button --- app/javascript/components/MapEditor/components/PointInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/MapEditor/components/PointInput.tsx b/app/javascript/components/MapEditor/components/PointInput.tsx index 6bd7496a6..78b7a6cb3 100644 --- a/app/javascript/components/MapEditor/components/PointInput.tsx +++ b/app/javascript/components/MapEditor/components/PointInput.tsx @@ -41,9 +41,9 @@ export function PointInput() { type="button" className="button mr-1" onClick={getCurrentPosition} - title="Localiser votre position" + title="Localiser votre position et l'afficher sur la carte" > - Localiser votre position + Localiser votre position et l'afficher sur la carte ) : null} From 6db07ca2a3f9262d0425e1bd9dc0444242af93ab Mon Sep 17 00:00:00 2001 From: Julie Salha Date: Tue, 14 Feb 2023 12:26:57 +0100 Subject: [PATCH 037/202] fix PR wording --- .../components/MapEditor/components/PointInput.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/MapEditor/components/PointInput.tsx b/app/javascript/components/MapEditor/components/PointInput.tsx index 78b7a6cb3..c7ef342b0 100644 --- a/app/javascript/components/MapEditor/components/PointInput.tsx +++ b/app/javascript/components/MapEditor/components/PointInput.tsx @@ -41,9 +41,11 @@ export function PointInput() { type="button" className="button mr-1" onClick={getCurrentPosition} - title="Localiser votre position et l'afficher sur la carte" + title="Afficher votre position sur la carte" > - Localiser votre position et l'afficher sur la carte + + Afficher votre position sur la carte + ) : null} From 681e1cf51873e32faa9afd810a80124b78255fea Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 14 Feb 2023 13:45:30 +0100 Subject: [PATCH 038/202] :recycle: refactor possible_values impl for clarity --- .../types_de_champ/prefill_type_de_champ.rb | 23 +++---- .../prefill_type_de_champ_spec.rb | 62 ++++++++++++++----- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 31422be58..aebd01ae2 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -24,13 +24,18 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator end def possible_values - [possible_values_list_display, link_to_all_possible_values].compact.join('
      ').html_safe # rubocop:disable Rails/OutputSafety + values = [] + values << description if description.present? + if too_many_possible_values? + values << link_to_all_possible_values + else + values << all_possible_values.to_sentence + end + values.compact.join('
      ').html_safe # rubocop:disable Rails/OutputSafety end def all_possible_values - return [] unless prefillable? - - [I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html")] + [] end def example_value @@ -50,7 +55,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator private def link_to_all_possible_values - return unless too_many_possible_values? && prefillable? + return unless prefillable? link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes end @@ -59,11 +64,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator all_possible_values.count > POSSIBLE_VALUES_THRESHOLD end - def possible_values_list_display - if too_many_possible_values? - I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html").html_safe # rubocop:disable Rails/OutputSafety - else - all_possible_values.to_sentence - end + def description + @description ||= I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html", default: nil)&.html_safe # rubocop:disable Rails/OutputSafety end end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index d5e322155..b0c19a666 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do + include ActionView::Helpers::UrlHelper + include ApplicationHelper + describe '.build' do subject(:built) { described_class.build(type_de_champ) } @@ -45,26 +48,57 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do end describe '#possible_values' do - subject(:possible_values) { described_class.new(type_de_champ).possible_values } + let(:built) { described_class.build(type_de_champ) } + subject(:possible_values) { built.possible_values } + + context 'when the type de champ is prefillable' do + context 'when the type de champ has a description' do + let(:type_de_champ) { build(:type_de_champ_text) } + + it { expect(possible_values).to include(I18n.t("views.prefill_descriptions.edit.possible_values.#{type_de_champ.type_champ}_html")) } + end + + context 'when the type de champ does not have a description' do + let(:type_de_champ) { build(:type_de_champ_mesri) } + + it { expect(possible_values).not_to include(I18n.t("views.prefill_descriptions.edit.possible_values.#{type_de_champ.type_champ}_html")) } + end + + describe 'too many possible values or not' do + let!(:procedure) { create(:procedure, :with_drop_down_list) } + let(:type_de_champ) { procedure.draft_types_de_champ_public.first } + let(:link_to_all_possible_values) { + link_to( + I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), + Rails.application.routes.url_helpers.prefill_type_de_champ_path(type_de_champ.path, type_de_champ), + title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), + **external_link_attributes + ) + } + + context 'when there is too many possible values' do + before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD + 1).map(&:to_s) } + + it { expect(possible_values).to include(link_to_all_possible_values) } + + it { expect(possible_values).not_to include(built.all_possible_values.to_sentence) } + end + + context 'when there is not too many possible values' do + before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD - 1).map(&:to_s) } + + it { expect(possible_values).not_to include(link_to_all_possible_values) } + + it { expect(possible_values).to include(built.all_possible_values.to_sentence) } + end + end + end context 'when the type de champ is not prefillable' do let(:type_de_champ) { build(:type_de_champ_mesri) } it { expect(possible_values).to be_empty } end - - context 'when there is too many possible values' do - let(:type_de_champ) { create(:type_de_champ_drop_down_list, procedure: create(:procedure)) } - before { type_de_champ.drop_down_options = (1..described_class::POSSIBLE_VALUES_THRESHOLD + 1).map(&:to_s) } - - it { expect(possible_values).to eq("Un choix parmi ceux sélectionnés à la création de la procédure") } - end - - context 'when the type de champ is prefillable' do - let(:type_de_champ) { build(:type_de_champ_email) } - - it { expect(possible_values).to eq("Une adresse email") } - end end describe '#example_value' do From 6f65a4c2feafaf31af1e001314f71563bbf9e534 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 14 Feb 2023 13:49:41 +0100 Subject: [PATCH 039/202] :rotating_light: line length --- app/models/types_de_champ/prefill_type_de_champ.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index aebd01ae2..45b8f07cc 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -57,7 +57,12 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator def link_to_all_possible_values return unless prefillable? - link_to I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes + link_to( + I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), + Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), + title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), + **external_link_attributes + ) end def too_many_possible_values? From c45f15ff61771985d20d710fe8c6d1bfe0fe0c64 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 13 Feb 2023 15:08:55 +0100 Subject: [PATCH 040/202] correctif(procedure/all.xsls): deconnecte le lien de telechargement de toutes les demarche de turbo --- .../administrateurs/procedures/all.html.haml | 2 +- .../administrateurs/procedure_cloning_spec.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/views/administrateurs/procedures/all.html.haml b/app/views/administrateurs/procedures/all.html.haml index 1ac68b3be..56ad5e129 100644 --- a/app/views/administrateurs/procedures/all.html.haml +++ b/app/views/administrateurs/procedures/all.html.haml @@ -11,7 +11,7 @@ = f.search_field 'libelle', size: 30, class: 'fr-input' .actions .link.fr-mx-1w= link_to 'Voir les administrateurs', administrateurs_admin_procedures_path(@filter.params), class: 'fr-btn fr-btn--secondary' - .link.fr-mx-1w= link_to 'Exporter les résultats', all_admin_procedures_path(@filter.params.merge(format: :xlsx)), class: 'fr-btn fr-btn--secondary' + .link.fr-mx-1w{ "data-turbo": "false" }= link_to 'Exporter les résultats', all_admin_procedures_path(@filter.params.merge(format: :xlsx)), class: 'fr-btn fr-btn--secondary' .fr-table.fr-table--bordered %table#all-demarches %caption diff --git a/spec/system/administrateurs/procedure_cloning_spec.rb b/spec/system/administrateurs/procedure_cloning_spec.rb index dc6dfafe4..a53545f09 100644 --- a/spec/system/administrateurs/procedure_cloning_spec.rb +++ b/spec/system/administrateurs/procedure_cloning_spec.rb @@ -14,7 +14,22 @@ describe 'As an administrateur I wanna clone a procedure', js: true do published_at: Time.zone.now) login_as administrateur.user, scope: :user end + context 'Visit all admin procedures' do + let(:download_dir) { Rails.root.join('tmp/capybara') } + let(:download_file_pattern) { download_dir.join('*.xlsx') } + scenario do + Dir[download_file_pattern].map { File.delete(_1) } + visit all_admin_procedures_path + + click_on "Exporter les résultats" + Timeout.timeout(Capybara.default_max_wait_time, + Timeout::Error, + "File download timeout! can't download procedure/all.xlsx") do + sleep 0.1 until !Dir[download_file_pattern].empty? + end + end + end context 'Cloning a procedure owned by the current admin' do scenario do visit admin_procedures_path From 8b74a6f39b1f32e76f20e9bebddd77f2880142d4 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 14 Feb 2023 18:38:01 +0100 Subject: [PATCH 041/202] fix(geometry): implement our own bbox to replace rgeo --- app/graphql/types/geo_area_type.rb | 2 +- app/models/champs/carte_champ.rb | 13 ++---- app/models/dossier.rb | 9 +--- app/models/geo_area.rb | 32 ++++++++------ app/serializers/champ_serializer.rb | 2 +- app/serializers/geo_area_serializer.rb | 2 +- app/services/geojson_service.rb | 60 ++++++++++++++++++++++++++ spec/models/champs/carte_champ_spec.rb | 2 +- spec/models/dossier_spec.rb | 4 +- spec/models/geo_area_spec.rb | 6 +-- 10 files changed, 93 insertions(+), 39 deletions(-) diff --git a/app/graphql/types/geo_area_type.rb b/app/graphql/types/geo_area_type.rb index 5827b3576..32bc7a26e 100644 --- a/app/graphql/types/geo_area_type.rb +++ b/app/graphql/types/geo_area_type.rb @@ -12,7 +12,7 @@ module Types global_id_field :id field :source, GeoAreaSource, null: false - field :geometry, Types::GeoJSON, null: false, method: :safe_geometry + field :geometry, Types::GeoJSON, null: false field :description, String, null: true definition_methods do diff --git a/app/models/champs/carte_champ.rb b/app/models/champs/carte_champ.rb index 8d86789fc..208b72dfb 100644 --- a/app/models/champs/carte_champ.rb +++ b/app/models/champs/carte_champ.rb @@ -64,21 +64,14 @@ class Champs::CarteChamp < Champ end def bounding_box - factory = RGeo::Geographic.simple_mercator_factory - bounding_box = RGeo::Cartesian::BoundingBox.new(factory) - if geo_areas.present? - geo_areas.filter_map(&:rgeo_geometry).each do |geometry| - bounding_box.add(geometry) - end + GeojsonService.bbox(type: 'FeatureCollection', features: geo_areas.map(&:to_feature)) elsif dossier.present? point = dossier.geo_position - bounding_box.add(factory.point(point[:lon], point[:lat])) + GeojsonService.bbox(type: 'Feature', geometry: { type: 'Point', coordinates: [point[:lon], point[:lat]] }) else - bounding_box.add(factory.point(DEFAULT_LON, DEFAULT_LAT)) + GeojsonService.bbox(type: 'Feature', geometry: { type: 'Point', coordinates: [DEFAULT_LON, DEFAULT_LAT] }) end - - [bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates) end def to_feature_collection diff --git a/app/models/dossier.rb b/app/models/dossier.rb index db8893a17..31eb93b03 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1312,14 +1312,7 @@ class Dossier < ApplicationRecord end def bounding_box - factory = RGeo::Geographic.simple_mercator_factory - bounding_box = RGeo::Cartesian::BoundingBox.new(factory) - - geo_areas.filter_map(&:rgeo_geometry).each do |geometry| - bounding_box.add(geometry) - end - - [bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates) + GeojsonService.bbox(type: 'FeatureCollection', features: geo_areas.map(&:to_feature)) end def log_dossier_operation(author, operation, subject = nil) diff --git a/app/models/geo_area.rb b/app/models/geo_area.rb index 024b20d4d..57040c80d 100644 --- a/app/models/geo_area.rb +++ b/app/models/geo_area.rb @@ -52,11 +52,12 @@ class GeoArea < ApplicationRecord scope :cadastres, -> { where(source: sources.fetch(:cadastre)) } validates :geometry, geo_json: true, allow_blank: false + before_validation :normalize_geometry def to_feature { type: 'Feature', - geometry: safe_geometry, + geometry: geometry.deep_symbolize_keys, properties: cadastre_properties.merge( source: source, area: area, @@ -96,16 +97,6 @@ class GeoArea < ApplicationRecord end end - def safe_geometry - RGeo::GeoJSON.encode(rgeo_geometry) - end - - def rgeo_geometry - RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) - rescue RGeo::Error::InvalidGeometry - nil - end - def area if polygon? GeojsonService.area(geometry.deep_symbolize_keys).round(1) @@ -120,7 +111,7 @@ class GeoArea < ApplicationRecord def location if point? - Geo::Coord.new(*rgeo_geometry.coordinates.reverse).to_s + Geo::Coord.new(*geometry['coordinates'].reverse).to_s end end @@ -238,4 +229,21 @@ class GeoArea < ApplicationRecord properties['id'] end end + + private + + def normalize_geometry + if geometry.present? + normalized_geometry = rgeo_geometry + if normalized_geometry.present? + self.geometry = RGeo::GeoJSON.encode(normalized_geometry) + end + end + end + + def rgeo_geometry + RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) + rescue RGeo::Error::InvalidGeometry + nil + end end diff --git a/app/serializers/champ_serializer.rb b/app/serializers/champ_serializer.rb index 9802a4874..670979c81 100644 --- a/app/serializers/champ_serializer.rb +++ b/app/serializers/champ_serializer.rb @@ -13,7 +13,7 @@ class ChampSerializer < ActiveModel::Serializer def value case object when GeoArea - object.safe_geometry + object.geometry else object.for_api end diff --git a/app/serializers/geo_area_serializer.rb b/app/serializers/geo_area_serializer.rb index f0ff85ccb..39fb6cfd3 100644 --- a/app/serializers/geo_area_serializer.rb +++ b/app/serializers/geo_area_serializer.rb @@ -12,7 +12,7 @@ class GeoAreaSerializer < ActiveModel::Serializer attribute :code_arr, if: :include_cadastre? def geometry - object.safe_geometry + object.geometry end def include_cadastre? diff --git a/app/services/geojson_service.rb b/app/services/geojson_service.rb index d2a78d884..154387193 100644 --- a/app/services/geojson_service.rb +++ b/app/services/geojson_service.rb @@ -45,6 +45,66 @@ class GeojsonService radians * EQUATORIAL_RADIUS end + def self.bbox(geojson) + result = [-Float::INFINITY, -Float::INFINITY, Float::INFINITY, Float::INFINITY] + + self.coord_each(geojson) do |coord| + if result[3] > coord[1] + result[3] = coord[1] + end + if result[2] > coord[0] + result[2] = coord[0] + end + if result[1] < coord[1] + result[1] = coord[1] + end + if result[0] < coord[0] + result[0] = coord[0] + end + end + + result + end + + def self.coord_each(geojson) + geometries = if geojson.fetch(:type) == "FeatureCollection" + geojson.fetch(:features).map { _1.fetch(:geometry) } + else + [geojson.fetch(:geometry)] + end.compact + + geometries.each do |geometry| + geometries = if geometry.fetch(:type) == "GeometryCollection" + geometry.fetch(:geometries) + else + [geometry] + end.compact + + geometries.each do |geometry| + case geometry.fetch(:type) + when "Point" + yield geometry.fetch(:coordinates).map(&:to_f) + when "LineString", "MultiPoint" + geometry.fetch(:coordinates).each { yield _1.map(&:to_f) } + when "Polygon", "MultiLineString" + geometry.fetch(:coordinates).each do |shapes| + shapes.each { yield _1.map(&:to_f) } + end + when "MultiPolygon" + geometry.fetch(:coordinates).each do |polygons| + polygons.each do |shapes| + shapes.each { yield _1.map(&:to_f) } + end + end + when "GeometryCollection" + geometry.fetch(:geometries).each do |geometry| + coord_each(geometry) { yield _1 } + end + end + end + end + end + def self.calculate_area(geom) total = 0 case geom[:type] diff --git a/spec/models/champs/carte_champ_spec.rb b/spec/models/champs/carte_champ_spec.rb index b006ad092..c7e4e5c99 100644 --- a/spec/models/champs/carte_champ_spec.rb +++ b/spec/models/champs/carte_champ_spec.rb @@ -1,7 +1,7 @@ describe Champs::CarteChamp do let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) } let(:value) { '' } - let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] } + let(:coordinates) { [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]]] } let(:geo_json) do { "type" => 'Polygon', diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 9a684dcb7..ca87290b1 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1516,8 +1516,8 @@ describe Dossier do { type: 'Feature', geometry: { - 'coordinates' => [[[2.428439855575562, 46.538476837725796], [2.4284291267395024, 46.53842148758162], [2.4282521009445195, 46.53841410755813], [2.42824137210846, 46.53847314771794], [2.428284287452698, 46.53847314771794], [2.428364753723145, 46.538487907747864], [2.4284291267395024, 46.538491597754714], [2.428439855575562, 46.538476837725796]]], - 'type' => 'Polygon' + coordinates: [[[2.428439855575562, 46.538476837725796], [2.4284291267395024, 46.53842148758162], [2.4282521009445195, 46.53841410755813], [2.42824137210846, 46.53847314771794], [2.428284287452698, 46.53847314771794], [2.428364753723145, 46.538487907747864], [2.4284291267395024, 46.538491597754714], [2.428439855575562, 46.538476837725796]]], + type: 'Polygon' }, properties: { area: 103.6, diff --git a/spec/models/geo_area_spec.rb b/spec/models/geo_area_spec.rb index 0a746f363..0cd51a7c6 100644 --- a/spec/models/geo_area_spec.rb +++ b/spec/models/geo_area_spec.rb @@ -23,7 +23,7 @@ RSpec.describe GeoArea, type: :model do it { expect(geo_area.location).to eq("46°32'19\"N 2°25'42\"E") } end - describe '#rgeo_geometry' do + describe '#geometry' do let(:geo_area) { build(:geo_area, :polygon, champ: nil) } let(:polygon) do { @@ -47,9 +47,9 @@ RSpec.describe GeoArea, type: :model do context 'polygon_with_extra_coordinate' do let(:geo_area) { build(:geo_area, :polygon_with_extra_coordinate, champ: nil) } + before { geo_area.valid? } - it { expect(geo_area.geometry).not_to eq(polygon) } - it { expect(geo_area.safe_geometry).to eq(polygon) } + it { expect(geo_area.geometry).to eq(polygon) } end end From f6b528e4974caa47aee2434c0856d62ece02aaee Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 15 Feb 2023 11:23:58 +0100 Subject: [PATCH 042/202] chore(geo_area): normalize all geo_areas --- app/jobs/migrations/normalize_geo_area_job.rb | 11 +++++++++++ .../20230215100231_normalize_geometries.rake | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 app/jobs/migrations/normalize_geo_area_job.rb create mode 100644 lib/tasks/deployment/20230215100231_normalize_geometries.rake diff --git a/app/jobs/migrations/normalize_geo_area_job.rb b/app/jobs/migrations/normalize_geo_area_job.rb new file mode 100644 index 000000000..e5f8e7d1d --- /dev/null +++ b/app/jobs/migrations/normalize_geo_area_job.rb @@ -0,0 +1,11 @@ +class Migrations::NormalizeGeoAreaJob < ApplicationJob + def perform(ids) + GeoArea.where(id: ids).find_each do |geo_area| + geojson = RGeo::GeoJSON.decode(geo_area.geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) + geometry = RGeo::GeoJSON.encode(geojson) + geo_area.update_column(:geometry, geometry) + rescue RGeo::Error::InvalidGeometry + geo_area.destroy + end + end +end diff --git a/lib/tasks/deployment/20230215100231_normalize_geometries.rake b/lib/tasks/deployment/20230215100231_normalize_geometries.rake new file mode 100644 index 000000000..275e37169 --- /dev/null +++ b/lib/tasks/deployment/20230215100231_normalize_geometries.rake @@ -0,0 +1,19 @@ +namespace :after_party do + desc 'Deployment task: normalize_geometries' + task normalize_geometries: :environment do + puts "Running deploy task 'normalize_geometries'" + + progress = ProgressReport.new(GeoArea.count) + GeoArea.in_batches(of: 100) do |geo_areas| + ids = geo_areas.ids + Migrations::NormalizeGeoAreaJob.perform_later(ids) + progress.inc(ids.size) + end + progress.finish + + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end From 0f483668cf8cd6cda2e8dcf8c2fec658c44de769 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 6 Feb 2023 09:33:27 +0100 Subject: [PATCH 043/202] let epci champ be prefillable --- app/models/type_de_champ.rb | 3 ++- spec/models/prefill_params_spec.rb | 4 +++- spec/models/type_de_champ_spec.rb | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 05091ec70..0d9692fdc 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -268,7 +268,8 @@ class TypeDeChamp < ApplicationRecord TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:drop_down_list), TypeDeChamp.type_champs.fetch(:regions), - TypeDeChamp.type_champs.fetch(:departements) + TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:epci) ]) end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 2c9ff077a..057e70d7d 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -127,6 +127,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is authorized", :drop_down_list, "value" it_behaves_like "a champ public value that is authorized", :regions, "03" it_behaves_like "a champ public value that is authorized", :departements, "03" + it_behaves_like "a champ public value that is authorized", :epci, ['01', '200042935'] it_behaves_like "a champ private value that is authorized", :text, "value" it_behaves_like "a champ private value that is authorized", :textarea, "value" @@ -145,7 +146,8 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :checkbox, "false" it_behaves_like "a champ private value that is authorized", :drop_down_list, "value" it_behaves_like "a champ private value that is authorized", :regions, "93" - it_behaves_like "a champ public value that is authorized", :departements, "03" + it_behaves_like "a champ private value that is authorized", :departements, "03" + it_behaves_like "a champ private value that is authorized", :epci, ['01', '200042935'] it_behaves_like "a champ public value that is unauthorized", :decimal_number, "non decimal string" it_behaves_like "a champ public value that is unauthorized", :integer_number, "non integer string" diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index 9d891f5e1..cbd34f501 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -251,6 +251,7 @@ describe TypeDeChamp do it_behaves_like "a prefillable type de champ", :type_de_champ_drop_down_list it_behaves_like "a prefillable type de champ", :type_de_champ_regions it_behaves_like "a prefillable type de champ", :type_de_champ_departements + it_behaves_like "a prefillable type de champ", :type_de_champ_epci it_behaves_like "a non-prefillable type de champ", :type_de_champ_number it_behaves_like "a non-prefillable type de champ", :type_de_champ_communes From f836f57e987c5e83cbd7234e942768eb7beafa2a Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 6 Feb 2023 11:22:26 +0100 Subject: [PATCH 044/202] transform the input value to assignable attributes Use the prefill decorator to transform the input value (for example ['01', '200042935']) into assignables attributes (for example { code_departement: '01', value: '200042935' }), in order to let the champ be prefilled successfully. --- app/models/prefill_params.rb | 13 +++-- .../prefill_epci_type_de_champ.rb | 23 +++++++++ .../types_de_champ/prefill_type_de_champ.rb | 6 +++ spec/models/prefill_params_spec.rb | 18 ++++--- .../prefill_epci_type_de_champ_spec.rb | 47 +++++++++++++++++++ .../prefill_type_de_champ_spec.rb | 14 ++++++ 6 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 app/models/types_de_champ/prefill_epci_type_de_champ.rb create mode 100644 spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 33261a8ac..37beddd11 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -56,10 +56,7 @@ class PrefillParams end def to_h - { - id: champ.id, - value: value - } + { id: champ.id }.merge(champ_attributes) end private @@ -67,8 +64,14 @@ class PrefillParams def valid? return true unless NEED_VALIDATION_TYPES_DE_CHAMPS.include?(champ.type_champ) - champ.value = value + champ.assign_attributes(champ_attributes) champ.valid?(:prefill) end + + def champ_attributes + TypesDeChamp::PrefillTypeDeChamp + .build(champ.type_de_champ) + .transform_value_to_assignable_attributes(value) + end end end diff --git a/app/models/types_de_champ/prefill_epci_type_de_champ.rb b/app/models/types_de_champ/prefill_epci_type_de_champ.rb new file mode 100644 index 000000000..9213aff3f --- /dev/null +++ b/app/models/types_de_champ/prefill_epci_type_de_champ.rb @@ -0,0 +1,23 @@ +class TypesDeChamp::PrefillEpciTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + def possible_values + departements.map { |departement| "#{departement[:code]} (#{departement[:name]})" } + end + + def example_value + departement_code = departements.pick(:code) + epci_code = APIGeoService.epcis(departement_code).pick(:code) + [departement_code, epci_code] + end + + def transform_value_to_assignable_attributes(value) + return { code_departement: nil, value: nil } if value.blank? || !value.is_a?(Array) + return { code_departement: value.first, value: nil } if value.one? + { code_departement: value.first, value: value.second } + end + + private + + def departements + @departements ||= APIGeoService.departements.sort_by { |departement| departement[:code] } + end +end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 5e9e55380..a385ea653 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -11,6 +11,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator TypesDeChamp::PrefillRegionTypeDeChamp.new(type_de_champ) when TypeDeChamp.type_champs.fetch(:departements) TypesDeChamp::PrefillDepartementTypeDeChamp.new(type_de_champ) + when TypeDeChamp.type_champs.fetch(:epci) + TypesDeChamp::PrefillEpciTypeDeChamp.new(type_de_champ) else new(type_de_champ) end @@ -33,4 +35,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator def too_many_possible_values? possible_values.count > POSSIBLE_VALUES_THRESHOLD end + + def transform_value_to_assignable_attributes(value) + { value: value } + end end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 057e70d7d..1354dcaa6 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -72,12 +72,12 @@ RSpec.describe PrefillParams do context "when the type de champ is authorized (#{type_de_champ_type})" do let(:types_de_champ_public) { [{ type: type_de_champ_type }] } let(:type_de_champ) { procedure.published_revision.types_de_champ_public.first } - let(:champ_id) { find_champ_by_stable_id(dossier, type_de_champ.stable_id).id } + let(:champ) { find_champ_by_stable_id(dossier, type_de_champ.stable_id) } let(:params) { { "champ_#{type_de_champ.to_typed_id}" => value } } - it "builds an array of hash(id, value) matching the given params" do - expect(prefill_params_array).to match([{ id: champ_id, value: value }]) + it "builds an array of hash matching the given params" do + expect(prefill_params_array).to match([{ id: champ.id }.merge(attributes(champ, value))]) end end end @@ -86,12 +86,12 @@ RSpec.describe PrefillParams do context "when the type de champ is authorized (#{type_de_champ_type})" do let(:types_de_champ_private) { [{ type: type_de_champ_type }] } let(:type_de_champ) { procedure.published_revision.types_de_champ_private.first } - let(:champ_id) { find_champ_by_stable_id(dossier, type_de_champ.stable_id).id } + let(:champ) { find_champ_by_stable_id(dossier, type_de_champ.stable_id) } let(:params) { { "champ_#{type_de_champ.to_typed_id}" => value } } - it "builds an array of hash(id, value) matching the given params" do - expect(prefill_params_array).to match([{ id: champ_id, value: value }]) + it "builds an array of hash matching the given params" do + expect(prefill_params_array).to match([{ id: champ.id }.merge(attributes(champ, value))]) end end end @@ -184,4 +184,10 @@ RSpec.describe PrefillParams do def find_champ_by_stable_id(dossier, stable_id) dossier.champs.joins(:type_de_champ).find_by(types_de_champ: { stable_id: stable_id }) end + + def attributes(champ, value) + TypesDeChamp::PrefillTypeDeChamp + .build(champ.type_de_champ) + .transform_value_to_assignable_attributes(value) + end end diff --git a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb new file mode 100644 index 000000000..eecdfdc15 --- /dev/null +++ b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do + let(:type_de_champ) { build(:type_de_champ_epci) } + + describe 'ancestors' do + subject { described_class.new(type_de_champ) } + + it { is_expected.to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } + end + # TODO: SEB describe '#possible_values' + # TODO: SEB describe '#example_value' + + describe '#transform_value_to_assignable_attributes' do + subject(:transform_value_to_assignable_attributes) { described_class.build(type_de_champ).transform_value_to_assignable_attributes(value) } + + context 'when the value is nil' do + let(:value) { nil } + it { is_expected.to match({ code_departement: nil, value: nil }) } + end + + context 'when the value is empty' do + let(:value) { '' } + it { is_expected.to match({ code_departement: nil, value: nil }) } + end + + context 'when the value is a string' do + let(:value) { 'hello' } + it { is_expected.to match({ code_departement: nil, value: nil }) } + end + + context 'when the value is an array of one element' do + let(:value) { ['01'] } + it { is_expected.to match({ code_departement: '01', value: nil }) } + end + + context 'when the value is an array of two elements' do + let(:value) { ['01', '200042935'] } + it { is_expected.to match({ code_departement: '01', value: '200042935' }) } + end + + context 'when the value is an array of three or more elements' do + let(:value) { ['01', '200042935', 'hello'] } + it { is_expected.to match({ code_departement: '01', value: '200042935' }) } + end + end +end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 52508be91..7477b3789 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -28,6 +28,12 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(built).to be_kind_of(TypesDeChamp::PrefillDepartementTypeDeChamp) } end + context 'when the type de champ is a epci' do + let(:type_de_champ) { build(:type_de_champ_epci) } + + it { expect(built).to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } + end + context 'when any other type de champ' do let(:type_de_champ) { build(:type_de_champ_date) } @@ -92,4 +98,12 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(too_many_possible_values).to eq(false) } end end + + describe '#transform_value_to_assignable_attributes' do + let(:type_de_champ) { build(:type_de_champ_email) } + let(:value) { "any@email.org" } + subject(:transform_value_to_assignable_attributes) { described_class.build(type_de_champ).transform_value_to_assignable_attributes(value) } + + it { is_expected.to match({ value: value }) } + end end From 5ac80f968af275ca2fb68f7e05e46e45f9015c94 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 6 Feb 2023 12:12:23 +0100 Subject: [PATCH 045/202] add possible and example values --- .../prefill_epci_type_de_champ.rb | 4 +- config/locales/en.yml | 5 +- config/locales/fr.yml | 5 +- .../prefill_epci_type_de_champ_spec.rb | 51 ++++++++++++++++++- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/app/models/types_de_champ/prefill_epci_type_de_champ.rb b/app/models/types_de_champ/prefill_epci_type_de_champ.rb index 9213aff3f..400e5da3f 100644 --- a/app/models/types_de_champ/prefill_epci_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_epci_type_de_champ.rb @@ -1,6 +1,8 @@ class TypesDeChamp::PrefillEpciTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp def possible_values - departements.map { |departement| "#{departement[:code]} (#{departement[:name]})" } + departements.map do |departement| + "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/epcis?codeDepartement=#{departement[:code]}" + end end def example_value diff --git a/config/locales/en.yml b/config/locales/en.yml index d1a6b9638..d4752fe73 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -128,12 +128,13 @@ en: iban_html: An Iban number yes_no_html: '"true" for Yes, "false" pour No' checkbox_html: '"true" to check, "false" to uncheck' - pays_html: An ISO 3166-2 country code + pays_html: An ISO 3166-2 country code departements_html: A department number - regions_html: An INSEE region code + regions_html: An INSEE region code date_html: ISO8601 date datetime_html: ISO8601 datetime drop_down_list_other_html: Any value + epci_html: An array of the department code and the EPCI one. examples: title: Example text: Short text diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 13aa78315..19e43735c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -119,12 +119,13 @@ fr: iban_html: Un numéro Iban yes_no_html: '"true" pour Oui, "false" pour Non' checkbox_html: '"true" pour coché, "false" pour décoché' - pays_html: Un code pays ISO 3166-2 + pays_html: Un code pays ISO 3166-2 departements_html: Un numéro de département - regions_html: Un code INSEE de région + regions_html: Un code INSEE de région datetime_html: Datetime au format ISO8601 date_html: Date au format ISO8601 drop_down_list_other_html: Toute valeur + epci_html: Un tableau contenant le code de département et celui de l'EPCI. examples: title: Exemple text: Texte court diff --git a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb index eecdfdc15..7f2a1eb31 100644 --- a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb @@ -2,14 +2,55 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do let(:type_de_champ) { build(:type_de_champ_epci) } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end describe 'ancestors' do subject { described_class.new(type_de_champ) } it { is_expected.to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } end - # TODO: SEB describe '#possible_values' - # TODO: SEB describe '#example_value' + + describe '#possible_values' do + let(:expected_values) do + departements.map { |departement| "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/epcis?codeDepartement=#{departement[:code]}" } + end + subject(:possible_values) { described_class.new(type_de_champ).possible_values } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + it { expect(possible_values).to match(expected_values) } + end + + describe '#example_value' do + let(:departement_code) { departements.pick(:code) } + let(:epci_code) { APIGeoService.epcis(departement_code).pick(:code) } + subject(:example_value) { described_class.new(type_de_champ).example_value } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + it { is_expected.to eq([departement_code, epci_code]) } + end describe '#transform_value_to_assignable_attributes' do subject(:transform_value_to_assignable_attributes) { described_class.build(type_de_champ).transform_value_to_assignable_attributes(value) } @@ -44,4 +85,10 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do it { is_expected.to match({ code_departement: '01', value: '200042935' }) } end end + + private + + def departements + APIGeoService.departements.sort_by { |departement| departement[:code] } + end end From edf90a77f3a849ee93ec31361827c91fc0219942 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 6 Feb 2023 14:24:51 +0100 Subject: [PATCH 046/202] validate epci to reject incorrect prefill values --- app/models/champs/epci_champ.rb | 22 ++++ app/models/prefill_params.rb | 3 +- config/locales/en.yml | 8 ++ config/locales/fr.yml | 8 ++ spec/models/champs/epci_champ_spec.rb | 149 +++++++++++++++++++++++++- spec/models/prefill_params_spec.rb | 10 ++ 6 files changed, 198 insertions(+), 2 deletions(-) diff --git a/app/models/champs/epci_champ.rb b/app/models/champs/epci_champ.rb index ce48da5a5..45803bd8b 100644 --- a/app/models/champs/epci_champ.rb +++ b/app/models/champs/epci_champ.rb @@ -24,6 +24,10 @@ class Champs::EpciChamp < Champs::TextChamp store_accessor :value_json, :code_departement before_validation :on_departement_change + validate :code_departement_in_departement_codes, unless: -> { code_departement.nil? } + validate :external_id_in_departement_epci_codes, unless: -> { code_departement.nil? || external_id.nil? } + validate :value_in_departement_epci_names, unless: -> { code_departement.nil? || external_id.nil? || value.nil? } + def for_export [value, code, "#{code_departement} – #{departement_name}"] end @@ -74,4 +78,22 @@ class Champs::EpciChamp < Champs::TextChamp self.value = nil end end + + def code_departement_in_departement_codes + return if code_departement.in?(APIGeoService.departements.pluck(:code)) + + errors.add(:code_departement, :not_in_departement_codes) + end + + def external_id_in_departement_epci_codes + return if external_id.in?(APIGeoService.epcis(code_departement).pluck(:code)) + + errors.add(:external_id, :not_in_departement_epci_codes) + end + + def value_in_departement_epci_names + return if value.in?(APIGeoService.epcis(code_departement).pluck(:name)) + + errors.add(:value, :not_in_departement_epci_names) + end end diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 37beddd11..f0841039a 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -41,7 +41,8 @@ class PrefillParams TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:pays), TypeDeChamp.type_champs.fetch(:regions), - TypeDeChamp.type_champs.fetch(:departements) + TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:epci) ] attr_reader :champ, :value diff --git a/config/locales/en.yml b/config/locales/en.yml index d4752fe73..fad29d059 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -483,6 +483,14 @@ en: not_in_departement_names: "must be a valid department name" external_id: not_in_departement_codes: "must be a valid department code" + "champs/epci_champ": + attributes: + code_departement: + not_in_departement_codes: "must be a valid department code" + external_id: + not_in_departement_epci_codes: "must be a valid EPCI code of the matching department" + value: + not_in_departement_epci_names: "must be a valid EPCI name of the matching department" errors: format: "Field « %{attribute} » %{message}" messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 19e43735c..b1492cc79 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -478,6 +478,14 @@ fr: not_in_departement_names: "doit être un nom de département valide" external_id: not_in_departement_codes: "doit être un code de département valide" + "champs/epci_champ": + attributes: + code_departement: + not_in_departement_codes: "doit être un code de département valide" + external_id: + not_in_departement_epci_codes: "doit être un code d'EPCI du département correspondant" + value: + not_in_departement_epci_names: "doit être un nom d'EPCI du département correspondant" errors: format: "Le champ « %{attribute} » %{message}" messages: diff --git a/spec/models/champs/epci_champ_spec.rb b/spec/models/champs/epci_champ_spec.rb index be06ed379..1b5a56dd4 100644 --- a/spec/models/champs/epci_champ_spec.rb +++ b/spec/models/champs/epci_champ_spec.rb @@ -6,9 +6,156 @@ describe Champs::EpciChamp, type: :model do Rails.cache.clear end - let(:champ) { described_class.new } + describe 'validations' do + describe 'code_departement', vcr: { cassette_name: 'api_geo_departements' } do + subject { build(:champ_epci, code_departement: code_departement) } + + context 'when nil' do + let(:code_departement) { nil } + + it { is_expected.to be_valid } + end + + context 'when empty' do + let(:code_departement) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when included in the departement codes' do + let(:code_departement) { "01" } + + it { is_expected.to be_valid } + end + + context 'when not included in the departement codes' do + let(:code_departement) { "totoro" } + + it { is_expected.not_to be_valid } + end + end + + describe 'external_id' do + let(:champ) { build(:champ_epci, code_departement: code_departement, external_id: nil) } + + subject { champ } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + champ.save! + champ.update_columns(external_id: external_id) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + context 'when code_departement is nil' do + let(:code_departement) { nil } + let(:external_id) { nil } + + it { is_expected.to be_valid } + end + + context 'when code_departement is not nil and valid' do + let(:code_departement) { "01" } + + context 'when external_id is nil' do + let(:external_id) { nil } + + it { is_expected.to be_valid } + end + + context 'when external_id is empty' do + let(:external_id) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when external_id is included in the epci codes of the departement' do + let(:external_id) { '200042935' } + + it { is_expected.to be_valid } + end + + context 'when external_id is not included in the epci codes of the departement' do + let(:external_id) { 'totoro' } + + it { is_expected.not_to be_valid } + end + end + end + + describe 'value' do + let(:champ) { build(:champ_epci, code_departement: code_departement, external_id: nil, value: nil) } + + subject { champ } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + champ.save! + champ.update_columns(external_id: external_id, value: value) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + context 'when code_departement is nil' do + let(:code_departement) { nil } + let(:external_id) { nil } + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when external_id is nil' do + let(:code_departement) { '01' } + let(:external_id) { nil } + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when code_departement and external_id are not nil and valid' do + let(:code_departement) { '01' } + let(:external_id) { '200042935' } + + context 'when value is nil' do + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when value is empty' do + let(:value) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when value is in departement epci names' do + let(:value) { 'CA Haut - Bugey Agglomération' } + + it { is_expected.to be_valid } + end + + context 'when value is not in departement epci names' do + let(:value) { 'totoro' } + + it { is_expected.not_to be_valid } + end + end + end + end describe 'value', vcr: { cassette_name: 'api_geo_epcis' } do + let(:champ) { described_class.new } it 'with departement and code' do champ.code_departement = '01' champ.value = '200042935' diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 1354dcaa6..b7e03b27b 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -12,6 +12,16 @@ RSpec.describe PrefillParams do before do allow(Rails).to receive(:cache).and_return(memory_store) Rails.cache.clear + + VCR.insert_cassette('api_geo_regions') + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_regions') + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') end context "when the stable ids match the TypeDeChamp of the corresponding procedure" do From 07b5a3b4b5e4ca2d2331c32db740f6f81fac6386 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 24 Jan 2023 10:05:58 +0100 Subject: [PATCH 047/202] manage types de champ with multiple example values --- app/models/prefill_description.rb | 34 +++++++++++++++++++------ spec/models/prefill_description_spec.rb | 28 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/app/models/prefill_description.rb b/app/models/prefill_description.rb index 2148b3dce..090e02fb8 100644 --- a/app/models/prefill_description.rb +++ b/app/models/prefill_description.rb @@ -45,15 +45,33 @@ class PrefillDescription < SimpleDelegator private - def prefilled_champs_for_link - prefilled_champs.map { |type_de_champ| ["champ_#{type_de_champ.to_typed_id}", type_de_champ.example_value] }.to_h - end - - def prefilled_champs_for_query - prefilled_champs.map { |type_de_champ| "\"champ_#{type_de_champ.to_typed_id}\": \"#{type_de_champ.example_value}\"" } .join(', ') - end - def active_fillable_public_types_de_champ active_revision.types_de_champ_public.fillable end + + def prefilled_champs_for_link + prefilled_champs_as_params.map(&:to_a).to_h + end + + def prefilled_champs_for_query + prefilled_champs_as_params.map(&:to_s).join(', ') + end + + def prefilled_champs_as_params + prefilled_champs.map { |type_de_champ| Param.new(type_de_champ.to_typed_id, type_de_champ.example_value) } + end + + Param = Struct.new(:key, :value) do + def to_a + ["champ_#{key}", value] + end + + def to_s + if value.is_a?(Array) + "\"champ_#{key}\": #{value}" + else + "\"champ_#{key}\": \"#{value}\"" + end + end + end end diff --git a/spec/models/prefill_description_spec.rb b/spec/models/prefill_description_spec.rb index 65c8cd1a5..5c5846965 100644 --- a/spec/models/prefill_description_spec.rb +++ b/spec/models/prefill_description_spec.rb @@ -101,6 +101,19 @@ RSpec.describe PrefillDescription, type: :model do ) ) end + + context 'when the type de champ can have multiple values' do + let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, procedure: procedure) } + + it 'builds the URL with array parameter' do + expect(prefill_description.prefill_link).to eq( + commencer_url( + path: procedure.path, + "champ_#{type_de_champ.to_typed_id}": TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ).example_value + ) + ) + end + end end describe '#prefill_query' do @@ -120,5 +133,20 @@ RSpec.describe PrefillDescription, type: :model do it "builds the query to create a new prefilled dossier" do expect(prefill_description.prefill_query).to eq(expected_query) end + + context 'when the type de champ can have multiple values' do + let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, procedure: procedure) } + let(:expected_query) do + <<~TEXT + curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ + --header 'Content-Type: application/json' \\ + --data '{"champ_#{type_de_champ.to_typed_id}": #{TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ).example_value}}' + TEXT + end + + it 'builds the query with array parameter' do + expect(prefill_description.prefill_query).to eq(expected_query) + end + end end end From 8aa31522b610de3f631faf052ee85f316335c222 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 6 Feb 2023 15:12:34 +0100 Subject: [PATCH 048/202] cover feature with specs --- spec/models/prefill_description_spec.rb | 40 ++++++++++++++++--- .../shared_examples_for_prefilled_dossier.rb | 1 + spec/system/users/dossier_prefill_get_spec.rb | 23 ++++++++++- .../system/users/dossier_prefill_post_spec.rb | 20 +++++++++- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/spec/models/prefill_description_spec.rb b/spec/models/prefill_description_spec.rb index 5c5846965..6a1dc13be 100644 --- a/spec/models/prefill_description_spec.rb +++ b/spec/models/prefill_description_spec.rb @@ -103,13 +103,28 @@ RSpec.describe PrefillDescription, type: :model do end context 'when the type de champ can have multiple values' do - let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, procedure: procedure) } + let(:type_de_champ) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } + + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end it 'builds the URL with array parameter' do expect(prefill_description.prefill_link).to eq( commencer_url( path: procedure.path, - "champ_#{type_de_champ.to_typed_id}": TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ).example_value + "champ_#{type_de_champ.to_typed_id}": type_de_champ.example_value ) ) end @@ -128,19 +143,34 @@ RSpec.describe PrefillDescription, type: :model do TEXT end - before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id]) } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id]) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end it "builds the query to create a new prefilled dossier" do expect(prefill_description.prefill_query).to eq(expected_query) end context 'when the type de champ can have multiple values' do - let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, procedure: procedure) } + let(:type_de_champ) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } let(:expected_query) do <<~TEXT curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ --header 'Content-Type: application/json' \\ - --data '{"champ_#{type_de_champ.to_typed_id}": #{TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ).example_value}}' + --data '{"champ_#{type_de_champ.to_typed_id}": #{type_de_champ.example_value}}' TEXT end diff --git a/spec/support/shared_examples_for_prefilled_dossier.rb b/spec/support/shared_examples_for_prefilled_dossier.rb index 20dd7dbe0..5ed67c49c 100644 --- a/spec/support/shared_examples_for_prefilled_dossier.rb +++ b/spec/support/shared_examples_for_prefilled_dossier.rb @@ -19,5 +19,6 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do expect(page).to have_field(type_de_champ_phone.libelle, with: phone_value) expect(page).to have_css('label', text: type_de_champ_phone.libelle) expect(page).to have_field(type_de_champ_datetime.libelle, with: datetime_value) + expect(page).to have_field(type_de_champ_epci.libelle, with: epci_value.last) end end diff --git a/spec/system/users/dossier_prefill_get_spec.rb b/spec/system/users/dossier_prefill_get_spec.rb index 62aca4e4a..41fc21403 100644 --- a/spec/system/users/dossier_prefill_get_spec.rb +++ b/spec/system/users/dossier_prefill_get_spec.rb @@ -1,4 +1,6 @@ describe 'Prefilling a dossier (with a GET request):' do + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + let(:password) { 'my-s3cure-p4ssword' } let(:procedure) { create(:procedure, :published, opendata: true) } @@ -7,9 +9,24 @@ describe 'Prefilling a dossier (with a GET request):' do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_phone) { create(:type_de_champ_phone, procedure: procedure) } let(:type_de_champ_datetime) { create(:type_de_champ_datetime, procedure: procedure) } + let(:type_de_champ_epci) { create(:type_de_champ_epci, procedure: procedure) } let(:text_value) { "My Neighbor Totoro is the best movie ever" } let(:phone_value) { "invalid phone value" } let(:datetime_value) { "2023-02-01T10:32" } + let(:epci_value) { ['01', '200029999'] } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end context 'when authenticated' do it_behaves_like "the user has got a prefilled dossier, owned by themselves" do @@ -23,7 +40,8 @@ describe 'Prefilling a dossier (with a GET request):' do path: procedure.path, "champ_#{type_de_champ_text.to_typed_id}" => text_value, "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value ) click_on "Poursuivre mon dossier prérempli" @@ -37,7 +55,8 @@ describe 'Prefilling a dossier (with a GET request):' do path: procedure.path, "champ_#{type_de_champ_text.to_typed_id}" => text_value, "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value ) end diff --git a/spec/system/users/dossier_prefill_post_spec.rb b/spec/system/users/dossier_prefill_post_spec.rb index 6e484795f..c0eaa03fe 100644 --- a/spec/system/users/dossier_prefill_post_spec.rb +++ b/spec/system/users/dossier_prefill_post_spec.rb @@ -1,4 +1,6 @@ describe 'Prefilling a dossier (with a POST request):' do + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + let(:password) { 'my-s3cure-p4ssword' } let(:procedure) { create(:procedure, :published) } @@ -7,9 +9,24 @@ describe 'Prefilling a dossier (with a POST request):' do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_phone) { create(:type_de_champ_phone, procedure: procedure) } let(:type_de_champ_datetime) { create(:type_de_champ_datetime, procedure: procedure) } + let(:type_de_champ_epci) { create(:type_de_champ_epci, procedure: procedure) } let(:text_value) { "My Neighbor Totoro is the best movie ever" } let(:phone_value) { "invalid phone value" } let(:datetime_value) { "2023-02-01T10:32" } + let(:epci_value) { ['01', '200029999'] } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end scenario "the user get the URL of a prefilled orphan brouillon dossier" do dossier_url = create_and_prefill_dossier_with_post_request @@ -98,7 +115,8 @@ describe 'Prefilling a dossier (with a POST request):' do params: { "champ_#{type_de_champ_text.to_typed_id}" => text_value, "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value }.to_json JSON.parse(session.response.body)["dossier_url"].gsub("http://www.example.com", "") end From 5a89a2dbe4e8e4744ca394caf4927c0e49097464 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 15 Feb 2023 12:15:34 +0100 Subject: [PATCH 049/202] Use Stable ID for repetitions prefill --- app/models/champ.rb | 2 ++ .../prefill_departement_type_de_champ.rb | 2 +- .../prefill_repetition_type_de_champ.rb | 10 +++++----- .../prefill_repetition_type_de_champ_spec.rb | 18 ++++++++++-------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/models/champ.rb b/app/models/champ.rb index 06a7d365f..6a8b409b7 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -72,6 +72,8 @@ class Champ < ApplicationRecord :refresh_after_update?, to: :type_de_champ + delegate :to_typed_id, to: :type_de_champ, prefix: true + delegate :revision, to: :dossier, prefix: true scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) } diff --git a/app/models/types_de_champ/prefill_departement_type_de_champ.rb b/app/models/types_de_champ/prefill_departement_type_de_champ.rb index 2ec1d1d11..4c92cad1d 100644 --- a/app/models/types_de_champ/prefill_departement_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_departement_type_de_champ.rb @@ -1,5 +1,5 @@ class TypesDeChamp::PrefillDepartementTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp - def possible_values + def all_possible_values departements.map { |departement| "#{departement[:code]} (#{departement[:name]})" } end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 6cf1f5153..35a7dd883 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -20,21 +20,21 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh value.map.with_index do |repetition, index| PrefillRepetitionRow.new(champ, repetition, index).to_assignable_attributes - end.compact + end.reject(&:blank?) end private def subchamps_all_possible_values "
        " + prefillable_subchamps.map do |prefill_type_de_champ| - "
      • #{prefill_type_de_champ.libelle}: #{prefill_type_de_champ.possible_values}
      • " + "
      • #{prefill_type_de_champ.to_typed_id}: #{prefill_type_de_champ.possible_values}
      • " end.join + "
      " end def row_values_format @row_example_value ||= prefillable_subchamps.map do |prefill_type_de_champ| - [prefill_type_de_champ.libelle, prefill_type_de_champ.example_value.to_s] + [prefill_type_de_champ.to_typed_id, prefill_type_de_champ.example_value.to_s] end.to_h end @@ -56,8 +56,8 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh row = @champ.rows[@index] || @champ.add_row(@champ.dossier_revision) JSON.parse(@repetition).map do |key, value| - subchamp = row.find { |champ| champ.libelle == key } - return unless subchamp + subchamp = row.find { |champ| champ.type_de_champ_to_typed_id == key } + next unless subchamp TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ).to_assignable_attributes(subchamp, value) rescue JSON::ParserError # On ignore les valeurs qu'on n'arrive pas à parser diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index 33197c381..c351a1321 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -5,6 +5,8 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { let(:type_de_champ) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } let(:champ) { create(:champ_repetition, type_de_champ: type_de_champ) } let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ).send(:prefillable_subchamps) } + let(:text_repetition) { prefillable_subchamps.first } + let(:integer_repetition) { prefillable_subchamps.second } let(:region_repetition) { prefillable_subchamps.third } let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } @@ -22,7 +24,7 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { describe '#possible_values' do subject(:possible_values) { described_class.new(type_de_champ).possible_values } let(:expected_value) { - "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
      " + "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
      " } it { @@ -32,14 +34,13 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { describe '#example_value' do subject(:example_value) { described_class.new(type_de_champ).example_value } - let(:expected_value) { ["{\"sub type de champ\":\"Texte court\", \"sub type de champ2\":\"42\", \"region sub_champ\":\"53\"}", "{\"sub type de champ\":\"Texte court\", \"sub type de champ2\":\"42\", \"region sub_champ\":\"53\"}"] } + let(:expected_value) { ["{\"#{text_repetition.to_typed_id}\":\"Texte court\", \"#{integer_repetition.to_typed_id}\":\"42\", \"#{region_repetition.to_typed_id}\":\"53\"}", "{\"#{text_repetition.to_typed_id}\":\"Texte court\", \"#{integer_repetition.to_typed_id}\":\"42\", \"#{region_repetition.to_typed_id}\":\"53\"}"] } it { expect(example_value).to eq(expected_value) } end describe '#to_assignable_attributes' do subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } - let(:type_de_champ_child) { champ.rows.first.first.type_de_champ } context 'when the value is nil' do let(:value) { nil } @@ -63,15 +64,16 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { end context 'when the value is an array with some wrong keys' do - let(:value) { ["{\"#{type_de_champ_child.libelle}\":\"value\"}", "{\"blabla\":\"value2\"}"] } + let(:value) { ["{\"#{text_repetition.to_typed_id}\":\"value\", \"blabla\":\"value2\"}", "{\"#{integer_repetition.to_typed_id}\":\"value3\"}", "{\"blabla\":\"false\"}"] } - it { is_expected.to match([[{ id: type_de_champ_child.champ.first.id, value: "value" }]]) } + it { is_expected.to match([[{ id: text_repetition.champ.first.id, value: "value" }], [{ id: integer_repetition.champ.second.id, value: "value3" }]]) } end - context 'when the value is an array with right keys' do - let(:value) { ["{\"#{type_de_champ_child.libelle}\":\"value\"}", "{\"#{type_de_champ_child.libelle}\":\"value2\"}"] } - it { is_expected.to match([[{ id: type_de_champ_child.champ.first.id, value: "value" }], [{ id: type_de_champ_child.champ.second.id, value: "value2" }]]) } + context 'when the value is an array with right keys' do + let(:value) { ["{\"#{text_repetition.to_typed_id}\":\"value\"}", "{\"#{text_repetition.to_typed_id}\":\"value2\"}"] } + + it { is_expected.to match([[{ id: text_repetition.champ.first.id, value: "value" }], [{ id: text_repetition.champ.second.id, value: "value2" }]]) } end end end From 1de936e1d6068a813c7f40ee0ac868b60743a3d0 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 9 Feb 2023 12:43:03 +0100 Subject: [PATCH 050/202] fix(procedures/all): table layout issue --- app/views/administrateurs/procedures/_detail.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/administrateurs/procedures/_detail.html.haml b/app/views/administrateurs/procedures/_detail.html.haml index f25a90fd1..a02396a9e 100644 --- a/app/views/administrateurs/procedures/_detail.html.haml +++ b/app/views/administrateurs/procedures/_detail.html.haml @@ -16,7 +16,7 @@ - if show_detail %tr.procedure{ id: "procedure_detail_#{procedure.id}" } - %td.fr-highlight--beige-gris-galet{ colspan: '6' } + %td.fr-highlight--beige-gris-galet{ colspan: '7' } .fr-container .fr-grid-row .fr-col-6 From 4b3d403d7e42c6ab2f249a73ff7d90fcbbdd4956 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 15 Feb 2023 17:14:29 +0100 Subject: [PATCH 051/202] fix spec --- .../prefill_departement_type_de_champ_spec.rb | 7 +++++-- .../prefill_repetition_type_de_champ_spec.rb | 1 - spec/system/users/dossier_prefill_get_spec.rb | 8 ++++---- spec/system/users/dossier_prefill_post_spec.rb | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb index f43a07393..a453a9e03 100644 --- a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do - let(:type_de_champ) { build(:type_de_champ_departements) } + let(:procedure) { create(:procedure) } + let(:type_de_champ) { build(:type_de_champ_departements, procedure: procedure) } let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } before do @@ -17,10 +18,12 @@ RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do describe '#possible_values', vcr: { cassette_name: 'api_geo_departements' } do let(:expected_values) { - APIGeoService.departements.sort_by { |departement| departement[:code] }.map { |departement| "#{departement[:code]} (#{departement[:name]})" } + "Un numéro de département
      Voir toutes les valeurs possibles" } subject(:possible_values) { described_class.new(type_de_champ).possible_values } + before { type_de_champ.reload } + it { expect(possible_values).to match(expected_values) } end end diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index c351a1321..df0a88402 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -69,7 +69,6 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { it { is_expected.to match([[{ id: text_repetition.champ.first.id, value: "value" }], [{ id: integer_repetition.champ.second.id, value: "value3" }]]) } end - context 'when the value is an array with right keys' do let(:value) { ["{\"#{text_repetition.to_typed_id}\":\"value\"}", "{\"#{text_repetition.to_typed_id}\":\"value2\"}"] } diff --git a/spec/system/users/dossier_prefill_get_spec.rb b/spec/system/users/dossier_prefill_get_spec.rb index 03a6b67a4..c876d09aa 100644 --- a/spec/system/users/dossier_prefill_get_spec.rb +++ b/spec/system/users/dossier_prefill_get_spec.rb @@ -31,8 +31,8 @@ describe 'Prefilling a dossier (with a GET request):' do "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, "champ_#{type_de_champ_repetition.to_typed_id}" => [ "{ - \"#{sub_type_de_champs_repetition.first.libelle}\": \"#{text_repetition_value}\", - \"#{sub_type_de_champs_repetition.second.libelle}\": \"#{integer_repetition_value}\" + \"#{sub_type_de_champs_repetition.first.to_typed_id}\": \"#{text_repetition_value}\", + \"#{sub_type_de_champs_repetition.second.to_typed_id}\": \"#{integer_repetition_value}\" }" ], "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value @@ -51,8 +51,8 @@ describe 'Prefilling a dossier (with a GET request):' do "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, "champ_#{type_de_champ_repetition.to_typed_id}" => [ "{ - \"#{sub_type_de_champs_repetition.first.libelle}\": \"#{text_repetition_value}\", - \"#{sub_type_de_champs_repetition.second.libelle}\": \"#{integer_repetition_value}\" + \"#{sub_type_de_champs_repetition.first.to_typed_id}\": \"#{text_repetition_value}\", + \"#{sub_type_de_champs_repetition.second.to_typed_id}\": \"#{integer_repetition_value}\" }" ], "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value diff --git a/spec/system/users/dossier_prefill_post_spec.rb b/spec/system/users/dossier_prefill_post_spec.rb index 639da167f..d67c93a48 100644 --- a/spec/system/users/dossier_prefill_post_spec.rb +++ b/spec/system/users/dossier_prefill_post_spec.rb @@ -106,8 +106,8 @@ describe 'Prefilling a dossier (with a POST request):' do "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, "champ_#{type_de_champ_repetition.to_typed_id}" => [ "{ - \"#{sub_type_de_champs_repetition.first.libelle}\": \"#{text_repetition_value}\", - \"#{sub_type_de_champs_repetition.second.libelle}\": \"#{integer_repetition_value}\" + \"#{sub_type_de_champs_repetition.first.to_typed_id}\": \"#{text_repetition_value}\", + \"#{sub_type_de_champs_repetition.second.to_typed_id}\": \"#{integer_repetition_value}\" }" ], "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value From 4876d583b673aa65fb214069e1e02ba0ef9fe3f2 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 15 Feb 2023 17:39:19 +0100 Subject: [PATCH 052/202] Merge branch 'main' into feature/prefill_repetible --- app/graphql/types/geo_area_type.rb | 2 +- app/jobs/migrations/normalize_geo_area_job.rb | 11 ++ app/models/champs/carte_champ.rb | 13 +- app/models/champs/epci_champ.rb | 22 +++ app/models/dossier.rb | 9 +- app/models/geo_area.rb | 32 ++-- app/models/prefill_description.rb | 36 +++-- app/models/prefill_params.rb | 15 +- app/models/type_de_champ.rb | 3 +- .../prefill_epci_type_de_champ.rb | 25 +++ .../prefill_repetition_type_de_champ.rb | 2 - .../types_de_champ/prefill_type_de_champ.rb | 6 +- app/serializers/champ_serializer.rb | 2 +- app/serializers/geo_area_serializer.rb | 2 +- app/services/geojson_service.rb | 60 +++++++ .../procedures/_detail.html.haml | 2 +- .../administrateurs/procedures/all.html.haml | 2 +- config/locales/en.yml | 13 +- config/locales/fr.yml | 13 +- .../20230215100231_normalize_geometries.rake | 19 +++ spec/models/champs/carte_champ_spec.rb | 2 +- spec/models/champs/epci_champ_spec.rb | 149 +++++++++++++++++- spec/models/dossier_spec.rb | 4 +- spec/models/geo_area_spec.rb | 6 +- spec/models/prefill_description_spec.rb | 61 +++++++ spec/models/prefill_params_spec.rb | 18 ++- spec/models/type_de_champ_spec.rb | 1 + .../prefill_epci_type_de_champ_spec.rb | 94 +++++++++++ .../prefill_type_de_champ_spec.rb | 6 + .../shared_examples_for_prefilled_dossier.rb | 1 + .../administrateurs/procedure_cloning_spec.rb | 15 ++ spec/system/users/dossier_prefill_get_spec.rb | 23 ++- .../system/users/dossier_prefill_post_spec.rb | 20 ++- 33 files changed, 613 insertions(+), 76 deletions(-) create mode 100644 app/jobs/migrations/normalize_geo_area_job.rb create mode 100644 app/models/types_de_champ/prefill_epci_type_de_champ.rb create mode 100644 lib/tasks/deployment/20230215100231_normalize_geometries.rake create mode 100644 spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb diff --git a/app/graphql/types/geo_area_type.rb b/app/graphql/types/geo_area_type.rb index 5827b3576..32bc7a26e 100644 --- a/app/graphql/types/geo_area_type.rb +++ b/app/graphql/types/geo_area_type.rb @@ -12,7 +12,7 @@ module Types global_id_field :id field :source, GeoAreaSource, null: false - field :geometry, Types::GeoJSON, null: false, method: :safe_geometry + field :geometry, Types::GeoJSON, null: false field :description, String, null: true definition_methods do diff --git a/app/jobs/migrations/normalize_geo_area_job.rb b/app/jobs/migrations/normalize_geo_area_job.rb new file mode 100644 index 000000000..e5f8e7d1d --- /dev/null +++ b/app/jobs/migrations/normalize_geo_area_job.rb @@ -0,0 +1,11 @@ +class Migrations::NormalizeGeoAreaJob < ApplicationJob + def perform(ids) + GeoArea.where(id: ids).find_each do |geo_area| + geojson = RGeo::GeoJSON.decode(geo_area.geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) + geometry = RGeo::GeoJSON.encode(geojson) + geo_area.update_column(:geometry, geometry) + rescue RGeo::Error::InvalidGeometry + geo_area.destroy + end + end +end diff --git a/app/models/champs/carte_champ.rb b/app/models/champs/carte_champ.rb index 8d86789fc..208b72dfb 100644 --- a/app/models/champs/carte_champ.rb +++ b/app/models/champs/carte_champ.rb @@ -64,21 +64,14 @@ class Champs::CarteChamp < Champ end def bounding_box - factory = RGeo::Geographic.simple_mercator_factory - bounding_box = RGeo::Cartesian::BoundingBox.new(factory) - if geo_areas.present? - geo_areas.filter_map(&:rgeo_geometry).each do |geometry| - bounding_box.add(geometry) - end + GeojsonService.bbox(type: 'FeatureCollection', features: geo_areas.map(&:to_feature)) elsif dossier.present? point = dossier.geo_position - bounding_box.add(factory.point(point[:lon], point[:lat])) + GeojsonService.bbox(type: 'Feature', geometry: { type: 'Point', coordinates: [point[:lon], point[:lat]] }) else - bounding_box.add(factory.point(DEFAULT_LON, DEFAULT_LAT)) + GeojsonService.bbox(type: 'Feature', geometry: { type: 'Point', coordinates: [DEFAULT_LON, DEFAULT_LAT] }) end - - [bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates) end def to_feature_collection diff --git a/app/models/champs/epci_champ.rb b/app/models/champs/epci_champ.rb index ce48da5a5..45803bd8b 100644 --- a/app/models/champs/epci_champ.rb +++ b/app/models/champs/epci_champ.rb @@ -24,6 +24,10 @@ class Champs::EpciChamp < Champs::TextChamp store_accessor :value_json, :code_departement before_validation :on_departement_change + validate :code_departement_in_departement_codes, unless: -> { code_departement.nil? } + validate :external_id_in_departement_epci_codes, unless: -> { code_departement.nil? || external_id.nil? } + validate :value_in_departement_epci_names, unless: -> { code_departement.nil? || external_id.nil? || value.nil? } + def for_export [value, code, "#{code_departement} – #{departement_name}"] end @@ -74,4 +78,22 @@ class Champs::EpciChamp < Champs::TextChamp self.value = nil end end + + def code_departement_in_departement_codes + return if code_departement.in?(APIGeoService.departements.pluck(:code)) + + errors.add(:code_departement, :not_in_departement_codes) + end + + def external_id_in_departement_epci_codes + return if external_id.in?(APIGeoService.epcis(code_departement).pluck(:code)) + + errors.add(:external_id, :not_in_departement_epci_codes) + end + + def value_in_departement_epci_names + return if value.in?(APIGeoService.epcis(code_departement).pluck(:name)) + + errors.add(:value, :not_in_departement_epci_names) + end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index db8893a17..31eb93b03 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1312,14 +1312,7 @@ class Dossier < ApplicationRecord end def bounding_box - factory = RGeo::Geographic.simple_mercator_factory - bounding_box = RGeo::Cartesian::BoundingBox.new(factory) - - geo_areas.filter_map(&:rgeo_geometry).each do |geometry| - bounding_box.add(geometry) - end - - [bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates) + GeojsonService.bbox(type: 'FeatureCollection', features: geo_areas.map(&:to_feature)) end def log_dossier_operation(author, operation, subject = nil) diff --git a/app/models/geo_area.rb b/app/models/geo_area.rb index 024b20d4d..57040c80d 100644 --- a/app/models/geo_area.rb +++ b/app/models/geo_area.rb @@ -52,11 +52,12 @@ class GeoArea < ApplicationRecord scope :cadastres, -> { where(source: sources.fetch(:cadastre)) } validates :geometry, geo_json: true, allow_blank: false + before_validation :normalize_geometry def to_feature { type: 'Feature', - geometry: safe_geometry, + geometry: geometry.deep_symbolize_keys, properties: cadastre_properties.merge( source: source, area: area, @@ -96,16 +97,6 @@ class GeoArea < ApplicationRecord end end - def safe_geometry - RGeo::GeoJSON.encode(rgeo_geometry) - end - - def rgeo_geometry - RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) - rescue RGeo::Error::InvalidGeometry - nil - end - def area if polygon? GeojsonService.area(geometry.deep_symbolize_keys).round(1) @@ -120,7 +111,7 @@ class GeoArea < ApplicationRecord def location if point? - Geo::Coord.new(*rgeo_geometry.coordinates.reverse).to_s + Geo::Coord.new(*geometry['coordinates'].reverse).to_s end end @@ -238,4 +229,21 @@ class GeoArea < ApplicationRecord properties['id'] end end + + private + + def normalize_geometry + if geometry.present? + normalized_geometry = rgeo_geometry + if normalized_geometry.present? + self.geometry = RGeo::GeoJSON.encode(normalized_geometry) + end + end + end + + def rgeo_geometry + RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory) + rescue RGeo::Error::InvalidGeometry + nil + end end diff --git a/app/models/prefill_description.rb b/app/models/prefill_description.rb index e85e30977..090e02fb8 100644 --- a/app/models/prefill_description.rb +++ b/app/models/prefill_description.rb @@ -45,17 +45,33 @@ class PrefillDescription < SimpleDelegator private - def prefilled_champs_for_link - prefilled_champs.map { |type_de_champ| ["champ_#{type_de_champ.to_typed_id}", type_de_champ.example_value] }.to_h - end - - def prefilled_champs_for_query - prefilled_champs.map do |type_de_champ| - "\"champ_#{type_de_champ.to_typed_id}\": #{type_de_champ.formatted_example_value}" - end.join(', ') - end - def active_fillable_public_types_de_champ active_revision.types_de_champ_public.fillable end + + def prefilled_champs_for_link + prefilled_champs_as_params.map(&:to_a).to_h + end + + def prefilled_champs_for_query + prefilled_champs_as_params.map(&:to_s).join(', ') + end + + def prefilled_champs_as_params + prefilled_champs.map { |type_de_champ| Param.new(type_de_champ.to_typed_id, type_de_champ.example_value) } + end + + Param = Struct.new(:key, :value) do + def to_a + ["champ_#{key}", value] + end + + def to_s + if value.is_a?(Array) + "\"champ_#{key}\": #{value}" + else + "\"champ_#{key}\": \"#{value}\"" + end + end + end end diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 6d1ad72d8..26308d322 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -41,7 +41,8 @@ class PrefillParams TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:pays), TypeDeChamp.type_champs.fetch(:regions), - TypeDeChamp.type_champs.fetch(:departements) + TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:epci) ] attr_reader :champ, :value @@ -55,12 +56,6 @@ class PrefillParams champ.prefillable? && valid? end - def champ_attributes - TypesDeChamp::PrefillTypeDeChamp - .build(champ.type_de_champ) - .to_assignable_attributes(champ, value) - end - private def valid? @@ -69,5 +64,11 @@ class PrefillParams champ.assign_attributes(champ_attributes) champ.valid?(:prefill) end + + def champ_attributes + TypesDeChamp::PrefillTypeDeChamp + .build(champ.type_de_champ) + .to_assignable_attributes(value) + end end end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 7386be9ad..058ab7f69 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -271,7 +271,8 @@ class TypeDeChamp < ApplicationRecord TypeDeChamp.type_champs.fetch(:drop_down_list), TypeDeChamp.type_champs.fetch(:regions), TypeDeChamp.type_champs.fetch(:repetition), - TypeDeChamp.type_champs.fetch(:departements) + TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:epci) ]) end diff --git a/app/models/types_de_champ/prefill_epci_type_de_champ.rb b/app/models/types_de_champ/prefill_epci_type_de_champ.rb new file mode 100644 index 000000000..aae3b1918 --- /dev/null +++ b/app/models/types_de_champ/prefill_epci_type_de_champ.rb @@ -0,0 +1,25 @@ +class TypesDeChamp::PrefillEpciTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp + def possible_values + departements.map do |departement| + "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/epcis?codeDepartement=#{departement[:code]}" + end + end + + def example_value + departement_code = departements.pick(:code) + epci_code = APIGeoService.epcis(departement_code).pick(:code) + [departement_code, epci_code] + end + + def to_assignable_attributes(champ, value) + return { id: champ.id, code_departement: nil, value: nil } if value.blank? || !value.is_a?(Array) + return { id: champ.id, code_departement: value.first, value: nil } if value.one? + { id: champ.id, code_departement: value.first, value: value.second } + end + + private + + def departements + @departements ||= APIGeoService.departements.sort_by { |departement| departement[:code] } + end +end diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 35a7dd883..240ca6f6f 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -13,8 +13,6 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh [row_values_format, row_values_format].map { |row| row.to_s.gsub("=>", ":") } end - alias_method :formatted_example_value, :example_value - def to_assignable_attributes(champ, value) return [] unless value.is_a?(Array) diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index e0136927a..e389315fa 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -16,6 +16,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ) when TypeDeChamp.type_champs.fetch(:departements) TypesDeChamp::PrefillDepartementTypeDeChamp.new(type_de_champ) + when TypeDeChamp.type_champs.fetch(:epci) + TypesDeChamp::PrefillEpciTypeDeChamp.new(type_de_champ) else new(type_de_champ) end @@ -46,10 +48,6 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator I18n.t("views.prefill_descriptions.edit.examples.#{type_champ}") end - def formatted_example_value - "\"#{example_value}\"" if example_value.present? - end - def to_assignable_attributes(champ, value) { id: champ.id, value: value } end diff --git a/app/serializers/champ_serializer.rb b/app/serializers/champ_serializer.rb index 9802a4874..670979c81 100644 --- a/app/serializers/champ_serializer.rb +++ b/app/serializers/champ_serializer.rb @@ -13,7 +13,7 @@ class ChampSerializer < ActiveModel::Serializer def value case object when GeoArea - object.safe_geometry + object.geometry else object.for_api end diff --git a/app/serializers/geo_area_serializer.rb b/app/serializers/geo_area_serializer.rb index f0ff85ccb..39fb6cfd3 100644 --- a/app/serializers/geo_area_serializer.rb +++ b/app/serializers/geo_area_serializer.rb @@ -12,7 +12,7 @@ class GeoAreaSerializer < ActiveModel::Serializer attribute :code_arr, if: :include_cadastre? def geometry - object.safe_geometry + object.geometry end def include_cadastre? diff --git a/app/services/geojson_service.rb b/app/services/geojson_service.rb index d2a78d884..154387193 100644 --- a/app/services/geojson_service.rb +++ b/app/services/geojson_service.rb @@ -45,6 +45,66 @@ class GeojsonService radians * EQUATORIAL_RADIUS end + def self.bbox(geojson) + result = [-Float::INFINITY, -Float::INFINITY, Float::INFINITY, Float::INFINITY] + + self.coord_each(geojson) do |coord| + if result[3] > coord[1] + result[3] = coord[1] + end + if result[2] > coord[0] + result[2] = coord[0] + end + if result[1] < coord[1] + result[1] = coord[1] + end + if result[0] < coord[0] + result[0] = coord[0] + end + end + + result + end + + def self.coord_each(geojson) + geometries = if geojson.fetch(:type) == "FeatureCollection" + geojson.fetch(:features).map { _1.fetch(:geometry) } + else + [geojson.fetch(:geometry)] + end.compact + + geometries.each do |geometry| + geometries = if geometry.fetch(:type) == "GeometryCollection" + geometry.fetch(:geometries) + else + [geometry] + end.compact + + geometries.each do |geometry| + case geometry.fetch(:type) + when "Point" + yield geometry.fetch(:coordinates).map(&:to_f) + when "LineString", "MultiPoint" + geometry.fetch(:coordinates).each { yield _1.map(&:to_f) } + when "Polygon", "MultiLineString" + geometry.fetch(:coordinates).each do |shapes| + shapes.each { yield _1.map(&:to_f) } + end + when "MultiPolygon" + geometry.fetch(:coordinates).each do |polygons| + polygons.each do |shapes| + shapes.each { yield _1.map(&:to_f) } + end + end + when "GeometryCollection" + geometry.fetch(:geometries).each do |geometry| + coord_each(geometry) { yield _1 } + end + end + end + end + end + def self.calculate_area(geom) total = 0 case geom[:type] diff --git a/app/views/administrateurs/procedures/_detail.html.haml b/app/views/administrateurs/procedures/_detail.html.haml index f25a90fd1..a02396a9e 100644 --- a/app/views/administrateurs/procedures/_detail.html.haml +++ b/app/views/administrateurs/procedures/_detail.html.haml @@ -16,7 +16,7 @@ - if show_detail %tr.procedure{ id: "procedure_detail_#{procedure.id}" } - %td.fr-highlight--beige-gris-galet{ colspan: '6' } + %td.fr-highlight--beige-gris-galet{ colspan: '7' } .fr-container .fr-grid-row .fr-col-6 diff --git a/app/views/administrateurs/procedures/all.html.haml b/app/views/administrateurs/procedures/all.html.haml index 1ac68b3be..56ad5e129 100644 --- a/app/views/administrateurs/procedures/all.html.haml +++ b/app/views/administrateurs/procedures/all.html.haml @@ -11,7 +11,7 @@ = f.search_field 'libelle', size: 30, class: 'fr-input' .actions .link.fr-mx-1w= link_to 'Voir les administrateurs', administrateurs_admin_procedures_path(@filter.params), class: 'fr-btn fr-btn--secondary' - .link.fr-mx-1w= link_to 'Exporter les résultats', all_admin_procedures_path(@filter.params.merge(format: :xlsx)), class: 'fr-btn fr-btn--secondary' + .link.fr-mx-1w{ "data-turbo": "false" }= link_to 'Exporter les résultats', all_admin_procedures_path(@filter.params.merge(format: :xlsx)), class: 'fr-btn fr-btn--secondary' .fr-table.fr-table--bordered %table#all-demarches %caption diff --git a/config/locales/en.yml b/config/locales/en.yml index 81f6e249e..ad1ab9573 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -128,14 +128,15 @@ en: iban_html: An Iban number yes_no_html: '"true" for Yes, "false" pour No' checkbox_html: '"true" to check, "false" to uncheck' - pays_html: An ISO 3166-2 country code + pays_html: An ISO 3166-2 country code departements_html: A department number - regions_html: An INSEE region code drop_down_list_html: A choice among those selected when the procedure was created date_html: ISO8601 date datetime_html: ISO8601 datetime drop_down_list_other_html: Any value repetition_html: A array of hashes with possible values for each field of the repetition. + regions_html: An INSEE region code + epci_html: An array of the department code and the EPCI one. examples: title: Example text: Short text @@ -484,6 +485,14 @@ en: not_in_departement_names: "must be a valid department name" external_id: not_in_departement_codes: "must be a valid department code" + "champs/epci_champ": + attributes: + code_departement: + not_in_departement_codes: "must be a valid department code" + external_id: + not_in_departement_epci_codes: "must be a valid EPCI code of the matching department" + value: + not_in_departement_epci_names: "must be a valid EPCI name of the matching department" errors: format: "Field « %{attribute} » %{message}" messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ed844d242..a1cc70287 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -119,14 +119,15 @@ fr: iban_html: Un numéro Iban yes_no_html: '"true" pour Oui, "false" pour Non' checkbox_html: '"true" pour coché, "false" pour décoché' - pays_html: Un code pays ISO 3166-2 + pays_html: Un code pays ISO 3166-2 departements_html: Un numéro de département - regions_html: Un code INSEE de région drop_down_list_html: Un choix parmi ceux sélectionnés à la création de la procédure datetime_html: Datetime au format ISO8601 date_html: Date au format ISO8601 drop_down_list_other_html: Toute valeur repetition_html: Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition. + regions_html: Un code INSEE de région + epci_html: Un tableau contenant le code de département et celui de l'EPCI. examples: title: Exemple text: Texte court @@ -479,6 +480,14 @@ fr: not_in_departement_names: "doit être un nom de département valide" external_id: not_in_departement_codes: "doit être un code de département valide" + "champs/epci_champ": + attributes: + code_departement: + not_in_departement_codes: "doit être un code de département valide" + external_id: + not_in_departement_epci_codes: "doit être un code d'EPCI du département correspondant" + value: + not_in_departement_epci_names: "doit être un nom d'EPCI du département correspondant" errors: format: "Le champ « %{attribute} » %{message}" messages: diff --git a/lib/tasks/deployment/20230215100231_normalize_geometries.rake b/lib/tasks/deployment/20230215100231_normalize_geometries.rake new file mode 100644 index 000000000..275e37169 --- /dev/null +++ b/lib/tasks/deployment/20230215100231_normalize_geometries.rake @@ -0,0 +1,19 @@ +namespace :after_party do + desc 'Deployment task: normalize_geometries' + task normalize_geometries: :environment do + puts "Running deploy task 'normalize_geometries'" + + progress = ProgressReport.new(GeoArea.count) + GeoArea.in_batches(of: 100) do |geo_areas| + ids = geo_areas.ids + Migrations::NormalizeGeoAreaJob.perform_later(ids) + progress.inc(ids.size) + end + progress.finish + + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end diff --git a/spec/models/champs/carte_champ_spec.rb b/spec/models/champs/carte_champ_spec.rb index b006ad092..c7e4e5c99 100644 --- a/spec/models/champs/carte_champ_spec.rb +++ b/spec/models/champs/carte_champ_spec.rb @@ -1,7 +1,7 @@ describe Champs::CarteChamp do let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) } let(:value) { '' } - let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] } + let(:coordinates) { [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]]] } let(:geo_json) do { "type" => 'Polygon', diff --git a/spec/models/champs/epci_champ_spec.rb b/spec/models/champs/epci_champ_spec.rb index be06ed379..1b5a56dd4 100644 --- a/spec/models/champs/epci_champ_spec.rb +++ b/spec/models/champs/epci_champ_spec.rb @@ -6,9 +6,156 @@ describe Champs::EpciChamp, type: :model do Rails.cache.clear end - let(:champ) { described_class.new } + describe 'validations' do + describe 'code_departement', vcr: { cassette_name: 'api_geo_departements' } do + subject { build(:champ_epci, code_departement: code_departement) } + + context 'when nil' do + let(:code_departement) { nil } + + it { is_expected.to be_valid } + end + + context 'when empty' do + let(:code_departement) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when included in the departement codes' do + let(:code_departement) { "01" } + + it { is_expected.to be_valid } + end + + context 'when not included in the departement codes' do + let(:code_departement) { "totoro" } + + it { is_expected.not_to be_valid } + end + end + + describe 'external_id' do + let(:champ) { build(:champ_epci, code_departement: code_departement, external_id: nil) } + + subject { champ } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + champ.save! + champ.update_columns(external_id: external_id) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + context 'when code_departement is nil' do + let(:code_departement) { nil } + let(:external_id) { nil } + + it { is_expected.to be_valid } + end + + context 'when code_departement is not nil and valid' do + let(:code_departement) { "01" } + + context 'when external_id is nil' do + let(:external_id) { nil } + + it { is_expected.to be_valid } + end + + context 'when external_id is empty' do + let(:external_id) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when external_id is included in the epci codes of the departement' do + let(:external_id) { '200042935' } + + it { is_expected.to be_valid } + end + + context 'when external_id is not included in the epci codes of the departement' do + let(:external_id) { 'totoro' } + + it { is_expected.not_to be_valid } + end + end + end + + describe 'value' do + let(:champ) { build(:champ_epci, code_departement: code_departement, external_id: nil, value: nil) } + + subject { champ } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + champ.save! + champ.update_columns(external_id: external_id, value: value) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + context 'when code_departement is nil' do + let(:code_departement) { nil } + let(:external_id) { nil } + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when external_id is nil' do + let(:code_departement) { '01' } + let(:external_id) { nil } + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when code_departement and external_id are not nil and valid' do + let(:code_departement) { '01' } + let(:external_id) { '200042935' } + + context 'when value is nil' do + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when value is empty' do + let(:value) { '' } + + it { is_expected.not_to be_valid } + end + + context 'when value is in departement epci names' do + let(:value) { 'CA Haut - Bugey Agglomération' } + + it { is_expected.to be_valid } + end + + context 'when value is not in departement epci names' do + let(:value) { 'totoro' } + + it { is_expected.not_to be_valid } + end + end + end + end describe 'value', vcr: { cassette_name: 'api_geo_epcis' } do + let(:champ) { described_class.new } it 'with departement and code' do champ.code_departement = '01' champ.value = '200042935' diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index f4d94a27b..cdc43d0f0 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1516,8 +1516,8 @@ describe Dossier do { type: 'Feature', geometry: { - 'coordinates' => [[[2.428439855575562, 46.538476837725796], [2.4284291267395024, 46.53842148758162], [2.4282521009445195, 46.53841410755813], [2.42824137210846, 46.53847314771794], [2.428284287452698, 46.53847314771794], [2.428364753723145, 46.538487907747864], [2.4284291267395024, 46.538491597754714], [2.428439855575562, 46.538476837725796]]], - 'type' => 'Polygon' + coordinates: [[[2.428439855575562, 46.538476837725796], [2.4284291267395024, 46.53842148758162], [2.4282521009445195, 46.53841410755813], [2.42824137210846, 46.53847314771794], [2.428284287452698, 46.53847314771794], [2.428364753723145, 46.538487907747864], [2.4284291267395024, 46.538491597754714], [2.428439855575562, 46.538476837725796]]], + type: 'Polygon' }, properties: { area: 103.6, diff --git a/spec/models/geo_area_spec.rb b/spec/models/geo_area_spec.rb index 0a746f363..0cd51a7c6 100644 --- a/spec/models/geo_area_spec.rb +++ b/spec/models/geo_area_spec.rb @@ -23,7 +23,7 @@ RSpec.describe GeoArea, type: :model do it { expect(geo_area.location).to eq("46°32'19\"N 2°25'42\"E") } end - describe '#rgeo_geometry' do + describe '#geometry' do let(:geo_area) { build(:geo_area, :polygon, champ: nil) } let(:polygon) do { @@ -47,9 +47,9 @@ RSpec.describe GeoArea, type: :model do context 'polygon_with_extra_coordinate' do let(:geo_area) { build(:geo_area, :polygon_with_extra_coordinate, champ: nil) } + before { geo_area.valid? } - it { expect(geo_area.geometry).not_to eq(polygon) } - it { expect(geo_area.safe_geometry).to eq(polygon) } + it { expect(geo_area.geometry).to eq(polygon) } end end diff --git a/spec/models/prefill_description_spec.rb b/spec/models/prefill_description_spec.rb index d93b0c68a..063eb2db3 100644 --- a/spec/models/prefill_description_spec.rb +++ b/spec/models/prefill_description_spec.rb @@ -105,6 +105,34 @@ RSpec.describe PrefillDescription, type: :model do ) ) end + + context 'when the type de champ can have multiple values' do + let(:type_de_champ) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } + + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + it 'builds the URL with array parameter' do + expect(prefill_description.prefill_link).to eq( + commencer_url( + path: procedure.path, + "champ_#{type_de_champ.to_typed_id}": type_de_champ.example_value + ) + ) + end + end end describe '#prefill_query', vcr: { cassette_name: 'api_geo_regions' } do @@ -121,10 +149,43 @@ RSpec.describe PrefillDescription, type: :model do --data '{"champ_#{type_de_champ.to_typed_id}": "#{I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")}", "champ_#{type_de_champ_repetition.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value}}' TEXT end + before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id, type_de_champ_repetition.id]) } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id]) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + it "builds the query to create a new prefilled dossier" do expect(prefill_description.prefill_query).to eq(expected_query) end + + context 'when the type de champ can have multiple values' do + let(:type_de_champ) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } + let(:expected_query) do + <<~TEXT + curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ + --header 'Content-Type: application/json' \\ + --data '{"champ_#{type_de_champ.to_typed_id}": #{type_de_champ.example_value}}' + TEXT + end + + it 'builds the query with array parameter' do + expect(prefill_description.prefill_query).to eq(expected_query) + end + end end end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index ab76397a0..1842b9900 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -12,6 +12,16 @@ RSpec.describe PrefillParams do before do allow(Rails).to receive(:cache).and_return(memory_store) Rails.cache.clear + + VCR.insert_cassette('api_geo_regions') + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_regions') + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') end context "when the stable ids match the TypeDeChamp of the corresponding procedure" do @@ -76,7 +86,7 @@ RSpec.describe PrefillParams do let(:params) { { "champ_#{type_de_champ.to_typed_id}" => value } } - it "builds an array of hash(id, value) matching the given params" do + it "builds an array of hash matching the given params" do expect(prefill_params_array).to match([{ id: champ.id }.merge(attributes(champ, value))]) end end @@ -90,7 +100,7 @@ RSpec.describe PrefillParams do let(:params) { { "champ_#{type_de_champ.to_typed_id}" => value } } - it "builds an array of hash(id, value) matching the given params" do + it "builds an array of hash matching the given params" do expect(prefill_params_array).to match([{ id: champ.id }.merge(attributes(champ, value))]) end end @@ -127,6 +137,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is authorized", :drop_down_list, "value" it_behaves_like "a champ public value that is authorized", :regions, "03" it_behaves_like "a champ public value that is authorized", :departements, "03" + it_behaves_like "a champ public value that is authorized", :epci, ['01', '200042935'] context "when the public type de champ is authorized (repetition)" do let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :text }] }] } @@ -159,7 +170,8 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :checkbox, "false" it_behaves_like "a champ private value that is authorized", :drop_down_list, "value" it_behaves_like "a champ private value that is authorized", :regions, "93" - it_behaves_like "a champ public value that is authorized", :departements, "03" + it_behaves_like "a champ private value that is authorized", :departements, "03" + it_behaves_like "a champ private value that is authorized", :epci, ['01', '200042935'] context "when the private type de champ is authorized (repetition)" do let(:types_de_champ_private) { [{ type: :repetition, children: [{ type: :text }] }] } diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index 79c5d9ecc..f2cb0754b 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -252,6 +252,7 @@ describe TypeDeChamp do it_behaves_like "a prefillable type de champ", :type_de_champ_regions it_behaves_like "a prefillable type de champ", :type_de_champ_repetition it_behaves_like "a prefillable type de champ", :type_de_champ_departements + it_behaves_like "a prefillable type de champ", :type_de_champ_epci it_behaves_like "a non-prefillable type de champ", :type_de_champ_number it_behaves_like "a non-prefillable type de champ", :type_de_champ_communes diff --git a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb new file mode 100644 index 000000000..ef59b9df3 --- /dev/null +++ b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do + let(:type_de_champ) { build(:type_de_champ_epci) } + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + + describe 'ancestors' do + subject { described_class.new(type_de_champ) } + + it { is_expected.to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } + end + + describe '#possible_values' do + let(:expected_values) do + departements.map { |departement| "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/epcis?codeDepartement=#{departement[:code]}" } + end + subject(:possible_values) { described_class.new(type_de_champ).possible_values } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + it { expect(possible_values).to match(expected_values) } + end + + describe '#example_value' do + let(:departement_code) { departements.pick(:code) } + let(:epci_code) { APIGeoService.epcis(departement_code).pick(:code) } + subject(:example_value) { described_class.new(type_de_champ).example_value } + + before do + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + it { is_expected.to eq([departement_code, epci_code]) } + end + + describe '#to_assignable_attributes' do + subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } + + context 'when the value is nil' do + let(:value) { nil } + it { is_expected.to match({ code_departement: nil, value: nil }) } + end + + context 'when the value is empty' do + let(:value) { '' } + it { is_expected.to match({ code_departement: nil, value: nil }) } + end + + context 'when the value is a string' do + let(:value) { 'hello' } + it { is_expected.to match({ code_departement: nil, value: nil }) } + end + + context 'when the value is an array of one element' do + let(:value) { ['01'] } + it { is_expected.to match({ code_departement: '01', value: nil }) } + end + + context 'when the value is an array of two elements' do + let(:value) { ['01', '200042935'] } + it { is_expected.to match({ code_departement: '01', value: '200042935' }) } + end + + context 'when the value is an array of three or more elements' do + let(:value) { ['01', '200042935', 'hello'] } + it { is_expected.to match({ code_departement: '01', value: '200042935' }) } + end + end + + private + + def departements + APIGeoService.departements.sort_by { |departement| departement[:code] } + end +end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index b9b32a50d..a4b1655e5 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -37,6 +37,12 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(built).to be_kind_of(TypesDeChamp::PrefillDepartementTypeDeChamp) } end + context 'when the type de champ is a epci' do + let(:type_de_champ) { build(:type_de_champ_epci) } + + it { expect(built).to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } + end + context 'when any other type de champ' do let(:type_de_champ) { build(:type_de_champ_date) } diff --git a/spec/support/shared_examples_for_prefilled_dossier.rb b/spec/support/shared_examples_for_prefilled_dossier.rb index 11805c31d..4698f788f 100644 --- a/spec/support/shared_examples_for_prefilled_dossier.rb +++ b/spec/support/shared_examples_for_prefilled_dossier.rb @@ -22,5 +22,6 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do expect(page).to have_field(text_repetition_libelle, with: text_repetition_value) expect(page).to have_field(integer_repetition_libelle, with: integer_repetition_value) expect(page).to have_field(type_de_champ_datetime.libelle, with: datetime_value) + expect(page).to have_field(type_de_champ_epci.libelle, with: epci_value.last) end end diff --git a/spec/system/administrateurs/procedure_cloning_spec.rb b/spec/system/administrateurs/procedure_cloning_spec.rb index dc6dfafe4..a53545f09 100644 --- a/spec/system/administrateurs/procedure_cloning_spec.rb +++ b/spec/system/administrateurs/procedure_cloning_spec.rb @@ -14,7 +14,22 @@ describe 'As an administrateur I wanna clone a procedure', js: true do published_at: Time.zone.now) login_as administrateur.user, scope: :user end + context 'Visit all admin procedures' do + let(:download_dir) { Rails.root.join('tmp/capybara') } + let(:download_file_pattern) { download_dir.join('*.xlsx') } + scenario do + Dir[download_file_pattern].map { File.delete(_1) } + visit all_admin_procedures_path + + click_on "Exporter les résultats" + Timeout.timeout(Capybara.default_max_wait_time, + Timeout::Error, + "File download timeout! can't download procedure/all.xlsx") do + sleep 0.1 until !Dir[download_file_pattern].empty? + end + end + end context 'Cloning a procedure owned by the current admin' do scenario do visit admin_procedures_path diff --git a/spec/system/users/dossier_prefill_get_spec.rb b/spec/system/users/dossier_prefill_get_spec.rb index c876d09aa..b93cb40c5 100644 --- a/spec/system/users/dossier_prefill_get_spec.rb +++ b/spec/system/users/dossier_prefill_get_spec.rb @@ -1,4 +1,6 @@ describe 'Prefilling a dossier (with a GET request):' do + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + let(:password) { 'my-s3cure-p4ssword' } let(:procedure) { create(:procedure, :published, opendata: true) } @@ -16,6 +18,21 @@ describe 'Prefilling a dossier (with a GET request):' do let(:integer_repetition_libelle) { sub_type_de_champs_repetition.second.libelle } let(:text_repetition_value) { "First repetition text" } let(:integer_repetition_value) { "42" } + let(:type_de_champ_epci) { create(:type_de_champ_epci, procedure: procedure) } + let(:epci_value) { ['01', '200029999'] } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end context 'when authenticated' do it_behaves_like "the user has got a prefilled dossier, owned by themselves" do @@ -35,7 +52,8 @@ describe 'Prefilling a dossier (with a GET request):' do \"#{sub_type_de_champs_repetition.second.to_typed_id}\": \"#{integer_repetition_value}\" }" ], - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value ) click_on "Poursuivre mon dossier prérempli" @@ -55,7 +73,8 @@ describe 'Prefilling a dossier (with a GET request):' do \"#{sub_type_de_champs_repetition.second.to_typed_id}\": \"#{integer_repetition_value}\" }" ], - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value ) end diff --git a/spec/system/users/dossier_prefill_post_spec.rb b/spec/system/users/dossier_prefill_post_spec.rb index d67c93a48..78c4037a1 100644 --- a/spec/system/users/dossier_prefill_post_spec.rb +++ b/spec/system/users/dossier_prefill_post_spec.rb @@ -1,4 +1,6 @@ describe 'Prefilling a dossier (with a POST request):' do + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + let(:password) { 'my-s3cure-p4ssword' } let(:procedure) { create(:procedure, :published) } @@ -16,6 +18,21 @@ describe 'Prefilling a dossier (with a POST request):' do let(:integer_repetition_libelle) { sub_type_de_champs_repetition.second.libelle } let(:text_repetition_value) { "First repetition text" } let(:integer_repetition_value) { "42" } + let(:type_de_champ_epci) { create(:type_de_champ_epci, procedure: procedure) } + let(:epci_value) { ['01', '200029999'] } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end scenario "the user get the URL of a prefilled orphan brouillon dossier" do dossier_url = create_and_prefill_dossier_with_post_request @@ -110,7 +127,8 @@ describe 'Prefilling a dossier (with a POST request):' do \"#{sub_type_de_champs_repetition.second.to_typed_id}\": \"#{integer_repetition_value}\" }" ], - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value }.to_json JSON.parse(session.response.body)["dossier_url"].gsub("http://www.example.com", "") end From 96b9ec3f42655a541dcaea17e3cc1a9aca8c3d31 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Wed, 15 Feb 2023 18:13:16 +0100 Subject: [PATCH 053/202] First fix tests --- app/models/prefill_params.rb | 11 ++++++----- .../prefill_repetition_type_de_champ_spec.rb | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 2065ad630..52f8ce1e6 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -56,6 +56,12 @@ class PrefillParams champ.prefillable? && valid? end + def champ_attributes + TypesDeChamp::PrefillTypeDeChamp + .build(champ.type_de_champ) + .to_assignable_attributes(champ, value) + end + private def valid? @@ -65,10 +71,5 @@ class PrefillParams champ.valid?(:prefill) end - def champ_attributes - TypesDeChamp::PrefillTypeDeChamp - .build(champ.type_de_champ) - .to_assignable_attributes(champ, value) - end end end diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index df0a88402..6397953a6 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -24,7 +24,7 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { describe '#possible_values' do subject(:possible_values) { described_class.new(type_de_champ).possible_values } let(:expected_value) { - "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
      " + "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
      " } it { From 74c6d45b74a1628ecd358887f2d6c7b1584206c2 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 15 Feb 2023 17:44:49 +0100 Subject: [PATCH 054/202] feat(graphql): add tracing support for managers --- app/controllers/api/v2/graphql_controller.rb | 36 +++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/v2/graphql_controller.rb b/app/controllers/api/v2/graphql_controller.rb index 52c453051..d18a641ec 100644 --- a/app/controllers/api/v2/graphql_controller.rb +++ b/app/controllers/api/v2/graphql_controller.rb @@ -1,10 +1,6 @@ class API::V2::GraphqlController < API::V2::BaseController def execute - result = API::V2::Schema.execute(query, - variables: variables, - context: context, - operation_name: params[:operationName]) - + result = tracing? ? instrumented { perform_query } : perform_query render json: result rescue GraphQL::ParseError, JSON::ParserError => exception handle_parse_error(exception) @@ -18,6 +14,13 @@ class API::V2::GraphqlController < API::V2::BaseController private + def perform_query + API::V2::Schema.execute(query, + variables: variables, + context: context, + operation_name: params[:operationName]) + end + def append_info_to_payload(payload) super @@ -124,4 +127,27 @@ class API::V2::GraphqlController < API::V2::BaseController data: nil }, status: 500 end + + def tracing? + params[:tracing].present? && (Rails.env.development? || manager?) + end + + def manager? + administrateur_signed_in? && AdministrateursProcedure.exists?(administrateur: current_administrateur, manager: true) + end + + def instrumented + events = [] + ActiveSupport::Notifications.subscribed(-> (_name, start, finish, _id, payload) { events << { start: Time.zone.at(start), duration: (finish - start) * 1000, sql: payload[:sql] } }, "sql.active_record", monotonic: true) do + result = yield + result.merge(extensions: { + tracing: { + startTime: events.first[:start], + endTime: events.last[:start], + duration: events.sum { _1[:duration] }, + events: events.sort_by { _1[:duration] }.reverse + } + }) + end + end end From 0fe027f408ee9c0968353965ffb4486e7ee9f3e7 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 7 Feb 2023 17:00:39 +0100 Subject: [PATCH 055/202] a11y(main_menu): utilise aria-current=true plutot que avec page --- app/views/administrateurs/procedures/_main_menu.html.haml | 2 +- app/views/layouts/_header.haml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/administrateurs/procedures/_main_menu.html.haml b/app/views/administrateurs/procedures/_main_menu.html.haml index 9d3d28ef9..89f3de634 100644 --- a/app/views/administrateurs/procedures/_main_menu.html.haml +++ b/app/views/administrateurs/procedures/_main_menu.html.haml @@ -1,6 +1,6 @@ .fr-container %nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal administrateur' } %ul.fr-nav__list - %li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(admin_procedures_path) ? 'page' : nil + %li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(controller: 'procedures', action: :index) ? 'true' : nil - if Rails.application.config.ds_zonage_enabled %li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil diff --git a/app/views/layouts/_header.haml b/app/views/layouts/_header.haml index 41c23b0e5..c447fad9e 100644 --- a/app/views/layouts/_header.haml +++ b/app/views/layouts/_header.haml @@ -87,18 +87,18 @@ - if current_instructeur.procedures.any? - current_url = request.path_info %li.fr-nav__item - = active_link_to t('utils.procedure'), instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'fr-nav__link' + = active_link_to t('utils.procedure'), instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'fr-nav__link', aria: { current: current_url == instructeur_procedures_path ? 'page' : true } - if current_instructeur.user.expert && current_expert.avis_summary[:total] > 0 = render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert } - if is_expert_context - if current_expert.user.instructeur && current_instructeur.procedures.any? - %li.fr-nav__item= active_link_to t('utils.procedure'), instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'fr-nav__link' + %li.fr-nav__item= active_link_to t('utils.procedure'), instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'fr-nav__link', aria: { current: true } - if current_expert.avis_summary[:total] > 0 = render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert } - if is_user_context - %li.fr-nav__item= active_link_to t('.files'), dossiers_path, active: :inclusive, class: 'fr-nav__link' + %li.fr-nav__item= active_link_to t('.files'), dossiers_path, active: :inclusive, class: 'fr-nav__link', aria: { current: true } - if current_user.expert && current_expert.avis_summary[:total] > 0 = render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert } From ade9811d101a4ce35ac549f89d0c3ae8c3a1ee98 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 15 Feb 2023 19:07:54 +0100 Subject: [PATCH 056/202] Revert "Merge pull request #8635 from tchak/graphql-with-traces" This reverts commit 76520ec77d64286f44e9a0532fe6fbe98d097062, reversing changes made to 2c729ff396980ae898dea3a7878ca1a09fe828a5. --- app/controllers/api/v2/graphql_controller.rb | 36 +++----------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/v2/graphql_controller.rb b/app/controllers/api/v2/graphql_controller.rb index d18a641ec..52c453051 100644 --- a/app/controllers/api/v2/graphql_controller.rb +++ b/app/controllers/api/v2/graphql_controller.rb @@ -1,6 +1,10 @@ class API::V2::GraphqlController < API::V2::BaseController def execute - result = tracing? ? instrumented { perform_query } : perform_query + result = API::V2::Schema.execute(query, + variables: variables, + context: context, + operation_name: params[:operationName]) + render json: result rescue GraphQL::ParseError, JSON::ParserError => exception handle_parse_error(exception) @@ -14,13 +18,6 @@ class API::V2::GraphqlController < API::V2::BaseController private - def perform_query - API::V2::Schema.execute(query, - variables: variables, - context: context, - operation_name: params[:operationName]) - end - def append_info_to_payload(payload) super @@ -127,27 +124,4 @@ class API::V2::GraphqlController < API::V2::BaseController data: nil }, status: 500 end - - def tracing? - params[:tracing].present? && (Rails.env.development? || manager?) - end - - def manager? - administrateur_signed_in? && AdministrateursProcedure.exists?(administrateur: current_administrateur, manager: true) - end - - def instrumented - events = [] - ActiveSupport::Notifications.subscribed(-> (_name, start, finish, _id, payload) { events << { start: Time.zone.at(start), duration: (finish - start) * 1000, sql: payload[:sql] } }, "sql.active_record", monotonic: true) do - result = yield - result.merge(extensions: { - tracing: { - startTime: events.first[:start], - endTime: events.last[:start], - duration: events.sum { _1[:duration] }, - events: events.sort_by { _1[:duration] }.reverse - } - }) - end - end end From cbf072961c7a725324c2e6cc85915ece2578140d Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Tue, 14 Feb 2023 11:17:52 +0100 Subject: [PATCH 057/202] feat(groupe instructeur mailer): add emailing to removed instructeurs --- .../groupe_instructeurs_controller.rb | 4 +++ .../groupe_instructeurs_controller.rb | 4 +++ ...oupe_instructeur_supprimer_instructeurs.rb | 4 +++ app/mailers/groupe_instructeur_mailer.rb | 10 +++++++ .../remove_instructeur.html.haml | 6 +--- .../remove_instructeurs.html.haml | 2 +- .../groupe_instructeurs/en.yml | 4 ++- .../groupe_instructeurs/fr.yml | 4 ++- .../groupe_instructeurs_controller_spec.rb | 13 ++++++++- .../mailers/groupe_instructeur_mailer_spec.rb | 29 ++++++++++++++++--- .../groupe_instructeur_mailer_preview.rb | 8 +++++ 11 files changed, 75 insertions(+), 13 deletions(-) diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 63390b1f1..35e89221f 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -147,6 +147,10 @@ module Administrateurs if groupe_instructeur.remove(instructeur) flash[:notice] = if procedure.routing_enabled? + GroupeInstructeurMailer + .remove_instructeur(groupe_instructeur, [instructeur], current_administrateur.email) + .deliver_later + GroupeInstructeurMailer .remove_instructeurs(groupe_instructeur, [instructeur], current_administrateur.email) .deliver_later diff --git a/app/controllers/instructeurs/groupe_instructeurs_controller.rb b/app/controllers/instructeurs/groupe_instructeurs_controller.rb index 6eb25e131..20519c8af 100644 --- a/app/controllers/instructeurs/groupe_instructeurs_controller.rb +++ b/app/controllers/instructeurs/groupe_instructeurs_controller.rb @@ -34,6 +34,10 @@ module Instructeurs instructeur = Instructeur.find(instructeur_id) if groupe_instructeur.remove(instructeur) flash[:notice] = "L’instructeur « #{instructeur.email} » a été retiré du groupe." + GroupeInstructeurMailer + .remove_instructeur(groupe_instructeur, [instructeur], current_administrateur.email) + .deliver_later + GroupeInstructeurMailer .remove_instructeurs(groupe_instructeur, [instructeur], current_user.email) .deliver_later diff --git a/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb b/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb index 864f0c3c3..f8c14542a 100644 --- a/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb +++ b/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb @@ -16,6 +16,10 @@ module Mutations groupe_instructeur.reload if groupe_instructeur.procedure.routing_enabled? && instructeurs.present? + GroupeInstructeurMailer + .remove_instructeur(groupe_instructeur, instructeurs, current_administrateur.email) + .deliver_later + GroupeInstructeurMailer .remove_instructeurs(groupe_instructeur, instructeurs, current_administrateur.email) .deliver_later diff --git a/app/mailers/groupe_instructeur_mailer.rb b/app/mailers/groupe_instructeur_mailer.rb index cb2865bb8..9674faae2 100644 --- a/app/mailers/groupe_instructeur_mailer.rb +++ b/app/mailers/groupe_instructeur_mailer.rb @@ -11,4 +11,14 @@ class GroupeInstructeurMailer < ApplicationMailer emails = @group.instructeurs.map(&:email) mail(bcc: emails, subject: subject) end + + def remove_instructeur(group, removed_instructeurs, current_instructeur_email) + removed_instructeur_emails = removed_instructeurs.map(&:email) + @group = group + @current_instructeur_email = current_instructeur_email + + subject = "Retrait du groupe \"#{group.label}\"" + + mail(bcc: removed_instructeur_emails, subject: subject) + end end diff --git a/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml b/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml index 9e7770919..d541c0fd9 100644 --- a/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml +++ b/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml @@ -2,10 +2,6 @@ Bonjour, %p - = t('administrateurs.groupe_instructeurs.remove_instructeur.email_body', count: 1, emails: @email, groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) - -%p - Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : - = link_to(@group.label, admin_procedure_groupe_instructeur_url(@group.procedure, @group)) + = t('administrateurs.groupe_instructeurs.remove_instructeur.email_body', groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) = render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml index d5a73e165..1d3b218da 100644 --- a/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml +++ b/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml @@ -2,7 +2,7 @@ Bonjour, %p - = t('administrateurs.groupe_instructeurs.remove_instructeur.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + = t('administrateurs.groupe_instructeurs.remove_instructeurs.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) %p Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : diff --git a/config/locales/views/administrateurs/groupe_instructeurs/en.yml b/config/locales/views/administrateurs/groupe_instructeurs/en.yml index 208cd053f..7564938d2 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/en.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/en.yml @@ -19,10 +19,12 @@ en: email_body: one: "The instructor %{emails} was assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." other: "The instructors %{emails} were assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - remove_instructeur: + remove_instructeurs: email_body: one: "The instructor %{emails} was removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." other: "The instructors %{emails} were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." + remove_instructeur: + email_body: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} group exist" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml index dc26ca649..f5f978ecf 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml @@ -25,10 +25,12 @@ fr: email_body: one: "L’instructeur %{emails} a été affecté au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." other: "Les instructeurs %{emails} ont été affectés au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - remove_instructeur: + remove_instructeurs: email_body: one: "L’instructeur %{emails} a été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." other: "Les instructeurs %{emails} ont été retirés du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." + remove_instructeur: + email_body: "Vous avez été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} groupe existe" diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index 90e3a193f..3835696d2 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -320,11 +320,22 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do end context 'when there are many instructeurs' do - before { remove_instructeur(admin.instructeur) } + before do + allow(GroupeInstructeurMailer).to receive(:remove_instructeur) + .and_return(double(deliver_later: true)) + remove_instructeur(admin.instructeur) + end it { expect(gi_1_1.instructeurs).to include(instructeur) } it { expect(gi_1_1.reload.instructeurs.count).to eq(1) } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_1)) } + it "calls GroupeInstructeurMailer with the right groupe and instructeur" do + expect(GroupeInstructeurMailer).to have_received(:remove_instructeur).with( + gi_1_1, + [admin.instructeur], + admin.email + ) + end end context 'when there is only one instructeur' do diff --git a/spec/mailers/groupe_instructeur_mailer_spec.rb b/spec/mailers/groupe_instructeur_mailer_spec.rb index 9157f03ef..94b8b06ff 100644 --- a/spec/mailers/groupe_instructeur_mailer_spec.rb +++ b/spec/mailers/groupe_instructeur_mailer_spec.rb @@ -7,15 +7,36 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do gi.instructeurs << instructeurs_to_remove gi end - let(:instructeur_1) { create(:instructeur, email: 'int3@g') } - let(:instructeur_2) { create(:instructeur, email: 'int4@g') } + let(:instructeur_3) { create(:instructeur, email: 'int3@g') } + let(:instructeur_4) { create(:instructeur, email: 'int4@g') } - let(:instructeurs_to_remove) { [instructeur_1, instructeur_2] } + let(:instructeurs_to_remove) { [instructeur_3, instructeur_4] } let(:current_instructeur_email) { 'toto@email.com' } subject { described_class.remove_instructeurs(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } it { expect(subject.body).to include('Les instructeurs int3@g, int4@g ont été retirés du groupe') } - it { expect(subject.bcc).to match_array(['int1@g', 'int2@g', 'int3@g', 'int4@g']) } + it { expect(subject.bcc).to include('int1@g', 'int2@g') } + end + + describe '#remove_instructeur' do + let(:groupe_instructeur) do + gi = GroupeInstructeur.create(label: 'gi1', procedure: create(:procedure)) + gi.instructeurs << create(:instructeur, email: 'int1@g') + gi.instructeurs << create(:instructeur, email: 'int2@g') + gi.instructeurs << instructeurs_to_remove + gi + end + let(:instructeur_3) { create(:instructeur, email: 'int3@g') } + let(:instructeur_4) { create(:instructeur, email: 'int4@g') } + + let(:instructeurs_to_remove) { [instructeur_3, instructeur_4] } + let(:current_instructeur_email) { 'toto@email.com' } + + subject { described_class.remove_instructeur(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } + + it { expect(subject.body).to include('ous avez été retiré du groupe « gi1 » par « toto@email.com »') } + it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) } + it { expect(subject.bcc).not_to match_array(['int1@g', 'int2@g']) } end end diff --git a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb index 1b6c006a1..5a5f7ca30 100644 --- a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb +++ b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb @@ -6,4 +6,12 @@ class GroupeInstructeurMailerPreview < ActionMailer::Preview instructeurs = Instructeur.limit(2) GroupeInstructeurMailer.remove_instructeurs(groupe, instructeurs, current_instructeur_email) end + + def remove_instructeur + procedure = Procedure.new(id: 1, libelle: 'une superbe procedure') + groupe = GroupeInstructeur.new(id: 1, label: 'Val-De-Marne', procedure:) + current_instructeur_email = 'admin@dgfip.com' + instructeurs = Instructeur.limit(2) + GroupeInstructeurMailer.remove_instructeur(groupe, instructeurs, current_instructeur_email) + end end From 5be58c8223c7398fb62466b1b57f9547d332581b Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Tue, 14 Feb 2023 15:19:58 +0100 Subject: [PATCH 058/202] refactor(groupe instructeur mailer): rename mailer methods --- .../administrateurs/groupe_instructeurs_controller.rb | 4 ++-- .../instructeurs/groupe_instructeurs_controller.rb | 4 ++-- .../groupe_instructeur_supprimer_instructeurs.rb | 4 ++-- app/mailers/groupe_instructeur_mailer.rb | 6 +++--- .../notify_group_when_instructeurs_removed.html.haml | 11 +++++++++++ .../notify_removed_instructeurs.html.haml | 7 +++++++ .../remove_instructeur.html.haml | 7 ------- .../remove_instructeurs.html.haml | 11 ----------- .../views/administrateurs/groupe_instructeurs/en.yml | 4 ++-- .../views/administrateurs/groupe_instructeurs/fr.yml | 4 ++-- .../groupe_instructeurs_controller_spec.rb | 4 ++-- spec/mailers/groupe_instructeur_mailer_spec.rb | 8 ++++---- .../previews/groupe_instructeur_mailer_preview.rb | 2 +- 13 files changed, 38 insertions(+), 38 deletions(-) create mode 100644 app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml create mode 100644 app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml delete mode 100644 app/views/groupe_instructeur_mailer/remove_instructeur.html.haml delete mode 100644 app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 35e89221f..1842174d8 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -148,11 +148,11 @@ module Administrateurs if groupe_instructeur.remove(instructeur) flash[:notice] = if procedure.routing_enabled? GroupeInstructeurMailer - .remove_instructeur(groupe_instructeur, [instructeur], current_administrateur.email) + .notify_removed_instructeurs(groupe_instructeur, [instructeur], current_administrateur.email) .deliver_later GroupeInstructeurMailer - .remove_instructeurs(groupe_instructeur, [instructeur], current_administrateur.email) + .notify_group_when_instructeurs_removed(groupe_instructeur, [instructeur], current_administrateur.email) .deliver_later "L’instructeur « #{instructeur.email} » a été retiré du groupe." diff --git a/app/controllers/instructeurs/groupe_instructeurs_controller.rb b/app/controllers/instructeurs/groupe_instructeurs_controller.rb index 20519c8af..cfa180c22 100644 --- a/app/controllers/instructeurs/groupe_instructeurs_controller.rb +++ b/app/controllers/instructeurs/groupe_instructeurs_controller.rb @@ -35,11 +35,11 @@ module Instructeurs if groupe_instructeur.remove(instructeur) flash[:notice] = "L’instructeur « #{instructeur.email} » a été retiré du groupe." GroupeInstructeurMailer - .remove_instructeur(groupe_instructeur, [instructeur], current_administrateur.email) + .notify_removed_instructeurs(groupe_instructeur, [instructeur], current_user.email) .deliver_later GroupeInstructeurMailer - .remove_instructeurs(groupe_instructeur, [instructeur], current_user.email) + .notify_group_when_instructeurs_removed(groupe_instructeur, [instructeur], current_user.email) .deliver_later else flash[:alert] = "L’instructeur « #{instructeur.email} » n’est pas dans le groupe." diff --git a/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb b/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb index f8c14542a..ba41a870b 100644 --- a/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb +++ b/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb @@ -17,11 +17,11 @@ module Mutations if groupe_instructeur.procedure.routing_enabled? && instructeurs.present? GroupeInstructeurMailer - .remove_instructeur(groupe_instructeur, instructeurs, current_administrateur.email) + .notify_removed_instructeurs(groupe_instructeur, instructeurs, current_administrateur.email) .deliver_later GroupeInstructeurMailer - .remove_instructeurs(groupe_instructeur, instructeurs, current_administrateur.email) + .notify_group_when_instructeurs_removed(groupe_instructeur, instructeurs, current_administrateur.email) .deliver_later end diff --git a/app/mailers/groupe_instructeur_mailer.rb b/app/mailers/groupe_instructeur_mailer.rb index 9674faae2..821b41c03 100644 --- a/app/mailers/groupe_instructeur_mailer.rb +++ b/app/mailers/groupe_instructeur_mailer.rb @@ -1,7 +1,7 @@ class GroupeInstructeurMailer < ApplicationMailer layout 'mailers/layout' - def remove_instructeurs(group, removed_instructeurs, current_instructeur_email) + def notify_group_when_instructeurs_removed(group, removed_instructeurs, current_instructeur_email) @removed_instructeur_emails = removed_instructeurs.map(&:email) @group = group @current_instructeur_email = current_instructeur_email @@ -12,12 +12,12 @@ class GroupeInstructeurMailer < ApplicationMailer mail(bcc: emails, subject: subject) end - def remove_instructeur(group, removed_instructeurs, current_instructeur_email) + def notify_removed_instructeurs(group, removed_instructeurs, current_instructeur_email) removed_instructeur_emails = removed_instructeurs.map(&:email) @group = group @current_instructeur_email = current_instructeur_email - subject = "Retrait du groupe \"#{group.label}\"" + subject = "Vous avez été retiré du groupe \"#{group.label}\" de la démarche \"#{group.procedure.libelle}\"" mail(bcc: removed_instructeur_emails, subject: subject) end diff --git a/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml b/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml new file mode 100644 index 000000000..50ba415ef --- /dev/null +++ b/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml @@ -0,0 +1,11 @@ +%p + Bonjour, + +%p + = t('administrateurs.groupe_instructeurs.notify_group_when_instructeurs_removed.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + +%p + Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : + = link_to(@group.label, admin_procedure_groupe_instructeur_url(@group.procedure, @group)) + += render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml new file mode 100644 index 000000000..0ed0e9e49 --- /dev/null +++ b/app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml @@ -0,0 +1,7 @@ +%p + Bonjour, + +%p + = t('administrateurs.groupe_instructeurs.notify_removed_instructeurs.email_body', groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + += render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml b/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml deleted file mode 100644 index d541c0fd9..000000000 --- a/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%p - Bonjour, - -%p - = t('administrateurs.groupe_instructeurs.remove_instructeur.email_body', groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) - -= render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml deleted file mode 100644 index 1d3b218da..000000000 --- a/app/views/groupe_instructeur_mailer/remove_instructeurs.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -%p - Bonjour, - -%p - = t('administrateurs.groupe_instructeurs.remove_instructeurs.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) - -%p - Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : - = link_to(@group.label, admin_procedure_groupe_instructeur_url(@group.procedure, @group)) - -= render partial: "layouts/mailers/signature" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/en.yml b/config/locales/views/administrateurs/groupe_instructeurs/en.yml index 7564938d2..49974b871 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/en.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/en.yml @@ -19,11 +19,11 @@ en: email_body: one: "The instructor %{emails} was assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." other: "The instructors %{emails} were assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - remove_instructeurs: + notify_group_when_instructeurs_removed: email_body: one: "The instructor %{emails} was removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." other: "The instructors %{emails} were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - remove_instructeur: + notify_removed_instructeurs: email_body: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." reaffecter_dossiers: existing_groupe: diff --git a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml index f5f978ecf..07fa50d4e 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml @@ -25,11 +25,11 @@ fr: email_body: one: "L’instructeur %{emails} a été affecté au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." other: "Les instructeurs %{emails} ont été affectés au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - remove_instructeurs: + notify_group_when_instructeurs_removed: email_body: one: "L’instructeur %{emails} a été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." other: "Les instructeurs %{emails} ont été retirés du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - remove_instructeur: + notify_removed_instructeurs: email_body: "Vous avez été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." reaffecter_dossiers: existing_groupe: diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index 3835696d2..6a585d51f 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -321,7 +321,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do context 'when there are many instructeurs' do before do - allow(GroupeInstructeurMailer).to receive(:remove_instructeur) + allow(GroupeInstructeurMailer).to receive(:notify_removed_instructeurs) .and_return(double(deliver_later: true)) remove_instructeur(admin.instructeur) end @@ -330,7 +330,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do it { expect(gi_1_1.reload.instructeurs.count).to eq(1) } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_1)) } it "calls GroupeInstructeurMailer with the right groupe and instructeur" do - expect(GroupeInstructeurMailer).to have_received(:remove_instructeur).with( + expect(GroupeInstructeurMailer).to have_received(:notify_removed_instructeurs).with( gi_1_1, [admin.instructeur], admin.email diff --git a/spec/mailers/groupe_instructeur_mailer_spec.rb b/spec/mailers/groupe_instructeur_mailer_spec.rb index 94b8b06ff..424620b04 100644 --- a/spec/mailers/groupe_instructeur_mailer_spec.rb +++ b/spec/mailers/groupe_instructeur_mailer_spec.rb @@ -1,5 +1,5 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do - describe '#remove_instructeurs' do + describe '#notify_group_when_instructeurs_removed' do let(:groupe_instructeur) do gi = GroupeInstructeur.create(label: 'gi1', procedure: create(:procedure)) gi.instructeurs << create(:instructeur, email: 'int1@g') @@ -13,13 +13,13 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do let(:instructeurs_to_remove) { [instructeur_3, instructeur_4] } let(:current_instructeur_email) { 'toto@email.com' } - subject { described_class.remove_instructeurs(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } + subject { described_class.notify_group_when_instructeurs_removed(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } it { expect(subject.body).to include('Les instructeurs int3@g, int4@g ont été retirés du groupe') } it { expect(subject.bcc).to include('int1@g', 'int2@g') } end - describe '#remove_instructeur' do + describe '#notify_removed_instructeurs' do let(:groupe_instructeur) do gi = GroupeInstructeur.create(label: 'gi1', procedure: create(:procedure)) gi.instructeurs << create(:instructeur, email: 'int1@g') @@ -33,7 +33,7 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do let(:instructeurs_to_remove) { [instructeur_3, instructeur_4] } let(:current_instructeur_email) { 'toto@email.com' } - subject { described_class.remove_instructeur(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } + subject { described_class.notify_removed_instructeurs(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } it { expect(subject.body).to include('ous avez été retiré du groupe « gi1 » par « toto@email.com »') } it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) } diff --git a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb index 5a5f7ca30..a099a206a 100644 --- a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb +++ b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb @@ -4,7 +4,7 @@ class GroupeInstructeurMailerPreview < ActionMailer::Preview groupe = GroupeInstructeur.new(id: 1, label: 'Val-De-Marne', procedure:) current_instructeur_email = 'admin@dgfip.com' instructeurs = Instructeur.limit(2) - GroupeInstructeurMailer.remove_instructeurs(groupe, instructeurs, current_instructeur_email) + GroupeInstructeurMailer.notify_group_when_instructeurs_removed(groupe, instructeurs, current_instructeur_email) end def remove_instructeur From a46faf8cdf07f6a376427f5016cae12399f74919 Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Wed, 15 Feb 2023 11:57:35 +0100 Subject: [PATCH 059/202] feat(groupe instructeur mailer): make two kind of notifications for removed instructeur --- .../groupe_instructeurs_controller.rb | 17 ++++---- .../groupe_instructeurs_controller.rb | 2 +- ...oupe_instructeur_supprimer_instructeurs.rb | 4 -- app/mailers/groupe_instructeur_mailer.rb | 13 ++++--- .../notify_removed_instructeur.html.haml | 8 ++++ .../notify_removed_instructeurs.html.haml | 7 ---- .../groupe_instructeurs/en.yml | 7 +++- .../groupe_instructeurs/fr.yml | 7 +++- .../groupe_instructeurs_controller_spec.rb | 6 +-- .../mailers/groupe_instructeur_mailer_spec.rb | 39 +++++++++++++------ 10 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml delete mode 100644 app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 1842174d8..6ac2e3205 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -146,19 +146,18 @@ module Administrateurs instructeur = groupe_instructeur.instructeurs.find_by(id: instructeur_id) if groupe_instructeur.remove(instructeur) - flash[:notice] = if procedure.routing_enabled? - GroupeInstructeurMailer - .notify_removed_instructeurs(groupe_instructeur, [instructeur], current_administrateur.email) - .deliver_later - - GroupeInstructeurMailer - .notify_group_when_instructeurs_removed(groupe_instructeur, [instructeur], current_administrateur.email) - .deliver_later - + flash[:notice] = if instructeur.in?(procedure.instructeurs) "L’instructeur « #{instructeur.email} » a été retiré du groupe." else "L’instructeur a bien été désaffecté de la démarche" end + GroupeInstructeurMailer + .notify_removed_instructeur(groupe_instructeur, instructeur, current_administrateur.email) + .deliver_later + + GroupeInstructeurMailer + .notify_group_when_instructeurs_removed(groupe_instructeur, [instructeur], current_administrateur.email) + .deliver_later else flash[:alert] = if procedure.routing_enabled? if instructeur.present? diff --git a/app/controllers/instructeurs/groupe_instructeurs_controller.rb b/app/controllers/instructeurs/groupe_instructeurs_controller.rb index cfa180c22..631b37afc 100644 --- a/app/controllers/instructeurs/groupe_instructeurs_controller.rb +++ b/app/controllers/instructeurs/groupe_instructeurs_controller.rb @@ -35,7 +35,7 @@ module Instructeurs if groupe_instructeur.remove(instructeur) flash[:notice] = "L’instructeur « #{instructeur.email} » a été retiré du groupe." GroupeInstructeurMailer - .notify_removed_instructeurs(groupe_instructeur, [instructeur], current_user.email) + .notify_removed_instructeur(groupe_instructeur, instructeur, current_user.email) .deliver_later GroupeInstructeurMailer diff --git a/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb b/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb index ba41a870b..0c1b1617d 100644 --- a/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb +++ b/app/graphql/mutations/groupe_instructeur_supprimer_instructeurs.rb @@ -16,10 +16,6 @@ module Mutations groupe_instructeur.reload if groupe_instructeur.procedure.routing_enabled? && instructeurs.present? - GroupeInstructeurMailer - .notify_removed_instructeurs(groupe_instructeur, instructeurs, current_administrateur.email) - .deliver_later - GroupeInstructeurMailer .notify_group_when_instructeurs_removed(groupe_instructeur, instructeurs, current_administrateur.email) .deliver_later diff --git a/app/mailers/groupe_instructeur_mailer.rb b/app/mailers/groupe_instructeur_mailer.rb index 821b41c03..fa438399d 100644 --- a/app/mailers/groupe_instructeur_mailer.rb +++ b/app/mailers/groupe_instructeur_mailer.rb @@ -12,13 +12,16 @@ class GroupeInstructeurMailer < ApplicationMailer mail(bcc: emails, subject: subject) end - def notify_removed_instructeurs(group, removed_instructeurs, current_instructeur_email) - removed_instructeur_emails = removed_instructeurs.map(&:email) + def notify_removed_instructeur(group, removed_instructeur, current_instructeur_email) @group = group @current_instructeur_email = current_instructeur_email + @still_assigned_to_procedure = removed_instructeur.in?(group.procedure.instructeurs) + subject = if @still_assigned_to_procedure + "Vous avez été retiré du groupe \"#{group.label}\" de la démarche \"#{group.procedure.libelle}\"" + else + "Vous avez été désaffecté de la démarche \"#{group.procedure.libelle}\"" + end - subject = "Vous avez été retiré du groupe \"#{group.label}\" de la démarche \"#{group.procedure.libelle}\"" - - mail(bcc: removed_instructeur_emails, subject: subject) + mail(to: removed_instructeur.email, subject: subject) end end diff --git a/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml b/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml new file mode 100644 index 000000000..e093dadcd --- /dev/null +++ b/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml @@ -0,0 +1,8 @@ +%p + Bonjour, + +%p + - assignment_state = @still_assigned_to_procedure ? 'assigned' : 'unassigned' + = t("administrateurs.groupe_instructeurs.notify_removed_instructeur.#{assignment_state}.email_body", groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + += render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml deleted file mode 100644 index 0ed0e9e49..000000000 --- a/app/views/groupe_instructeur_mailer/notify_removed_instructeurs.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%p - Bonjour, - -%p - = t('administrateurs.groupe_instructeurs.notify_removed_instructeurs.email_body', groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) - -= render partial: "layouts/mailers/signature" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/en.yml b/config/locales/views/administrateurs/groupe_instructeurs/en.yml index 49974b871..49b54a6db 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/en.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/en.yml @@ -23,8 +23,11 @@ en: email_body: one: "The instructor %{emails} was removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." other: "The instructors %{emails} were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - notify_removed_instructeurs: - email_body: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." + notify_removed_instructeur: + unassigned: + email_body: "You were unassigned from the procedure « %{procedure} » by « %{email} »." + assigned: + email_body: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} group exist" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml index 07fa50d4e..7ebd44c7d 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml @@ -29,8 +29,11 @@ fr: email_body: one: "L’instructeur %{emails} a été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." other: "Les instructeurs %{emails} ont été retirés du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - notify_removed_instructeurs: - email_body: "Vous avez été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." + notify_removed_instructeur: + assigned: + email_body: "Vous avez été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." + unassigned: + email_body: "Vous avez été désaffecté de la démarche « %{procedure} » par « %{email} »." reaffecter_dossiers: existing_groupe: one: "%{count} groupe existe" diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index 6a585d51f..384a24756 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -321,7 +321,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do context 'when there are many instructeurs' do before do - allow(GroupeInstructeurMailer).to receive(:notify_removed_instructeurs) + allow(GroupeInstructeurMailer).to receive(:notify_removed_instructeur) .and_return(double(deliver_later: true)) remove_instructeur(admin.instructeur) end @@ -330,9 +330,9 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do it { expect(gi_1_1.reload.instructeurs.count).to eq(1) } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_1)) } it "calls GroupeInstructeurMailer with the right groupe and instructeur" do - expect(GroupeInstructeurMailer).to have_received(:notify_removed_instructeurs).with( + expect(GroupeInstructeurMailer).to have_received(:notify_removed_instructeur).with( gi_1_1, - [admin.instructeur], + admin.instructeur, admin.email ) end diff --git a/spec/mailers/groupe_instructeur_mailer_spec.rb b/spec/mailers/groupe_instructeur_mailer_spec.rb index 424620b04..6cf1ed75e 100644 --- a/spec/mailers/groupe_instructeur_mailer_spec.rb +++ b/spec/mailers/groupe_instructeur_mailer_spec.rb @@ -15,28 +15,45 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do subject { described_class.notify_group_when_instructeurs_removed(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } + before { instructeurs_to_remove.each { groupe_instructeur.remove(_1) } } + it { expect(subject.body).to include('Les instructeurs int3@g, int4@g ont été retirés du groupe') } - it { expect(subject.bcc).to include('int1@g', 'int2@g') } + it { expect(subject.bcc).to match_array(['int1@g', 'int2@g']) } end - describe '#notify_removed_instructeurs' do + describe '#notify_removed_instructeur' do + let(:procedure) { create(:procedure) } let(:groupe_instructeur) do - gi = GroupeInstructeur.create(label: 'gi1', procedure: create(:procedure)) + gi = GroupeInstructeur.create(label: 'gi1', procedure: procedure) gi.instructeurs << create(:instructeur, email: 'int1@g') gi.instructeurs << create(:instructeur, email: 'int2@g') - gi.instructeurs << instructeurs_to_remove + gi.instructeurs << instructeur_to_remove gi end - let(:instructeur_3) { create(:instructeur, email: 'int3@g') } - let(:instructeur_4) { create(:instructeur, email: 'int4@g') } + let(:instructeur_to_remove) { create(:instructeur, email: 'int3@g') } - let(:instructeurs_to_remove) { [instructeur_3, instructeur_4] } let(:current_instructeur_email) { 'toto@email.com' } - subject { described_class.notify_removed_instructeurs(groupe_instructeur, instructeurs_to_remove, current_instructeur_email) } + subject { described_class.notify_removed_instructeur(groupe_instructeur, instructeur_to_remove, current_instructeur_email) } - it { expect(subject.body).to include('ous avez été retiré du groupe « gi1 » par « toto@email.com »') } - it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) } - it { expect(subject.bcc).not_to match_array(['int1@g', 'int2@g']) } + before { groupe_instructeur.remove(instructeur_to_remove) } + + context 'when instructeur is fully removed form procedure' do + it { expect(subject.body).to include('Vous avez été désaffecté de la démarche') } + it { expect(subject.to).to include('int3@g') } + it { expect(subject.to).not_to include('int1@g', 'int2@g') } + end + + context 'when instructeur is removed from one group but still affected to procedure' do + let!(:groupe_instructeur_2) do + gi2 = GroupeInstructeur.create(label: 'gi2', procedure: procedure) + gi2.instructeurs << instructeur_to_remove + gi2 + end + + it { expect(subject.body).to include('Vous avez été retiré du groupe « gi1 » par « toto@email.com »') } + it { expect(subject.to).to include('int3@g') } + it { expect(subject.to).not_to include('int1@g', 'int2@g') } + end end end From 4926fbff19e08b93bdcb7a0959b044bb73926b05 Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Wed, 15 Feb 2023 13:59:10 +0100 Subject: [PATCH 060/202] fix(groupe instructeur mailer): fix mailers preview --- .../mailers/previews/groupe_instructeur_mailer_preview.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb index a099a206a..4eab9b7d7 100644 --- a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb +++ b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb @@ -1,5 +1,5 @@ class GroupeInstructeurMailerPreview < ActionMailer::Preview - def remove_instructeurs + def notify_group_when_instructeurs_removed procedure = Procedure.new(id: 1, libelle: 'une superbe procedure') groupe = GroupeInstructeur.new(id: 1, label: 'Val-De-Marne', procedure:) current_instructeur_email = 'admin@dgfip.com' @@ -7,11 +7,11 @@ class GroupeInstructeurMailerPreview < ActionMailer::Preview GroupeInstructeurMailer.notify_group_when_instructeurs_removed(groupe, instructeurs, current_instructeur_email) end - def remove_instructeur + def notify_removed_instructeur procedure = Procedure.new(id: 1, libelle: 'une superbe procedure') groupe = GroupeInstructeur.new(id: 1, label: 'Val-De-Marne', procedure:) current_instructeur_email = 'admin@dgfip.com' - instructeurs = Instructeur.limit(2) - GroupeInstructeurMailer.remove_instructeur(groupe, instructeurs, current_instructeur_email) + instructeur = Instructeur.last + GroupeInstructeurMailer.notify_removed_instructeur(groupe, instructeur, current_instructeur_email) end end From cbe2dc9c2de7c69fcb3c9f6a711137aae986235c Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 23 Jan 2023 14:28:39 +0100 Subject: [PATCH 061/202] let the multiple drop down list be prefillable --- app/models/type_de_champ.rb | 3 ++- spec/models/prefill_params_spec.rb | 6 ++++-- spec/models/type_de_champ_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 0d9692fdc..354711ef2 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -262,13 +262,14 @@ class TypeDeChamp < ApplicationRecord TypeDeChamp.type_champs.fetch(:iban), TypeDeChamp.type_champs.fetch(:civilite), TypeDeChamp.type_champs.fetch(:pays), + TypeDeChamp.type_champs.fetch(:regions), TypeDeChamp.type_champs.fetch(:date), TypeDeChamp.type_champs.fetch(:datetime), TypeDeChamp.type_champs.fetch(:yes_no), TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:drop_down_list), - TypeDeChamp.type_champs.fetch(:regions), TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:multiple_drop_down_list), TypeDeChamp.type_champs.fetch(:epci) ]) end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index b7e03b27b..ed3cfa001 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -128,6 +128,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is authorized", :iban, "value" it_behaves_like "a champ public value that is authorized", :civilite, "M." it_behaves_like "a champ public value that is authorized", :pays, "FR" + it_behaves_like "a champ public value that is authorized", :regions, "03" it_behaves_like "a champ public value that is authorized", :date, "2022-12-22" it_behaves_like "a champ public value that is authorized", :datetime, "2022-12-22T10:30" it_behaves_like "a champ public value that is authorized", :yes_no, "true" @@ -135,8 +136,8 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is authorized", :checkbox, "true" it_behaves_like "a champ public value that is authorized", :checkbox, "false" it_behaves_like "a champ public value that is authorized", :drop_down_list, "value" - it_behaves_like "a champ public value that is authorized", :regions, "03" it_behaves_like "a champ public value that is authorized", :departements, "03" + it_behaves_like "a champ public value that is authorized", :multiple_drop_down_list, ["val1", "val2"] it_behaves_like "a champ public value that is authorized", :epci, ['01', '200042935'] it_behaves_like "a champ private value that is authorized", :text, "value" @@ -148,6 +149,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :iban, "value" it_behaves_like "a champ private value that is authorized", :civilite, "M." it_behaves_like "a champ private value that is authorized", :pays, "FR" + it_behaves_like "a champ private value that is authorized", :regions, "93" it_behaves_like "a champ private value that is authorized", :date, "2022-12-22" it_behaves_like "a champ private value that is authorized", :datetime, "2022-12-22T10:30" it_behaves_like "a champ private value that is authorized", :yes_no, "true" @@ -157,6 +159,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ private value that is authorized", :drop_down_list, "value" it_behaves_like "a champ private value that is authorized", :regions, "93" it_behaves_like "a champ private value that is authorized", :departements, "03" + it_behaves_like "a champ private value that is authorized", :multiple_drop_down_list, ["val1", "val2"] it_behaves_like "a champ private value that is authorized", :epci, ['01', '200042935'] it_behaves_like "a champ public value that is unauthorized", :decimal_number, "non decimal string" @@ -169,7 +172,6 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is unauthorized", :date, "value" it_behaves_like "a champ public value that is unauthorized", :datetime, "value" it_behaves_like "a champ public value that is unauthorized", :datetime, "12-22-2022T10:30" - it_behaves_like "a champ public value that is unauthorized", :multiple_drop_down_list, "value" it_behaves_like "a champ public value that is unauthorized", :linked_drop_down_list, "value" it_behaves_like "a champ public value that is unauthorized", :header_section, "value" it_behaves_like "a champ public value that is unauthorized", :explication, "value" diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index cbd34f501..785cf9e8f 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -246,18 +246,18 @@ describe TypeDeChamp do it_behaves_like "a prefillable type de champ", :type_de_champ_datetime it_behaves_like "a prefillable type de champ", :type_de_champ_civilite it_behaves_like "a prefillable type de champ", :type_de_champ_pays + it_behaves_like "a prefillable type de champ", :type_de_champ_regions it_behaves_like "a prefillable type de champ", :type_de_champ_yes_no it_behaves_like "a prefillable type de champ", :type_de_champ_checkbox it_behaves_like "a prefillable type de champ", :type_de_champ_drop_down_list - it_behaves_like "a prefillable type de champ", :type_de_champ_regions it_behaves_like "a prefillable type de champ", :type_de_champ_departements + it_behaves_like "a prefillable type de champ", :type_de_champ_multiple_drop_down_list it_behaves_like "a prefillable type de champ", :type_de_champ_epci it_behaves_like "a non-prefillable type de champ", :type_de_champ_number it_behaves_like "a non-prefillable type de champ", :type_de_champ_communes it_behaves_like "a non-prefillable type de champ", :type_de_champ_dossier_link it_behaves_like "a non-prefillable type de champ", :type_de_champ_titre_identite - it_behaves_like "a non-prefillable type de champ", :type_de_champ_multiple_drop_down_list it_behaves_like "a non-prefillable type de champ", :type_de_champ_linked_drop_down_list it_behaves_like "a non-prefillable type de champ", :type_de_champ_header_section it_behaves_like "a non-prefillable type de champ", :type_de_champ_explication From d648ac31c2d25b7d18b2421c6e87583a263b908b Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Mon, 23 Jan 2023 14:14:44 +0100 Subject: [PATCH 062/202] add prefill decorator for multiple drop down list --- ...l_multiple_drop_down_list_type_de_champ.rb | 8 +++++ .../types_de_champ/prefill_type_de_champ.rb | 2 ++ ...tiple_drop_down_list_type_de_champ_spec.rb | 32 +++++++++++++++++++ .../prefill_type_de_champ_spec.rb | 6 ++++ 4 files changed, 48 insertions(+) create mode 100644 app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb create mode 100644 spec/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ_spec.rb diff --git a/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb new file mode 100644 index 000000000..11992f53d --- /dev/null +++ b/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb @@ -0,0 +1,8 @@ +class TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp < TypesDeChamp::PrefillDropDownListTypeDeChamp + def example_value + return nil if possible_values.empty? + return possible_values.first if possible_values.one? + + [possible_values.first, possible_values.second] + end +end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index a385ea653..1dc57e46f 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -5,6 +5,8 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator case type_de_champ.type_champ when TypeDeChamp.type_champs.fetch(:drop_down_list) TypesDeChamp::PrefillDropDownListTypeDeChamp.new(type_de_champ) + when TypeDeChamp.type_champs.fetch(:multiple_drop_down_list) + TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ) when TypeDeChamp.type_champs.fetch(:pays) TypesDeChamp::PrefillPaysTypeDeChamp.new(type_de_champ) when TypeDeChamp.type_champs.fetch(:regions) diff --git a/spec/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ_spec.rb new file mode 100644 index 000000000..d44fd1274 --- /dev/null +++ b/spec/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +RSpec.describe TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp do + describe 'ancestors' do + subject { described_class.new(build(:type_de_champ_multiple_drop_down_list)) } + + it { is_expected.to be_kind_of(TypesDeChamp::PrefillDropDownListTypeDeChamp) } + end + + describe '#example_value' do + let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, drop_down_list_value: drop_down_list_value) } + subject(:example_value) { described_class.new(type_de_champ).example_value } + + context 'when the multiple drop down list has no option' do + let(:drop_down_list_value) { "" } + + it { expect(example_value).to eq(nil) } + end + + context 'when the multiple drop down list only has one option' do + let(:drop_down_list_value) { "value" } + + it { expect(example_value).to eq("value") } + end + + context 'when the multiple drop down list has two options or more' do + let(:drop_down_list_value) { "value1\r\nvalue2\r\nvalue3" } + + it { expect(example_value).to eq(["value1", "value2"]) } + end + end +end diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 7477b3789..3f892f907 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -10,6 +10,12 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { expect(built).to be_kind_of(TypesDeChamp::PrefillDropDownListTypeDeChamp) } end + context 'when the type de champ is a multiple_drop_down_list' do + let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list) } + + it { expect(built).to be_kind_of(TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp) } + end + context 'when the type de champ is a pays' do let(:type_de_champ) { build(:type_de_champ_pays) } From d5ffd61ab65b3be8deb98dc39de5ca3bf36b421c Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 24 Jan 2023 15:10:25 +0100 Subject: [PATCH 063/202] validate values inclusion --- .../champs/multiple_drop_down_list_champ.rb | 9 +++++ app/models/prefill_params.rb | 1 + config/locales/en.yml | 4 ++ config/locales/fr.yml | 4 ++ .../instructeurs/dossiers_controller_spec.rb | 6 +-- spec/factories/champ.rb | 2 +- spec/models/champ_spec.rb | 6 +-- .../multiple_drop_down_list_champ_spec.rb | 38 +++++++++++++++++++ spec/models/prefill_params_spec.rb | 1 + 9 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 spec/models/champs/multiple_drop_down_list_champ_spec.rb diff --git a/app/models/champs/multiple_drop_down_list_champ.rb b/app/models/champs/multiple_drop_down_list_champ.rb index 365332e30..29ce06dcf 100644 --- a/app/models/champs/multiple_drop_down_list_champ.rb +++ b/app/models/champs/multiple_drop_down_list_champ.rb @@ -23,6 +23,8 @@ class Champs::MultipleDropDownListChamp < Champ before_save :format_before_save + validate :values_are_in_options, if: -> { value.present? } + def options? drop_down_list_options? end @@ -90,4 +92,11 @@ class Champs::MultipleDropDownListChamp < Champ end end end + + def values_are_in_options + return if (json = JSON.parse(value) - ['']).empty? + return if json.filter { |val| enabled_non_empty_options.exclude?(val) }.empty? + + errors.add(:value, :not_in_options) + end end diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index f0841039a..4af3e8532 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -42,6 +42,7 @@ class PrefillParams TypeDeChamp.type_champs.fetch(:pays), TypeDeChamp.type_champs.fetch(:regions), TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:multiple_drop_down_list), TypeDeChamp.type_champs.fetch(:epci) ] diff --git a/config/locales/en.yml b/config/locales/en.yml index fad29d059..e39221915 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -471,6 +471,10 @@ en: attributes: value: not_in_options: "must be in the given options" + "champs/multiple_drop_down_list_champ": + attributes: + value: + not_in_options: "must be in the given options" "champs/region_champ": attributes: value: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b1492cc79..ef2e6ab19 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -466,6 +466,10 @@ fr: attributes: value: not_in_options: "doit être dans les options proposées" + "champs/multiple_drop_down_list_champ": + attributes: + value: + not_in_options: "doit être dans les options proposées" "champs/region_champ": attributes: value: diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 990b3a29c..b6e1af036 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -792,7 +792,7 @@ describe Instructeurs::DossiersController, type: :controller do champs_private_attributes: { '0': { id: champ_multiple_drop_down_list.id, - value: ['', 'un', 'deux'] + value: ['', 'val1', 'val2'] }, '1': { id: champ_datetime.id, @@ -813,7 +813,7 @@ describe Instructeurs::DossiersController, type: :controller do end it { - expect(champ_multiple_drop_down_list.value).to eq('["un", "deux"]') + expect(champ_multiple_drop_down_list.value).to eq('["val1", "val2"]') expect(champ_linked_drop_down_list.primary_value).to eq('primary') expect(champ_linked_drop_down_list.secondary_value).to eq('secondary') expect(champ_datetime.value).to eq('2019-12-21T13:17:00+01:00') @@ -839,7 +839,7 @@ describe Instructeurs::DossiersController, type: :controller do champs_public_attributes: { '0': { id: champ_multiple_drop_down_list.id, - value: ['', 'un', 'deux'] + value: ['', 'val1', 'val2'] } } } diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index 253247a8e..d1f4448f8 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -97,7 +97,7 @@ FactoryBot.define do factory :champ_multiple_drop_down_list, class: 'Champs::MultipleDropDownListChamp' do type_de_champ { association :type_de_champ_multiple_drop_down_list, procedure: dossier.procedure } - value { '["choix 1", "choix 2"]' } + value { '["val1", "val2"]' } end factory :champ_linked_drop_down_list, class: 'Champs::LinkedDropDownListChamp' do diff --git a/spec/models/champ_spec.rb b/spec/models/champ_spec.rb index bec9c3097..7cad128a7 100644 --- a/spec/models/champ_spec.rb +++ b/spec/models/champ_spec.rb @@ -117,7 +117,7 @@ describe Champ do # when using the old form, and the ChampsService Class # TODO: to remove context 'when the value is already deserialized' do - let(:value) { '["1", "2"]' } + let(:value) { '["val1", "val2"]' } it { expect(champ.value).to eq(value) } @@ -133,9 +133,9 @@ describe Champ do # GOTCHA context 'when the value is not already deserialized' do context 'when a choice is selected' do - let(:value) { '["", "1", "2"]' } + let(:value) { '["", "val1", "val2"]' } - it { expect(champ.value).to eq('["1", "2"]') } + it { expect(champ.value).to eq('["val1", "val2"]') } end context 'when all choices are removed' do diff --git a/spec/models/champs/multiple_drop_down_list_champ_spec.rb b/spec/models/champs/multiple_drop_down_list_champ_spec.rb new file mode 100644 index 000000000..08a78c77c --- /dev/null +++ b/spec/models/champs/multiple_drop_down_list_champ_spec.rb @@ -0,0 +1,38 @@ +describe Champs::MultipleDropDownListChamp do + describe 'validations' do + describe 'inclusion' do + let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, drop_down_list_value: "val1\r\nval2\r\nval3") } + subject { build(:champ_multiple_drop_down_list, type_de_champ:, value:) } + + context 'when the value is nil' do + let(:value) { nil } + + it { is_expected.to be_valid } + end + + context 'when the value is an empty string' do + let(:value) { '' } + + it { is_expected.to be_valid } + end + + context 'when the value is an empty array' do + let(:value) { [] } + + it { is_expected.to be_valid } + end + + context 'when the value is included in the option list' do + let(:value) { ["val3", "val1"] } + + it { is_expected.to be_valid } + end + + context 'when the value is not included in the option list' do + let(:value) { ["totoro", "val1"] } + + it { is_expected.not_to be_valid } + end + end + end +end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index ed3cfa001..bf9378ae0 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -189,6 +189,7 @@ RSpec.describe PrefillParams do it_behaves_like "a champ public value that is unauthorized", :siret, "value" it_behaves_like "a champ public value that is unauthorized", :rna, "value" it_behaves_like "a champ public value that is unauthorized", :annuaire_education, "value" + it_behaves_like "a champ public value that is unauthorized", :multiple_drop_down_list, ["value"] end private From a3ca79076ea7908ff45826f89a884cccdbc383b9 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 24 Jan 2023 11:44:02 +0100 Subject: [PATCH 064/202] cover the use case with feature specs --- .../shared_examples_for_prefilled_dossier.rb | 3 ++ spec/system/users/dossier_prefill_get_spec.rb | 36 ++++++++++--------- .../system/users/dossier_prefill_post_spec.rb | 8 +++++ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/spec/support/shared_examples_for_prefilled_dossier.rb b/spec/support/shared_examples_for_prefilled_dossier.rb index 5ed67c49c..5cd01bd2e 100644 --- a/spec/support/shared_examples_for_prefilled_dossier.rb +++ b/spec/support/shared_examples_for_prefilled_dossier.rb @@ -19,6 +19,9 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do expect(page).to have_field(type_de_champ_phone.libelle, with: phone_value) expect(page).to have_css('label', text: type_de_champ_phone.libelle) expect(page).to have_field(type_de_champ_datetime.libelle, with: datetime_value) + expect(page).to have_css('label', text: type_de_champ_multiple_drop_down_list.libelle) + expect(page).to have_content(multiple_drop_down_list_values.first) + expect(page).to have_content(multiple_drop_down_list_values.last) expect(page).to have_field(type_de_champ_epci.libelle, with: epci_value.last) end end diff --git a/spec/system/users/dossier_prefill_get_spec.rb b/spec/system/users/dossier_prefill_get_spec.rb index 41fc21403..2e0523bfd 100644 --- a/spec/system/users/dossier_prefill_get_spec.rb +++ b/spec/system/users/dossier_prefill_get_spec.rb @@ -9,12 +9,30 @@ describe 'Prefilling a dossier (with a GET request):' do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_phone) { create(:type_de_champ_phone, procedure: procedure) } let(:type_de_champ_datetime) { create(:type_de_champ_datetime, procedure: procedure) } + let(:type_de_champ_multiple_drop_down_list) { create(:type_de_champ_multiple_drop_down_list, procedure: procedure) } let(:type_de_champ_epci) { create(:type_de_champ_epci, procedure: procedure) } let(:text_value) { "My Neighbor Totoro is the best movie ever" } let(:phone_value) { "invalid phone value" } let(:datetime_value) { "2023-02-01T10:32" } + let(:multiple_drop_down_list_values) { + [ + type_de_champ_multiple_drop_down_list.drop_down_list_enabled_non_empty_options.first, + type_de_champ_multiple_drop_down_list.drop_down_list_enabled_non_empty_options.last + ] + } let(:epci_value) { ['01', '200029999'] } + let(:entry_path) { + commencer_path( + path: procedure.path, + "champ_#{type_de_champ_text.to_typed_id}" => text_value, + "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, + "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_multiple_drop_down_list.to_typed_id}" => multiple_drop_down_list_values, + "champ_#{type_de_champ_epci.to_typed_id}" => epci_value + ) + } + before do allow(Rails).to receive(:cache).and_return(memory_store) Rails.cache.clear @@ -36,13 +54,7 @@ describe 'Prefilling a dossier (with a GET request):' do visit "/users/sign_in" sign_in_with user.email, password - visit commencer_path( - path: procedure.path, - "champ_#{type_de_champ_text.to_typed_id}" => text_value, - "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, - "champ_#{type_de_champ_epci.to_typed_id}" => epci_value - ) + visit entry_path click_on "Poursuivre mon dossier prérempli" end @@ -50,15 +62,7 @@ describe 'Prefilling a dossier (with a GET request):' do end context 'when unauthenticated' do - before do - visit commencer_path( - path: procedure.path, - "champ_#{type_de_champ_text.to_typed_id}" => text_value, - "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, - "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, - "champ_#{type_de_champ_epci.to_typed_id}" => epci_value - ) - end + before { visit entry_path } context 'when the user signs in with email and password' do it_behaves_like "the user has got a prefilled dossier, owned by themselves" do diff --git a/spec/system/users/dossier_prefill_post_spec.rb b/spec/system/users/dossier_prefill_post_spec.rb index c0eaa03fe..454919e67 100644 --- a/spec/system/users/dossier_prefill_post_spec.rb +++ b/spec/system/users/dossier_prefill_post_spec.rb @@ -9,10 +9,17 @@ describe 'Prefilling a dossier (with a POST request):' do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_phone) { create(:type_de_champ_phone, procedure: procedure) } let(:type_de_champ_datetime) { create(:type_de_champ_datetime, procedure: procedure) } + let(:type_de_champ_multiple_drop_down_list) { create(:type_de_champ_multiple_drop_down_list, procedure: procedure) } let(:type_de_champ_epci) { create(:type_de_champ_epci, procedure: procedure) } let(:text_value) { "My Neighbor Totoro is the best movie ever" } let(:phone_value) { "invalid phone value" } let(:datetime_value) { "2023-02-01T10:32" } + let(:multiple_drop_down_list_values) { + [ + type_de_champ_multiple_drop_down_list.drop_down_list_enabled_non_empty_options.first, + type_de_champ_multiple_drop_down_list.drop_down_list_enabled_non_empty_options.last + ] + } let(:epci_value) { ['01', '200029999'] } before do @@ -116,6 +123,7 @@ describe 'Prefilling a dossier (with a POST request):' do "champ_#{type_de_champ_text.to_typed_id}" => text_value, "champ_#{type_de_champ_phone.to_typed_id}" => phone_value, "champ_#{type_de_champ_datetime.to_typed_id}" => datetime_value, + "champ_#{type_de_champ_multiple_drop_down_list.to_typed_id}" => multiple_drop_down_list_values, "champ_#{type_de_champ_epci.to_typed_id}" => epci_value }.to_json JSON.parse(session.response.body)["dossier_url"].gsub("http://www.example.com", "") From e344b97d51927c84f17a2ce819e9b6e7258f8301 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Thu, 26 Jan 2023 10:51:54 +0100 Subject: [PATCH 065/202] review: readability --- app/models/champs/multiple_drop_down_list_champ.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/champs/multiple_drop_down_list_champ.rb b/app/models/champs/multiple_drop_down_list_champ.rb index 29ce06dcf..4f14f197b 100644 --- a/app/models/champs/multiple_drop_down_list_champ.rb +++ b/app/models/champs/multiple_drop_down_list_champ.rb @@ -94,8 +94,9 @@ class Champs::MultipleDropDownListChamp < Champ end def values_are_in_options - return if (json = JSON.parse(value) - ['']).empty? - return if json.filter { |val| enabled_non_empty_options.exclude?(val) }.empty? + json = JSON.parse(value).reject(&:blank?) + return if json.empty? + return if (json - enabled_non_empty_options).empty? errors.add(:value, :not_in_options) end From 8c67df71305315d28d55e93c11d928e9ba95ec30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Carceles?= Date: Wed, 15 Feb 2023 12:17:28 +0100 Subject: [PATCH 066/202] review: use selected options Co-authored-by: Paul Chavard --- app/models/champs/multiple_drop_down_list_champ.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/champs/multiple_drop_down_list_champ.rb b/app/models/champs/multiple_drop_down_list_champ.rb index 4f14f197b..4e041fd0b 100644 --- a/app/models/champs/multiple_drop_down_list_champ.rb +++ b/app/models/champs/multiple_drop_down_list_champ.rb @@ -94,7 +94,7 @@ class Champs::MultipleDropDownListChamp < Champ end def values_are_in_options - json = JSON.parse(value).reject(&:blank?) + json = selected_options.reject(&:blank?) return if json.empty? return if (json - enabled_non_empty_options).empty? From c0ad7853cb41fe0430083daf40824aafd3b43e2c Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 9 Feb 2023 14:39:33 +0100 Subject: [PATCH 067/202] fix(administrateur): procedure page n+1 --- .../administrateurs/procedures_controller.rb | 46 +++++++++++++++++-- .../concerns/tags_substitution_concern.rb | 4 +- app/models/procedure.rb | 15 +++--- app/models/procedure_revision.rb | 23 +++++++--- app/validators/tags_validator.rb | 22 ++++----- spec/models/type_de_champ_spec.rb | 2 +- 6 files changed, 83 insertions(+), 29 deletions(-) diff --git a/app/controllers/administrateurs/procedures_controller.rb b/app/controllers/administrateurs/procedures_controller.rb index c206b3724..e81a3a42c 100644 --- a/app/controllers/administrateurs/procedures_controller.rb +++ b/app/controllers/administrateurs/procedures_controller.rb @@ -96,8 +96,20 @@ module Administrateurs @procedure = current_administrateur .procedures .includes( - published_revision: :types_de_champ, - draft_revision: :types_de_champ + published_revision: { + types_de_champ: [], + revision_types_de_champ: { type_de_champ: { piece_justificative_template_attachment: :blob } } + }, + draft_revision: { + types_de_champ: [], + revision_types_de_champ: { type_de_champ: { piece_justificative_template_attachment: :blob } } + }, + attestation_template: [], + initiated_mail: [], + received_mail: [], + closed_mail: [], + refused_mail: [], + without_continuation_mail: [] ) .find(params[:id]) @@ -332,7 +344,35 @@ module Administrateurs end def champs - @procedure = Procedure.includes(draft_revision: { revision_types_de_champ_public: :type_de_champ }).find(@procedure.id) + @procedure = Procedure.includes(draft_revision: { + revision_types_de_champ: { + type_de_champ: { piece_justificative_template_attachment: :blob, revision: [], procedure: [] }, + revision: [], + procedure: [] + }, + revision_types_de_champ_public: { + type_de_champ: { piece_justificative_template_attachment: :blob, revision: [], procedure: [] }, + revision: [], + procedure: [] + }, + procedure: [] + }).find(@procedure.id) + end + + def annotations + @procedure = Procedure.includes(draft_revision: { + revision_types_de_champ: { + type_de_champ: { piece_justificative_template_attachment: :blob, revision: [], procedure: [] }, + revision: [], + procedure: [] + }, + revision_types_de_champ_private: { + type_de_champ: { piece_justificative_template_attachment: :blob, revision: [], procedure: [] }, + revision: [], + procedure: [] + }, + procedure: [] + }).find(@procedure.id) end def detail diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 972882f5b..86f5f1100 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -270,12 +270,12 @@ module TagsSubstitutionConcern end def champ_public_tags(dossier: nil) - types_de_champ = (dossier || procedure.active_revision).types_de_champ_public.not_condition + types_de_champ = (dossier || procedure.active_revision).types_de_champ_public.filter { !_1.condition? } types_de_champ_tags(types_de_champ, Dossier::SOUMIS) end def champ_private_tags(dossier: nil) - types_de_champ = (dossier || procedure.active_revision).types_de_champ_private.not_condition + types_de_champ = (dossier || procedure.active_revision).types_de_champ_private.filter { !_1.condition? } types_de_champ_tags(types_de_champ, Dossier::INSTRUCTION_COMMENCEE) end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index b6ef52750..8e8515bd6 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -173,12 +173,15 @@ class Procedure < ApplicationRecord types_de_champ_for_tags.private_only end - def revision_ids_with_pending_dossiers - dossiers - .where.not(revision_id: [draft_revision_id, published_revision_id].compact) - .state_en_construction_ou_instruction - .distinct(:revision_id) - .pluck(:revision_id) + def revisions_with_pending_dossiers + @revisions_with_pending_dossiers ||= begin + ids = dossiers + .where.not(revision_id: [draft_revision_id, published_revision_id].compact) + .state_en_construction_ou_instruction + .distinct(:revision_id) + .pluck(:revision_id) + ProcedureRevision.includes(revision_types_de_champ: [:type_de_champ]).where(id: ids) + end end has_many :administrateurs_procedures, dependent: :delete_all diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index 7e94d0595..ff3e587b5 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -132,7 +132,7 @@ class ProcedureRevision < ApplicationRecord end def draft? - procedure.draft_revision == self + procedure.draft_revision_id == id end def locked? @@ -172,11 +172,22 @@ class ProcedureRevision < ApplicationRecord end def children_of(tdc) - parent_coordinate_id = revision_types_de_champ.where(type_de_champ: tdc).select(:id) + if revision_types_de_champ.loaded? + parent_coordinate_id = revision_types_de_champ + .filter { _1.type_de_champ_id == tdc.id } + .map(&:id) - types_de_champ - .where(procedure_revision_types_de_champ: { parent_id: parent_coordinate_id }) - .order("procedure_revision_types_de_champ.position") + revision_types_de_champ + .filter { _1.parent_id.in?(parent_coordinate_id) } + .sort_by(&:position) + .map(&:type_de_champ) + else + parent_coordinate_id = revision_types_de_champ.where(type_de_champ: tdc).select(:id) + + types_de_champ + .where(procedure_revision_types_de_champ: { parent_id: parent_coordinate_id }) + .order("procedure_revision_types_de_champ.position") + end end def remove_children_of(tdc) @@ -380,7 +391,7 @@ class ProcedureRevision < ApplicationRecord public_tdcs .map.with_index - .filter_map { |tdc, i| tdc.condition.present? ? [tdc, i] : nil } + .filter_map { |tdc, i| tdc.condition? ? [tdc, i] : nil } .map { |tdc, i| [tdc, tdc.condition.errors(public_tdcs.take(i))] } .filter { |_tdc, errors| errors.present? } .each { |tdc, message| errors.add(:condition, message, type_de_champ: tdc) } diff --git a/app/validators/tags_validator.rb b/app/validators/tags_validator.rb index b8f3520aa..4b55c5cf9 100644 --- a/app/validators/tags_validator.rb +++ b/app/validators/tags_validator.rb @@ -7,18 +7,18 @@ class TagsValidator < ActiveModel::EachValidator tag if stable_id.nil? end - invalid_for_draft_revision = invalid_tags_for_revision(record, attribute, tags, procedure.draft_revision_id) + invalid_for_draft_revision = invalid_tags_for_revision(record, attribute, tags, procedure.draft_revision) invalid_for_published_revision = if procedure.published_revision_id.present? - invalid_tags_for_revision(record, attribute, tags, procedure.published_revision_id) + invalid_tags_for_revision(record, attribute, tags, procedure.published_revision) else [] end invalid_for_previous_revision = procedure - .revision_ids_with_pending_dossiers - .flat_map do |revision_id| - invalid_tags_for_revision(record, attribute, tags, revision_id) + .revisions_with_pending_dossiers + .flat_map do |revision| + invalid_tags_for_revision(record, attribute, tags, revision) end.uniq # champ is added in draft revision but not yet published @@ -48,12 +48,12 @@ class TagsValidator < ActiveModel::EachValidator end end - def invalid_tags_for_revision(record, attribute, tags, revision_id) - revision_stable_ids = TypeDeChamp - .joins(:revision_types_de_champ) - .where(procedure_revision_types_de_champ: { revision_id: revision_id, parent_id: nil }) - .distinct(:stable_id) - .pluck(:stable_id) + def invalid_tags_for_revision(record, attribute, tags, revision) + revision_stable_ids = revision + .revision_types_de_champ + .filter { !_1.child? } + .map(&:stable_id) + .uniq tags.filter_map do |(tag, stable_id)| if stable_id.present? && !stable_id.in?(revision_stable_ids) diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index 785cf9e8f..d7f5c5502 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -95,7 +95,7 @@ describe TypeDeChamp do let(:target_type_champ) { TypeDeChamp.type_champs.fetch(:text) } it 'removes the children types de champ' do - expect(procedure.draft_revision.children_of(tdc)).to be_empty + expect(procedure.draft_revision.reload.children_of(tdc)).to be_empty end end end From 0e5deda86c16b2caeb553d4d387742f6868e8fb1 Mon Sep 17 00:00:00 2001 From: Julie Salha Date: Tue, 14 Feb 2023 13:11:00 +0100 Subject: [PATCH 068/202] add scrolling functionnality for description procedure --- app/assets/stylesheets/procedure_context.scss | 2 +- .../shared/_procedure_description.html.haml | 2 +- config/locales/en.yml | 20 +++---------------- config/locales/fr.yml | 20 +++---------------- 4 files changed, 8 insertions(+), 36 deletions(-) diff --git a/app/assets/stylesheets/procedure_context.scss b/app/assets/stylesheets/procedure_context.scss index ee5a96010..e2f220fd3 100644 --- a/app/assets/stylesheets/procedure_context.scss +++ b/app/assets/stylesheets/procedure_context.scss @@ -100,7 +100,7 @@ $procedure-description-line-height: 22px; // If the text exceeds the max-height, // truncate it and displays the "Read more" button. &.read-more-enabled { - overflow: hidden; + overflow: auto; border-bottom: 1px solid $border-grey; + .read-more-button { diff --git a/app/views/shared/_procedure_description.html.haml b/app/views/shared/_procedure_description.html.haml index e10d4607f..6500210d7 100644 --- a/app/views/shared/_procedure_description.html.haml +++ b/app/views/shared/_procedure_description.html.haml @@ -22,6 +22,6 @@ %p Vous pouvez déposer vos dossiers jusqu’au #{procedure_auto_archive_datetime(procedure)}. .procedure-description - .procedure-description-body.read-more-enabled.read-more-collapsed + .procedure-description-body.read-more-enabled.read-more-collapsed{ tabindex: "0", role: "region", "aria-label": t('views.users.dossiers.identite.description') } = h string_to_html(procedure.description, allow_a: true) = button_tag "Afficher la description complète", class: 'button read-more-button' diff --git a/config/locales/en.yml b/config/locales/en.yml index e39221915..dcd81f16a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -68,6 +68,7 @@ en: are_you_new: First time on %{app_name}? my_account: My account header: + label_modal: "Burger menu" close_modal: 'Close' back: "Back" back_title: "Revenir sur le site de mon administration" @@ -92,13 +93,11 @@ en: existing_dossiers: You already have files for this procedure show_dossiers: View my current files prefilled_draft: "You have a prefilled file" - prefilled_draft_detail_html: "You are ready to continue a prefilled file for the \"%{procedure}\" procedure, started %{time_ago} ago." - prefill_dossier_detail_html: "You are ready to continue a prefilled file for the \"%{procedure}\" procedure." + prefilled_draft_detail_html: "You prefilled a file for the \"%{procedure}\" procedure %{time_ago} ago" already_draft: "You already started to fill a file" already_draft_detail_html: "You started to fill a file for the \"%{procedure}\" procedure %{time_ago} ago" already_not_draft: "You already submitted a file" already_not_draft_detail_html: "You submitted a file for the \"%{procedure}\" procedure %{time_ago} ago." - go_to_prefilled_file: 'Continue to fill my prefilled file' continue_file: "Continue to fill my file" start_new_file: "Start a new file" show_my_submitted_file: 'Show my submitted file' @@ -146,7 +145,6 @@ en: iban: FR7611315000011234567890138 yes_no: "true" pays: "FR" - departements: "56" regions: "53" date: "2023-02-01" datetime: "2023-02-01T10:30" @@ -265,6 +263,7 @@ en: identity_data: Identity data all_required: All fields are required. civility: Civility + description: Description of the procedure first_name: First Name last_name: Last Name birthdate: Date de naissance @@ -397,19 +396,6 @@ en: zone: This procedure is run by champs: value: Value - default_mail_attributes: &default_mail_attributes - hints: - subject: The generated subject will be truncated if it exceeds 100 characters. - mails/closed_mail: - << : *default_mail_attributes - mails/initiated_mail: - << : *default_mail_attributes - mails/received_mail: - << : *default_mail_attributes - mails/refused_mail: - << : *default_mail_attributes - mails/without_continuation_mail: - << : *default_mail_attributes errors: messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ef2e6ab19..1fb1e9fa1 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -59,6 +59,7 @@ fr: are_you_new: Vous êtes nouveau sur %{app_name} ? my_account: Mon compte header: + label_modal: "Menu en-tête de page" close_modal: 'Fermer' back: "Revenir en arrière" back_title: "Revenir sur le site de mon administration" @@ -83,13 +84,11 @@ fr: existing_dossiers: Vous avez déjà des dossiers pour cette démarche show_dossiers: Voir mes dossiers en cours prefilled_draft: "Vous avez un dossier prérempli" - prefilled_draft_detail_html: "Vous êtes prêt·e à poursuivre un dossier prérempli sur la démarche « %{procedure} », commencé il y a %{time_ago}." - prefill_dossier_detail_html: "Vous êtes prêt·e à poursuivre un dossier prérempli sur la démarche « %{procedure} »." + prefilled_draft_detail_html: "Il y a %{time_ago}, vous avez prérempli un dossier sur la démarche « %{procedure} »." already_draft: "Vous avez déjà commencé à remplir un dossier" already_draft_detail_html: "Il y a %{time_ago}, vous avez commencé à remplir un dossier sur la démarche « %{procedure} »." already_not_draft: "Vous avez déjà déposé un dossier" already_not_draft_detail_html: "Il y a %{time_ago}, vous avez déposé un dossier sur la démarche « %{procedure} »." - go_to_prefilled_file: 'Poursuivre mon dossier prérempli' continue_file: 'Continuer à remplir mon dossier' start_new_file: 'Commencer un nouveau dossier' show_my_submitted_file: 'Voir mon dossier déposé' @@ -138,7 +137,6 @@ fr: yes_no: "true" civilite: "M." pays: "FR" - departements: "56" regions: "53" date: "2023-02-01" datetime: "2023-02-01T10:30" @@ -261,6 +259,7 @@ fr: identity_data: Données d’identité all_required: Tous les champs sont obligatoires. civility: Civilité + description: Description de la procédure first_name: Prénom last_name: Nom birthdate: Date de naissance @@ -394,19 +393,6 @@ fr: zone: La démarche est mise en œuvre par champs: value: Valeur du champ - default_mail_attributes: &default_mail_attributes - hints: - subject: "L’objet généré sera tronqué s’il dépasse 100 caractères." - mails/closed_mail: - << : *default_mail_attributes - mails/initiated_mail: - << : *default_mail_attributes - mails/received_mail: - << : *default_mail_attributes - mails/refused_mail: - << : *default_mail_attributes - mails/without_continuation_mail: - << : *default_mail_attributes errors: messages: From a38c6077a59c211b48d611f50bf0b47d89ebe245 Mon Sep 17 00:00:00 2001 From: Julie Salha Date: Tue, 14 Feb 2023 13:22:51 +0100 Subject: [PATCH 069/202] fix PR translations --- config/locales/en.yml | 19 +++++++++++++++++-- config/locales/fr.yml | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index dcd81f16a..05d56ed0d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -68,7 +68,6 @@ en: are_you_new: First time on %{app_name}? my_account: My account header: - label_modal: "Burger menu" close_modal: 'Close' back: "Back" back_title: "Revenir sur le site de mon administration" @@ -93,11 +92,13 @@ en: existing_dossiers: You already have files for this procedure show_dossiers: View my current files prefilled_draft: "You have a prefilled file" - prefilled_draft_detail_html: "You prefilled a file for the \"%{procedure}\" procedure %{time_ago} ago" + prefilled_draft_detail_html: "You are ready to continue a prefilled file for the \"%{procedure}\" procedure, started %{time_ago} ago." + prefill_dossier_detail_html: "You are ready to continue a prefilled file for the \"%{procedure}\" procedure." already_draft: "You already started to fill a file" already_draft_detail_html: "You started to fill a file for the \"%{procedure}\" procedure %{time_ago} ago" already_not_draft: "You already submitted a file" already_not_draft_detail_html: "You submitted a file for the \"%{procedure}\" procedure %{time_ago} ago." + go_to_prefilled_file: 'Continue to fill my prefilled file' continue_file: "Continue to fill my file" start_new_file: "Start a new file" show_my_submitted_file: 'Show my submitted file' @@ -145,6 +146,7 @@ en: iban: FR7611315000011234567890138 yes_no: "true" pays: "FR" + departements: "56" regions: "53" date: "2023-02-01" datetime: "2023-02-01T10:30" @@ -396,6 +398,19 @@ en: zone: This procedure is run by champs: value: Value + default_mail_attributes: &default_mail_attributes + hints: + subject: The generated subject will be truncated if it exceeds 100 characters. + mails/closed_mail: + << : *default_mail_attributes + mails/initiated_mail: + << : *default_mail_attributes + mails/received_mail: + << : *default_mail_attributes + mails/refused_mail: + << : *default_mail_attributes + mails/without_continuation_mail: + << : *default_mail_attributes errors: messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1fb1e9fa1..8825332ff 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -59,7 +59,6 @@ fr: are_you_new: Vous êtes nouveau sur %{app_name} ? my_account: Mon compte header: - label_modal: "Menu en-tête de page" close_modal: 'Fermer' back: "Revenir en arrière" back_title: "Revenir sur le site de mon administration" @@ -84,11 +83,13 @@ fr: existing_dossiers: Vous avez déjà des dossiers pour cette démarche show_dossiers: Voir mes dossiers en cours prefilled_draft: "Vous avez un dossier prérempli" - prefilled_draft_detail_html: "Il y a %{time_ago}, vous avez prérempli un dossier sur la démarche « %{procedure} »." + prefilled_draft_detail_html: "Vous êtes prêt·e à poursuivre un dossier prérempli sur la démarche « %{procedure} », commencé il y a %{time_ago}." + prefill_dossier_detail_html: "Vous êtes prêt·e à poursuivre un dossier prérempli sur la démarche « %{procedure} »." already_draft: "Vous avez déjà commencé à remplir un dossier" already_draft_detail_html: "Il y a %{time_ago}, vous avez commencé à remplir un dossier sur la démarche « %{procedure} »." already_not_draft: "Vous avez déjà déposé un dossier" already_not_draft_detail_html: "Il y a %{time_ago}, vous avez déposé un dossier sur la démarche « %{procedure} »." + go_to_prefilled_file: 'Poursuivre mon dossier prérempli' continue_file: 'Continuer à remplir mon dossier' start_new_file: 'Commencer un nouveau dossier' show_my_submitted_file: 'Voir mon dossier déposé' @@ -137,6 +138,7 @@ fr: yes_no: "true" civilite: "M." pays: "FR" + departements: "56" regions: "53" date: "2023-02-01" datetime: "2023-02-01T10:30" @@ -393,6 +395,19 @@ fr: zone: La démarche est mise en œuvre par champs: value: Valeur du champ + default_mail_attributes: &default_mail_attributes + hints: + subject: "L’objet généré sera tronqué s’il dépasse 100 caractères." + mails/closed_mail: + << : *default_mail_attributes + mails/initiated_mail: + << : *default_mail_attributes + mails/received_mail: + << : *default_mail_attributes + mails/refused_mail: + << : *default_mail_attributes + mails/without_continuation_mail: + << : *default_mail_attributes errors: messages: From ca12e3f58c4a36ae772ee761ea2927bd29006995 Mon Sep 17 00:00:00 2001 From: Julie Salha <49035942+julieSalha@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:32:03 +0100 Subject: [PATCH 070/202] Update french translation description Co-authored-by: Colin Darie --- config/locales/fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8825332ff..4e22a7f01 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -261,7 +261,7 @@ fr: identity_data: Données d’identité all_required: Tous les champs sont obligatoires. civility: Civilité - description: Description de la procédure + description: Description de la démarche first_name: Prénom last_name: Nom birthdate: Date de naissance From 0427c28103ad263fb722a70fa2709629c54759a9 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 16 Feb 2023 14:09:05 +0100 Subject: [PATCH 071/202] correctif(db.active_storage_attachements): ajoute un fk non validable a priori pour eviter les problemes d'attachments sans blob --- ...22_fix_active_storage_attachment_missing_fk_on_blob_id.rb | 5 +++++ db/schema.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20230216130722_fix_active_storage_attachment_missing_fk_on_blob_id.rb diff --git a/db/migrate/20230216130722_fix_active_storage_attachment_missing_fk_on_blob_id.rb b/db/migrate/20230216130722_fix_active_storage_attachment_missing_fk_on_blob_id.rb new file mode 100644 index 000000000..51b697797 --- /dev/null +++ b/db/migrate/20230216130722_fix_active_storage_attachment_missing_fk_on_blob_id.rb @@ -0,0 +1,5 @@ +class FixActiveStorageAttachmentMissingFkOnBlobId < ActiveRecord::Migration[6.1] + def change + add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id, validate: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 59abcc837..7ab997120 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: 2023_02_07_105539) do +ActiveRecord::Schema.define(version: 2023_02_16_130722) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" From 437be2c901fa971b93ffbeb9d693fe896eca3dc8 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 16 Feb 2023 14:09:05 +0100 Subject: [PATCH 072/202] correctif(data): supprime les attachment sans blob --- .../20230216135218_reclean_attachments.rake | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/tasks/deployment/20230216135218_reclean_attachments.rake diff --git a/lib/tasks/deployment/20230216135218_reclean_attachments.rake b/lib/tasks/deployment/20230216135218_reclean_attachments.rake new file mode 100644 index 000000000..cfc5eb52f --- /dev/null +++ b/lib/tasks/deployment/20230216135218_reclean_attachments.rake @@ -0,0 +1,21 @@ +namespace :after_party do + desc 'Deployment task: reclean_attachments' + task reclean_attachments: :environment do + puts "Running deploy task 'reclean_attachments'" + + invalid_attachments = ActiveStorage::Attachment.where.missing(:blob) + invalid_attachments_count = invalid_attachments.size + + if invalid_attachments.any? + invalid_attachments.destroy_all + puts "#{invalid_attachments_count} with blob that doesn't exist have been destroyed" + else + puts "No attachments with blob that doesn't exist found" + end + + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end From 514835af1c46bc6721fd20b9f30c5a55f0bbeb6c Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 16 Feb 2023 15:43:35 +0100 Subject: [PATCH 073/202] correctif(db): force active_storage_attachments.blob fk on active_storage_blobs --- ...558_validate_foreign_key_between_attachments_and_blobs.rb | 5 +++++ db/schema.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20230216141558_validate_foreign_key_between_attachments_and_blobs.rb diff --git a/db/migrate/20230216141558_validate_foreign_key_between_attachments_and_blobs.rb b/db/migrate/20230216141558_validate_foreign_key_between_attachments_and_blobs.rb new file mode 100644 index 000000000..ba383c80a --- /dev/null +++ b/db/migrate/20230216141558_validate_foreign_key_between_attachments_and_blobs.rb @@ -0,0 +1,5 @@ +class ValidateForeignKeyBetweenAttachmentsAndBlobs < ActiveRecord::Migration[6.1] + def up + validate_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 59abcc837..14d0f9498 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: 2023_02_07_105539) do +ActiveRecord::Schema.define(version: 2023_02_16_141558) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" From 1f930dfe2336294f2eda1ea40d45a837d5fd7055 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 7 Feb 2023 18:06:38 +0100 Subject: [PATCH 074/202] feat(user): can show password when editing password --- app/components/dsfr/input_component.rb | 4 ++++ .../input_component/input_component.html.haml | 4 ++-- app/views/devise/passwords/edit.html.haml | 23 +++++++++++-------- config/locales/en.yml | 4 ++++ config/locales/fr.yml | 4 ++++ 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/components/dsfr/input_component.rb b/app/components/dsfr/input_component.rb index aa724a775..42113167b 100644 --- a/app/components/dsfr/input_component.rb +++ b/app/components/dsfr/input_component.rb @@ -89,6 +89,10 @@ class Dsfr::InputComponent < ApplicationComponent @input_type == :email_field end + def show_password_id + dom_id(object, "#{@attribute}_show_password") + end + private def hint? diff --git a/app/components/dsfr/input_component/input_component.html.haml b/app/components/dsfr/input_component/input_component.html.haml index a56c7fff8..8fc5324a3 100644 --- a/app/components/dsfr/input_component/input_component.html.haml +++ b/app/components/dsfr/input_component/input_component.html.haml @@ -23,8 +23,8 @@ - if password? .fr-password__checkbox.fr-checkbox-group.fr-checkbox-group--sm - %input#show_password{ "aria-label" => t('.show_password.aria_label'), type: "checkbox" }/ - %label.fr--password__checkbox.fr-label{ for: "show_password" }= t('.show_password.label') + %input{ id: show_password_id, "aria-label" => t('.show_password.aria_label'), type: "checkbox" }/ + %label.fr--password__checkbox.fr-label{ for: show_password_id }= t('.show_password.label') - if email? .suspect-email.hidden{ data: { "email-input-target": 'ariaRegion'}, aria: { live: 'off' } } diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 46d33dd55..e30dbf1d9 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -7,16 +7,21 @@ .one-column-centered = devise_error_messages! - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :patch, class: 'form' }) do |f| - - %h1 Changement de mot de passe - + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :patch, class: '' }) do |f| = f.hidden_field :reset_password_token - = f.label 'Nouveau mot de passe' - = render 'password_complexity/field', { form: f, test_complexity: populated_resource.validate_password_complexity? } - = f.label 'Confirmez le nouveau mot de passe' - = f.password_field :password_confirmation, autocomplete: 'off' + %fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } } + %legend.fr-fieldset__legend#edit-password-legend + %h1.fr-h2= I18n.t('views.users.passwords.edit.subtitle') - = f.submit 'Changer le mot de passe', class: 'button large primary expand', id: "submit-password", data: { disable_with: "Envoi…" } + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, + opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: populated_resource.validate_password_complexity? ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }}) + + = render 'password_complexity/field', { form: f, test_complexity: populated_resource.validate_password_complexity? } + + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :password_confirmation, input_type: :password_field, opts: { autocomplete: 'new-password' }) + + = f.submit t('views.users.passwords.edit.submit'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') } diff --git a/config/locales/en.yml b/config/locales/en.yml index 05d56ed0d..f2f481aee 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -351,6 +351,10 @@ en: connect_with_agent_connect: Visit our dedicated page subtitle: "Sign in with my account" passwords: + edit: + subtitle: Change password + submit: Change password + submit_loading: Sending… reset_link_sent: got_it: Got it! open_your_mailbox: Now open your mailbox. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4e22a7f01..efd7db901 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -347,6 +347,10 @@ fr: connect_with_agent_connect: Accédez à notre page dédiée subtitle: "Se connecter avec son compte" passwords: + edit: + subtitle: Changement de mot de passe + submit: Changer le mot de passe + submit_loading: Envoi… reset_link_sent: email_sent_html: "Nous vous avons envoyé un email à l’adresse %{email}." click_link_to_reset_password: "Cliquez sur le lien contenu dans l’email pour changer votre mot de passe." From 4f7839039d048e357eb9e7a94443c36d5e41ce83 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 9 Feb 2023 19:06:44 +0100 Subject: [PATCH 075/202] refactor(password-complexity): as component for better form integration --- .../stylesheets/password_complexity.scss | 2 - .../password_complexity_component.rb | 53 +++++++++++++++++++ .../password_complexity_component.en.yml | 10 ++++ .../password_complexity_component.fr.yml | 10 ++++ .../password_complexity_component.html.haml | 6 +++ app/views/devise/passwords/edit.html.haml | 33 ++++++------ app/views/password_complexity/_bar.html.haml | 1 - .../password_complexity/_field.html.haml | 9 ---- .../password_complexity/_label.html.haml | 16 ------ .../show.turbo_stream.haml | 5 +- .../password_complexity_controller_spec.rb | 3 +- 11 files changed, 101 insertions(+), 47 deletions(-) create mode 100644 app/components/password_complexity_component.rb create mode 100644 app/components/password_complexity_component/password_complexity_component.en.yml create mode 100644 app/components/password_complexity_component/password_complexity_component.fr.yml create mode 100644 app/components/password_complexity_component/password_complexity_component.html.haml delete mode 100644 app/views/password_complexity/_bar.html.haml delete mode 100644 app/views/password_complexity/_field.html.haml delete mode 100644 app/views/password_complexity/_label.html.haml diff --git a/app/assets/stylesheets/password_complexity.scss b/app/assets/stylesheets/password_complexity.scss index 24fc2ef77..ee521b0b3 100644 --- a/app/assets/stylesheets/password_complexity.scss +++ b/app/assets/stylesheets/password_complexity.scss @@ -9,12 +9,10 @@ $complexity-color-3: #FFD000; $complexity-color-4: $green; .password-complexity { - margin-top: -24px; width: 100%; height: 12px; background: $complexity-bg; display: block; - margin-bottom: $default-spacer; text-align: center; border-radius: 8px; diff --git a/app/components/password_complexity_component.rb b/app/components/password_complexity_component.rb new file mode 100644 index 000000000..32efd3908 --- /dev/null +++ b/app/components/password_complexity_component.rb @@ -0,0 +1,53 @@ +class PasswordComplexityComponent < ApplicationComponent + def initialize(length: nil, min_length: nil, score: nil, min_complexity: nil) + @length = length + @min_length = min_length + @score = score + @min_complexity = min_complexity + end + + private + + def filled? + !@length.nil? || !@score.nil? + end + + def alert_classes + class_names( + "fr-alert": true, + "fr-alert--sm": true, + "fr-alert--info": !success?, + "fr-alert--success": success? + ) + end + + def success? + return false if !filled? + + @length >= @min_length && @score >= @min_complexity + end + + def complexity_classes + [ + "password-complexity fr-mt-2w fr-mb-1w", + filled? ? "complexity-#{@length < @min_length ? @score / 2 : @score}" : nil + ] + end + + def title + return t(".title.empty") if !filled? + + return t(".title.too_short", min_length: @min_length) if @length < @min_length + + case @score + when 0..1 + return t(".title.weakest") + when 2...@min_complexity + return t(".title.weak") + when @min_complexity...4 + return t(".title.passable") + else + return t(".title.strong") + end + end +end diff --git a/app/components/password_complexity_component/password_complexity_component.en.yml b/app/components/password_complexity_component/password_complexity_component.en.yml new file mode 100644 index 000000000..d5930cd58 --- /dev/null +++ b/app/components/password_complexity_component/password_complexity_component.en.yml @@ -0,0 +1,10 @@ +--- +en: + title: + empty: Enter a password. + too_short: Password must be at least %{min_length} characters long. + passable: Password is acceptable. You can validate… or improve your password. + strong: Congratulations! Password is strong and secure enough. + weak: Vulnerable password. + weakest: Very vulnerable password. + hint: A short sentence with punctuation can be a very secure password. diff --git a/app/components/password_complexity_component/password_complexity_component.fr.yml b/app/components/password_complexity_component/password_complexity_component.fr.yml new file mode 100644 index 000000000..225a5775c --- /dev/null +++ b/app/components/password_complexity_component/password_complexity_component.fr.yml @@ -0,0 +1,10 @@ +--- +fr: + title: + empty: Inscrivez un mot de passe. + too_short: Le mot de passe doit faire au moins %{min_length} caractères. + passable: Mot de passe acceptable. Vous pouvez valider… ou améliorer votre mot de passe. + strong: Félicitations ! Mot de passe suffisamment fort et sécurisé. + weak: Mot de passe vulnérable. + weakest: Mot de passe très vulnérable. + hint: Une courte phrase avec ponctuation peut être un mot de passe très sécurisé. diff --git a/app/components/password_complexity_component/password_complexity_component.html.haml b/app/components/password_complexity_component/password_complexity_component.html.haml new file mode 100644 index 000000000..bfa7d5620 --- /dev/null +++ b/app/components/password_complexity_component/password_complexity_component.html.haml @@ -0,0 +1,6 @@ +%div{ class: complexity_classes } + +%div{ class: alert_classes } + %h3.fr-alert__title= title + - if !success? + %p= t(".hint") diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index e30dbf1d9..2ebfb3ee8 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -3,25 +3,28 @@ - content_for :footer do = render partial: 'root/footer' -.container.devise-container - .one-column-centered - = devise_error_messages! +.fr-container.fr-my-5w + .fr-grid-row.fr-grid-row--center + .fr-col-lg-6 + = devise_error_messages! - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :patch, class: '' }) do |f| - = f.hidden_field :reset_password_token + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :patch, class: '' }) do |f| + = f.hidden_field :reset_password_token - %fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } } - %legend.fr-fieldset__legend#edit-password-legend - %h1.fr-h2= I18n.t('views.users.passwords.edit.subtitle') + %fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } } + %legend.fr-fieldset__legend#edit-password-legend + %h1.fr-h2= I18n.t('views.users.passwords.edit.subtitle') - .fr-fieldset__element - = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, - opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: populated_resource.validate_password_complexity? ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }}) + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, + opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: populated_resource.validate_password_complexity? ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }}) - = render 'password_complexity/field', { form: f, test_complexity: populated_resource.validate_password_complexity? } + - if populated_resource.validate_password_complexity? + #password_complexity + = render PasswordComplexityComponent.new - .fr-fieldset__element - = render Dsfr::InputComponent.new(form: f, attribute: :password_confirmation, input_type: :password_field, opts: { autocomplete: 'new-password' }) + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :password_confirmation, input_type: :password_field, opts: { autocomplete: 'new-password' }) - = f.submit t('views.users.passwords.edit.submit'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') } + = f.submit t('views.users.passwords.edit.submit'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') } diff --git a/app/views/password_complexity/_bar.html.haml b/app/views/password_complexity/_bar.html.haml deleted file mode 100644 index a9b8c8262..000000000 --- a/app/views/password_complexity/_bar.html.haml +++ /dev/null @@ -1 +0,0 @@ -#complexity-bar.password-complexity{ class: "complexity-#{@length < @min_length ? @score/2 : @score}" } diff --git a/app/views/password_complexity/_field.html.haml b/app/views/password_complexity/_field.html.haml deleted file mode 100644 index 2e031f574..000000000 --- a/app/views/password_complexity/_field.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -= form.password_field :password, autofocus: true, autocomplete: 'off', placeholder: 'Mot de passe', data: { controller: test_complexity ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path } - -- if test_complexity - #complexity-bar.password-complexity - - .explication - #complexity-label{ style: 'font-weight: bold' } - Inscrivez un mot de passe. - Une courte phrase avec ponctuation peut être un mot de passe très sécurisé. diff --git a/app/views/password_complexity/_label.html.haml b/app/views/password_complexity/_label.html.haml deleted file mode 100644 index 2e9bda1d0..000000000 --- a/app/views/password_complexity/_label.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -#complexity-label{ style: 'font-weight: bold' } - - if @length > 0 - - if @length < @min_length - Le mot de passe doit faire au moins #{@min_length} caractères. - - else - - case @score - - when 0..1 - Mot de passe très vulnérable. - - when 2...@min_complexity - Mot de passe vulnérable. - - when @min_complexity...4 - Mot de passe acceptable. Vous pouvez valider...
      ou améliorer votre mot de passe. - - else - Félicitations ! Mot de passe suffisamment fort et sécurisé. - - else - Inscrivez un mot de passe. diff --git a/app/views/password_complexity/show.turbo_stream.haml b/app/views/password_complexity/show.turbo_stream.haml index 3fd3648f6..461ad5b12 100644 --- a/app/views/password_complexity/show.turbo_stream.haml +++ b/app/views/password_complexity/show.turbo_stream.haml @@ -1,5 +1,6 @@ -= turbo_stream.replace 'complexity-label', partial: 'label' -= turbo_stream.replace 'complexity-bar', partial: 'bar' += turbo_stream.update 'password_complexity' do + = render PasswordComplexityComponent.new(length: @length, min_length: @min_length, score: @score, min_complexity: @min_complexity) + - if @score < @min_complexity || @length < @min_length = turbo_stream.disable 'submit-password' - else diff --git a/spec/controllers/password_complexity_controller_spec.rb b/spec/controllers/password_complexity_controller_spec.rb index b04985806..6d03cab40 100644 --- a/spec/controllers/password_complexity_controller_spec.rb +++ b/spec/controllers/password_complexity_controller_spec.rb @@ -27,8 +27,7 @@ describe PasswordComplexityController, type: :controller do it 'renders Javascript that updates the password complexity meter' do subject - expect(response.body).to include('complexity-label') - expect(response.body).to include('complexity-bar') + expect(response.body).to include('Mot de passe vulnérable') end end end From 31c99e935a560764bc5746e879bcd75ff05e4119 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 15 Feb 2023 10:16:01 +0100 Subject: [PATCH 076/202] refactor(admin): migrate password form to DSFR components --- app/components/dsfr/input_component.rb | 2 +- .../administrateurs/activate/new.html.haml | 33 +++++++++++-------- config/locales/en.yml | 5 +++ config/locales/fr.yml | 4 +++ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/components/dsfr/input_component.rb b/app/components/dsfr/input_component.rb index 42113167b..99ad02172 100644 --- a/app/components/dsfr/input_component.rb +++ b/app/components/dsfr/input_component.rb @@ -6,7 +6,7 @@ class Dsfr::InputComponent < ApplicationComponent # it uses aria-describedby on input and link it to yielded content renders_one :describedby - def initialize(form:, attribute:, input_type:, opts: {}, required: true) + def initialize(form:, attribute:, input_type: :text_field, opts: {}, required: true) @form = form @attribute = attribute @input_type = input_type diff --git a/app/views/administrateurs/activate/new.html.haml b/app/views/administrateurs/activate/new.html.haml index 5d17e6aa8..b826ddffe 100644 --- a/app/views/administrateurs/activate/new.html.haml +++ b/app/views/administrateurs/activate/new.html.haml @@ -1,21 +1,26 @@ -- content_for(:title, "Choix du mot de passe") +- content_for(:title, t('.title')) - content_for :footer do = render partial: "root/footer" -.container.devise-container - .one-column-centered - = form_for @administrateur, url: { controller: 'administrateurs/activate', action: :create }, html: { class: "form" } do |f| - %br - %h1 - Choix du mot de passe +.fr-container.fr-my-5w + .fr-grid-row.fr-grid-row--center + .fr-col-lg-6 + = form_for @administrateur, url: { controller: 'administrateurs/activate', action: :create } do |f| + = f.hidden_field :reset_password_token, value: @token - = f.hidden_field :reset_password_token, value: @token - = f.label :email, "Email" - = f.text_field :email, disabled: true + %fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } } + %legend.fr-fieldset__legend#edit-password-legend + %h1.fr-h2= t('.title') - = f.label :password do - Mot de passe - = render 'password_complexity/field', { form: f, test_complexity: true } + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :email, opts: { disabled: true }) - = f.submit 'Continuer', class: 'button large primary expand', id: "submit-password", data: { disable_with: "Envoi..." } + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, + opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: 'turbo-input', turbo_input_url_value: show_password_complexity_path }}) + + #password_complexity + = render PasswordComplexityComponent.new + + = f.submit t('.continue'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') } diff --git a/config/locales/en.yml b/config/locales/en.yml index f2f481aee..6410dadb8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -580,6 +580,11 @@ en: deleted: one: Deleted other: Deleted + administrateurs: + activate: + new: + title: Pick a password + continue: Continue users: dossiers: test_procedure: "This file is submitted on a test procedure. Any modification of the procedure by the administrator (addition of a field, publication of the procedure, etc.) will result in the removal of the file." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index efd7db901..70c52acfc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -629,6 +629,10 @@ fr: to_follow: à suivre total: dossiers administrateurs: + activate: + new: + title: Choix du mot de passe + continue: Continuer index: restored: La démarche %{procedure_id} a été restaurée dropdown_actions: From c968aa63d3a8290aa6122ddab0ff8d747bdcd204 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 15 Feb 2023 11:33:42 +0100 Subject: [PATCH 077/202] fix(a11y/password form): describedby rel with input and password rules --- app/components/dsfr/input_component.rb | 22 ++++++++++--------- .../input_component/input_component.html.haml | 6 ++--- app/views/devise/_password_rules.html.haml | 3 +++ app/views/devise/passwords/edit.html.haml | 13 ++++++----- app/views/users/registrations/new.html.haml | 6 ++--- 5 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 app/views/devise/_password_rules.html.haml diff --git a/app/components/dsfr/input_component.rb b/app/components/dsfr/input_component.rb index 99ad02172..271762470 100644 --- a/app/components/dsfr/input_component.rb +++ b/app/components/dsfr/input_component.rb @@ -40,19 +40,21 @@ class Dsfr::InputComponent < ApplicationComponent 'fr-mb-0': true, 'fr-input--error': errors_on_attribute?)) - if errors_on_attribute? || describedby - @opts = @opts.deep_merge(aria: { - describedby: error_message_id, - invalid: errors_on_attribute? + if errors_on_attribute? || describedby? + @opts.deep_merge!(aria: { + describedby: describedby_id, + invalid: errors_on_attribute? }) end + if @required @opts[:required] = true end + if email? - @opts = @opts.deep_merge(data: { + @opts.deep_merge!(data: { action: "blur->email-input#checkEmail", - 'email-input-target': 'input' + 'email-input-target': 'input' }) end @opts @@ -63,14 +65,14 @@ class Dsfr::InputComponent < ApplicationComponent errors.has_key?(attribute_or_rich_body) end - def error_message_id - dom_id(object, @attribute) - end - def error_messages errors.full_messages_for(attribute_or_rich_body) end + def describedby_id + dom_id(object, "#{@attribute}-messages") + end + # i18n lookups def label object.class.human_attribute_name(@attribute) diff --git a/app/components/dsfr/input_component/input_component.html.haml b/app/components/dsfr/input_component/input_component.html.haml index 8fc5324a3..4e52ec670 100644 --- a/app/components/dsfr/input_component/input_component.html.haml +++ b/app/components/dsfr/input_component/input_component.html.haml @@ -7,13 +7,13 @@ - if hint? %span.fr-hint-text= hint - = @form.send(@input_type, @attribute, input_opts) + = @form.public_send(@input_type, @attribute, input_opts) - if errors_on_attribute? - if error_messages.size == 1 - %p.fr-error-text{ id: error_message_id }= error_messages.first + %p.fr-error-text{ id: describedby_id }= error_messages.first - else - .fr-error-text{ id: error_message_id } + .fr-error-text{ id: describedby_id } %ul.list-style-type-none.fr-pl-0 - error_messages.map do |error_message| %li= error_message diff --git a/app/views/devise/_password_rules.html.haml b/app/views/devise/_password_rules.html.haml new file mode 100644 index 000000000..2d4083d2f --- /dev/null +++ b/app/views/devise/_password_rules.html.haml @@ -0,0 +1,3 @@ +.fr-messages-group{ "aria-live" => "off", id: id } + %p.fr-message= t('views.registrations.new.password_message') + %p.fr-message.fr-message--info= t('views.registrations.new.password_placeholder', min_length: PASSWORD_MIN_LENGTH) diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 2ebfb3ee8..62103f214 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -18,11 +18,14 @@ .fr-fieldset__element = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, - opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: populated_resource.validate_password_complexity? ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }}) - - - if populated_resource.validate_password_complexity? - #password_complexity - = render PasswordComplexityComponent.new + opts: { autofocus: 'true', autocomplete: 'new-password', minlength: PASSWORD_MIN_LENGTH, data: { controller: populated_resource.validate_password_complexity? ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }}) do |c| + - c.describedby do + - if populated_resource.validate_password_complexity? + %div{ id: c.describedby_id } + #password_complexity + = render PasswordComplexityComponent.new + - else + = render partial: "devise/password_rules", locals: { id: c.describedby_id } .fr-fieldset__element = render Dsfr::InputComponent.new(form: f, attribute: :password_confirmation, input_type: :password_field, opts: { autocomplete: 'new-password' }) diff --git a/app/views/users/registrations/new.html.haml b/app/views/users/registrations/new.html.haml index feb6e0870..e86419ad2 100644 --- a/app/views/users/registrations/new.html.haml +++ b/app/views/users/registrations/new.html.haml @@ -18,10 +18,8 @@ .fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { autocomplete: 'email', autofocus: true }) .fr-fieldset__element - = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, opts: { autocomplete: 'new-password', min_length: PASSWORD_MIN_LENGTH }) do |c| + = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, opts: { autocomplete: 'new-password', minlength: PASSWORD_MIN_LENGTH }) do |c| - c.describedby do - #password-input-messages.fr-messages-group{ "aria-live" => "off" } - %p#password-input-message.fr-message= t('views.registrations.new.password_message') - %p#password-input-message-info.fr-message.fr-message--info= t('views.registrations.new.password_placeholder', min_length: PASSWORD_MIN_LENGTH) + = render partial: "devise/password_rules", locals: { id: c.describedby_id } = f.submit t('views.shared.account.create'), class: "fr-btn fr-btn--lg" From 6231c75e07f3ae5dc2b5c6910ff5ee9264b41e60 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Thu, 16 Feb 2023 16:58:55 +0100 Subject: [PATCH 078/202] Fix tests conflicts --- app/models/prefill_params.rb | 1 - app/models/types_de_champ/prefill_type_de_champ.rb | 4 ---- spec/models/prefill_params_spec.rb | 6 +++--- spec/models/types_de_champ/prefill_type_de_champ_spec.rb | 8 -------- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index 52f8ce1e6..63ff0b25d 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -70,6 +70,5 @@ class PrefillParams champ.assign_attributes(champ_attributes) champ.valid?(:prefill) end - end end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 6a4e6a047..e389315fa 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -72,8 +72,4 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator def description @description ||= I18n.t("views.prefill_descriptions.edit.possible_values.#{type_champ}_html", default: nil)&.html_safe # rubocop:disable Rails/OutputSafety end - - def transform_value_to_assignable_attributes(value) - { value: value } - end end diff --git a/spec/models/prefill_params_spec.rb b/spec/models/prefill_params_spec.rb index 1842b9900..53f15ed17 100644 --- a/spec/models/prefill_params_spec.rb +++ b/spec/models/prefill_params_spec.rb @@ -146,7 +146,7 @@ RSpec.describe PrefillParams do let(:type_de_champ_child_value) { "value" } let(:type_de_champ_child_value2) { "value2" } - let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value}\"}", "{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value2}\"}"] } } + let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"#{type_de_champ_child.to_typed_id}\":\"#{type_de_champ_child_value}\"}", "{\"#{type_de_champ_child.to_typed_id}\":\"#{type_de_champ_child_value2}\"}"] } } it "builds an array of hash(id, value) matching the given params" do expect(prefill_params_array).to match([{ id: type_de_champ_child.champ.first.id, value: type_de_champ_child_value }, { id: type_de_champ_child.champ.second.id, value: type_de_champ_child_value2 }]) @@ -180,7 +180,7 @@ RSpec.describe PrefillParams do let(:type_de_champ_child_value) { "value" } let(:type_de_champ_child_value2) { "value2" } - let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value}\"}", "{\"#{type_de_champ_child.libelle}\":\"#{type_de_champ_child_value2}\"}"] } } + let(:params) { { "champ_#{type_de_champ.to_typed_id}" => ["{\"#{type_de_champ_child.to_typed_id}\":\"#{type_de_champ_child_value}\"}", "{\"#{type_de_champ_child.to_typed_id}\":\"#{type_de_champ_child_value2}\"}"] } } it "builds an array of hash(id, value) matching the given params" do expect(prefill_params_array).to match([{ id: type_de_champ_child.champ.first.id, value: type_de_champ_child_value }, { id: type_de_champ_child.champ.second.id, value: type_de_champ_child_value2 }]) @@ -227,7 +227,7 @@ RSpec.describe PrefillParams do end end - context "when the public type de champ is unauthorized because of wrong value libelle (repetition)" do + context "when the public type de champ is unauthorized because of wrong value typed_id (repetition)" do let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :text }] }] } let(:type_de_champ) { procedure.published_revision.types_de_champ_public.first } let(:type_de_champ_child) { procedure.published_revision.children_of(type_de_champ).first } diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 45ea7dce9..a4b1655e5 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -137,12 +137,4 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do it { is_expected.to match({ id: champ.id, value: value }) } end - - describe '#transform_value_to_assignable_attributes' do - let(:type_de_champ) { build(:type_de_champ_email) } - let(:value) { "any@email.org" } - subject(:transform_value_to_assignable_attributes) { described_class.build(type_de_champ).transform_value_to_assignable_attributes(value) } - - it { is_expected.to match({ value: value }) } - end end From d2452980fe6262e6f75db6f1c40fdef62f17f920 Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Thu, 16 Feb 2023 17:28:18 +0100 Subject: [PATCH 079/202] Fix merge conflict spec --- ...l_multiple_drop_down_list_type_de_champ.rb | 6 +- spec/models/prefill_description_spec.rb | 125 +++++++----------- .../prefill_epci_type_de_champ_spec.rb | 13 +- .../prefill_pays_type_de_champ_spec.rb | 2 +- .../prefill_region_type_de_champ_spec.rb | 2 +- 5 files changed, 61 insertions(+), 87 deletions(-) diff --git a/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb index 11992f53d..17bea90a4 100644 --- a/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_multiple_drop_down_list_type_de_champ.rb @@ -1,8 +1,8 @@ class TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp < TypesDeChamp::PrefillDropDownListTypeDeChamp def example_value - return nil if possible_values.empty? - return possible_values.first if possible_values.one? + return nil if all_possible_values.empty? + return all_possible_values.first if all_possible_values.one? - [possible_values.first, possible_values.second] + [all_possible_values.first, all_possible_values.second] end end diff --git a/spec/models/prefill_description_spec.rb b/spec/models/prefill_description_spec.rb index 063eb2db3..dc8a62fa4 100644 --- a/spec/models/prefill_description_spec.rb +++ b/spec/models/prefill_description_spec.rb @@ -88,70 +88,13 @@ RSpec.describe PrefillDescription, type: :model do describe '#prefill_link', vcr: { cassette_name: 'api_geo_regions' } do let(:procedure) { create(:procedure) } - let(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) } - let(:type_de_champ_repetition) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } + let(:type_de_champ_text) { build(:type_de_champ_text, procedure: procedure) } + let(:type_de_champ_epci) { build(:type_de_champ_epci, procedure: procedure) } + let(:type_de_champ_repetition) { create(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } let(:region_repetition) { prefillable_subchamps.third } let(:prefill_description) { described_class.new(procedure) } - before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id, type_de_champ_repetition.id]) } - - it "builds the URL to create a new prefilled dossier" do - expect(prefill_description.prefill_link).to eq( - commencer_url( - path: procedure.path, - "champ_#{type_de_champ.to_typed_id}" => I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}"), - "champ_#{type_de_champ_repetition.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value - ) - ) - end - - context 'when the type de champ can have multiple values' do - let(:type_de_champ) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } - - let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } - - before do - allow(Rails).to receive(:cache).and_return(memory_store) - Rails.cache.clear - - VCR.insert_cassette('api_geo_departements') - VCR.insert_cassette('api_geo_epcis') - end - - after do - VCR.eject_cassette('api_geo_departements') - VCR.eject_cassette('api_geo_epcis') - end - - it 'builds the URL with array parameter' do - expect(prefill_description.prefill_link).to eq( - commencer_url( - path: procedure.path, - "champ_#{type_de_champ.to_typed_id}": type_de_champ.example_value - ) - ) - end - end - end - - describe '#prefill_query', vcr: { cassette_name: 'api_geo_regions' } do - let(:procedure) { create(:procedure) } - let(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) } - let(:type_de_champ_repetition) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } - let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } - let(:region_repetition) { prefillable_subchamps.third } - let(:prefill_description) { described_class.new(procedure) } - let(:expected_query) do - <<~TEXT - curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ - --header 'Content-Type: application/json' \\ - --data '{"champ_#{type_de_champ.to_typed_id}": "#{I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")}", "champ_#{type_de_champ_repetition.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value}}' - TEXT - end - - before { prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id, type_de_champ_repetition.id]) } - let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } before do @@ -161,7 +104,52 @@ RSpec.describe PrefillDescription, type: :model do VCR.insert_cassette('api_geo_departements') VCR.insert_cassette('api_geo_epcis') - prefill_description.update(selected_type_de_champ_ids: [type_de_champ.id]) + prefill_description.update(selected_type_de_champ_ids: [type_de_champ_text.id, type_de_champ_epci.id, type_de_champ_repetition.id]) + end + + after do + VCR.eject_cassette('api_geo_departements') + VCR.eject_cassette('api_geo_epcis') + end + + it "builds the URL to create a new prefilled dossier" do + expect(prefill_description.prefill_link).to eq( + commencer_url( + path: procedure.path, + "champ_#{type_de_champ_text.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_text).example_value, + "champ_#{type_de_champ_epci.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_epci).example_value, + "champ_#{type_de_champ_repetition.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value + ) + ) + end + end + + describe '#prefill_query', vcr: { cassette_name: 'api_geo_regions' } do + let(:procedure) { create(:procedure) } + let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } + let(:type_de_champ_epci) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } + let(:type_de_champ_repetition) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } + let(:region_repetition) { prefillable_subchamps.third } + let(:prefill_description) { described_class.new(procedure) } + let(:expected_query) do + <<~TEXT + curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ + --header 'Content-Type: application/json' \\ + --data '{"champ_#{type_de_champ_text.to_typed_id}": "#{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_text).example_value}", "champ_#{type_de_champ_epci.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_epci).example_value}, "champ_#{type_de_champ_repetition.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value}}' + TEXT + end + + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + + VCR.insert_cassette('api_geo_departements') + VCR.insert_cassette('api_geo_epcis') + + prefill_description.update(selected_type_de_champ_ids: [type_de_champ_text.id, type_de_champ_epci.id, type_de_champ_repetition.id]) end after do @@ -172,20 +160,5 @@ RSpec.describe PrefillDescription, type: :model do it "builds the query to create a new prefilled dossier" do expect(prefill_description.prefill_query).to eq(expected_query) end - - context 'when the type de champ can have multiple values' do - let(:type_de_champ) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } - let(:expected_query) do - <<~TEXT - curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ - --header 'Content-Type: application/json' \\ - --data '{"champ_#{type_de_champ.to_typed_id}": #{type_de_champ.example_value}}' - TEXT - end - - it 'builds the query with array parameter' do - expect(prefill_description.prefill_query).to eq(expected_query) - end - end end end diff --git a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb index ef59b9df3..ae68772f8 100644 --- a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb @@ -2,6 +2,7 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do let(:type_de_champ) { build(:type_de_champ_epci) } + let(:champ) { create(:champ_epci, type_de_champ: type_de_champ) } let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } before do @@ -57,32 +58,32 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do context 'when the value is nil' do let(:value) { nil } - it { is_expected.to match({ code_departement: nil, value: nil }) } + it { is_expected.to match({ code_departement: nil, value: nil, id: champ.id }) } end context 'when the value is empty' do let(:value) { '' } - it { is_expected.to match({ code_departement: nil, value: nil }) } + it { is_expected.to match({ code_departement: nil, value: nil, id: champ.id }) } end context 'when the value is a string' do let(:value) { 'hello' } - it { is_expected.to match({ code_departement: nil, value: nil }) } + it { is_expected.to match({ code_departement: nil, value: nil, id: champ.id }) } end context 'when the value is an array of one element' do let(:value) { ['01'] } - it { is_expected.to match({ code_departement: '01', value: nil }) } + it { is_expected.to match({ code_departement: '01', value: nil, id: champ.id }) } end context 'when the value is an array of two elements' do let(:value) { ['01', '200042935'] } - it { is_expected.to match({ code_departement: '01', value: '200042935' }) } + it { is_expected.to match({ code_departement: '01', value: '200042935', id: champ.id }) } end context 'when the value is an array of three or more elements' do let(:value) { ['01', '200042935', 'hello'] } - it { is_expected.to match({ code_departement: '01', value: '200042935' }) } + it { is_expected.to match({ code_departement: '01', value: '200042935', id: champ.id }) } end end diff --git a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb index 7a353da35..36f5aebcb 100644 --- a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb @@ -10,7 +10,7 @@ RSpec.describe TypesDeChamp::PrefillPaysTypeDeChamp, type: :model do end describe '#possible_values' do - let(:expected_values) { "Un code pays ISO 3166-2
      Voir toutes les valeurs possibles" } + let(:expected_values) { "Un code pays ISO 3166-2
      Voir toutes les valeurs possibles" } subject(:possible_values) { described_class.new(type_de_champ).possible_values } before { type_de_champ.reload } diff --git a/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb index 31d7b7a86..3a73fedc4 100644 --- a/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb @@ -17,7 +17,7 @@ RSpec.describe TypesDeChamp::PrefillRegionTypeDeChamp, type: :model do end describe '#possible_values', vcr: { cassette_name: 'api_geo_regions' } do - let(:expected_values) { "Un code INSEE de région
      Voir toutes les valeurs possibles" } + let(:expected_values) { "Un code INSEE de région
      Voir toutes les valeurs possibles" } subject(:possible_values) { described_class.new(type_de_champ).possible_values } before { type_de_champ.reload } From b85eeedd6c0fdb8a663f0efb9cb4fb201eed6298 Mon Sep 17 00:00:00 2001 From: sebastiencarceles Date: Tue, 14 Feb 2023 09:14:27 +0100 Subject: [PATCH 080/202] remove departements and regions migration specs --- ...20230207144243_normalize_regions_spec.rake | 139 ------ ...208084036_normalize_departements_spec.rake | 409 ------------------ 2 files changed, 548 deletions(-) delete mode 100644 spec/lib/tasks/deployment/20230207144243_normalize_regions_spec.rake delete mode 100644 spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake diff --git a/spec/lib/tasks/deployment/20230207144243_normalize_regions_spec.rake b/spec/lib/tasks/deployment/20230207144243_normalize_regions_spec.rake deleted file mode 100644 index 3a049fddc..000000000 --- a/spec/lib/tasks/deployment/20230207144243_normalize_regions_spec.rake +++ /dev/null @@ -1,139 +0,0 @@ -describe '20230207144243_normalize_regions', vcr: { cassette_name: 'api_geo_regions' } do - let(:champ) { create(:champ_regions) } - let(:rake_task) { Rake::Task['after_party:normalize_regions'] } - let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } - - subject(:run_task) { rake_task.invoke } - - before do - allow(Rails).to receive(:cache).and_return(memory_store) - Rails.cache.clear - end - - after { rake_task.reenable } - - shared_examples "a non-changer" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.not_to change { champ.reload.external_id } } - - it { expect { run_task }.not_to change { champ.reload.value } } - end - - shared_examples "an external_id nullifier" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(nil) } - - it { expect { run_task }.not_to change { champ.reload.value } } - end - - shared_examples "a value nullifier" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.not_to change { champ.reload.external_id } } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(nil) } - end - - shared_examples "an external_id and value nullifier" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(nil) } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(nil) } - end - - shared_examples "an external_id updater" do |external_id, value, expected_external_id| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(expected_external_id) } - - it { expect { run_task }.not_to change { champ.reload.value } } - end - - shared_examples "a result checker" do |external_id, value, expected_external_id, expected_value| - before do - champ.update_columns(external_id:, value:) - run_task - end - - it { expect(champ.reload.external_id).to eq(expected_external_id) } - - it { expect(champ.reload.value).to eq(expected_value) } - end - - shared_examples "a value updater" do |external_id, value, expected_value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.not_to change { champ.reload.external_id } } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(expected_value) } - end - - it_behaves_like "a non-changer", nil, nil - it_behaves_like "an external_id nullifier", '', nil - it_behaves_like "a value nullifier", nil, '' - it_behaves_like "an external_id and value nullifier", '', '' - it_behaves_like "an external_id updater", nil, 'Auvergne-Rhône-Alpes', '84' - it_behaves_like "an external_id updater", '', 'Auvergne-Rhône-Alpes', '84' - it_behaves_like "a value updater", '11', nil, 'Île-de-France' - - # Integrity data check: - it_behaves_like "a result checker", "84", "Auvergne-Rhône-Alpes", "84", "Auvergne-Rhône-Alpes" - it_behaves_like "a result checker", nil, "Auvergne-Rhône-Alpes", "84", "Auvergne-Rhône-Alpes" - it_behaves_like "a result checker", '', "Auvergne-Rhône-Alpes", "84", "Auvergne-Rhône-Alpes" - it_behaves_like "a result checker", "27", "Bourgogne-Franche-Comté", "27", "Bourgogne-Franche-Comté" - it_behaves_like "a result checker", nil, "Bourgogne-Franche-Comté", "27", "Bourgogne-Franche-Comté" - it_behaves_like "a result checker", '', "Bourgogne-Franche-Comté", "27", "Bourgogne-Franche-Comté" - it_behaves_like "a result checker", "53", "Bretagne", "53", "Bretagne" - it_behaves_like "a result checker", nil, "Bretagne", "53", "Bretagne" - it_behaves_like "a result checker", '', "Bretagne", "53", "Bretagne" - it_behaves_like "a result checker", "24", "Centre-Val de Loire", "24", "Centre-Val de Loire" - it_behaves_like "a result checker", nil, "Centre-Val de Loire", "24", "Centre-Val de Loire" - it_behaves_like "a result checker", '', "Centre-Val de Loire", "24", "Centre-Val de Loire" - it_behaves_like "a result checker", "94", "Corse", "94", "Corse" - it_behaves_like "a result checker", nil, "Corse", "94", "Corse" - it_behaves_like "a result checker", '', "Corse", "94", "Corse" - it_behaves_like "a result checker", "44", "Grand Est", "44", "Grand Est" - it_behaves_like "a result checker", nil, "Grand Est", "44", "Grand Est" - it_behaves_like "a result checker", '', "Grand Est", "44", "Grand Est" - it_behaves_like "a result checker", "01", "Guadeloupe", "01", "Guadeloupe" - it_behaves_like "a result checker", nil, "Guadeloupe", "01", "Guadeloupe" - it_behaves_like "a result checker", '', "Guadeloupe", "01", "Guadeloupe" - it_behaves_like "a result checker", "03", "Guyane", "03", "Guyane" - it_behaves_like "a result checker", nil, "Guyane", "03", "Guyane" - it_behaves_like "a result checker", '', "Guyane", "03", "Guyane" - it_behaves_like "a result checker", "32", "Hauts-de-France", "32", "Hauts-de-France" - it_behaves_like "a result checker", nil, "Hauts-de-France", "32", "Hauts-de-France" - it_behaves_like "a result checker", '', "Hauts-de-France", "32", "Hauts-de-France" - it_behaves_like "a result checker", "04", "La Réunion", "04", "La Réunion" - it_behaves_like "a result checker", nil, "La Réunion", "04", "La Réunion" - it_behaves_like "a result checker", '', "La Réunion", "04", "La Réunion" - it_behaves_like "a result checker", "02", "Martinique", "02", "Martinique" - it_behaves_like "a result checker", nil, "Martinique", "02", "Martinique" - it_behaves_like "a result checker", '', "Martinique", "02", "Martinique" - it_behaves_like "a result checker", "06", "Mayotte", "06", "Mayotte" - it_behaves_like "a result checker", nil, "Mayotte", "06", "Mayotte" - it_behaves_like "a result checker", '', "Mayotte", "06", "Mayotte" - it_behaves_like "a result checker", "28", "Normandie", "28", "Normandie" - it_behaves_like "a result checker", nil, "Normandie", "28", "Normandie" - it_behaves_like "a result checker", '', "Normandie", "28", "Normandie" - it_behaves_like "a result checker", "75", "Nouvelle-Aquitaine", "75", "Nouvelle-Aquitaine" - it_behaves_like "a result checker", nil, "Nouvelle-Aquitaine", "75", "Nouvelle-Aquitaine" - it_behaves_like "a result checker", '', "Nouvelle-Aquitaine", "75", "Nouvelle-Aquitaine" - it_behaves_like "a result checker", "76", "Occitanie", "76", "Occitanie" - it_behaves_like "a result checker", nil, "Occitanie", "76", "Occitanie" - it_behaves_like "a result checker", '', "Occitanie", "76", "Occitanie" - it_behaves_like "a result checker", "52", "Pays de la Loire", "52", "Pays de la Loire" - it_behaves_like "a result checker", nil, "Pays de la Loire", "52", "Pays de la Loire" - it_behaves_like "a result checker", '', "Pays de la Loire", "52", "Pays de la Loire" - it_behaves_like "a result checker", "93", "Provence-Alpes-Côte d'Azur", "93", "Provence-Alpes-Côte d’Azur" - it_behaves_like "a result checker", nil, "Provence-Alpes-Côte d'Azur", "93", "Provence-Alpes-Côte d’Azur" - it_behaves_like "a result checker", '', "Provence-Alpes-Côte d'Azur", "93", "Provence-Alpes-Côte d’Azur" - it_behaves_like "a result checker", "93", "Provence-Alpes-Côte d’Azur", "93", "Provence-Alpes-Côte d’Azur" - it_behaves_like "a result checker", "11", "Île-de-France", "11", "Île-de-France" - it_behaves_like "a result checker", "11", nil, "11", "Île-de-France" - it_behaves_like "a result checker", nil, "Île-de-France", "11", "Île-de-France" - it_behaves_like "a result checker", '', "Île-de-France", "11", "Île-de-France" -end diff --git a/spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake b/spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake deleted file mode 100644 index 968f37fb3..000000000 --- a/spec/lib/tasks/deployment/20230208084036_normalize_departements_spec.rake +++ /dev/null @@ -1,409 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe '20230208084036_normalize_departements', vcr: { cassette_name: 'api_geo_departements' } do - let(:champ) { create(:champ_departements) } - let(:rake_task) { Rake::Task['after_party:normalize_departements'] } - let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } - - subject(:run_task) { perform_enqueued_jobs { rake_task.invoke } } - - before do - allow(Rails).to receive(:cache).and_return(memory_store) - Rails.cache.clear - end - - after { rake_task.reenable } - - shared_examples "a non-changer" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.not_to change { champ.reload.external_id } } - - it { expect { run_task }.not_to change { champ.reload.value } } - end - - shared_examples "an external_id nullifier" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(nil) } - - it { expect { run_task }.not_to change { champ.reload.value } } - end - - shared_examples "a value nullifier" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.not_to change { champ.reload.external_id } } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(nil) } - end - - shared_examples "an external_id and value nullifier" do |external_id, value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(nil) } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(nil) } - end - - shared_examples "an external_id updater" do |external_id, value, expected_external_id| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(expected_external_id) } - - it { expect { run_task }.not_to change { champ.reload.value } } - end - - shared_examples "a value updater" do |external_id, value, expected_value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.not_to change { champ.reload.external_id } } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(expected_value) } - end - - shared_examples "an external_id and value updater" do |external_id, value, expected_external_id, expected_value| - before { champ.update_columns(external_id:, value:) } - - it { expect { run_task }.to change { champ.reload.external_id }.from(external_id).to(expected_external_id) } - - it { expect { run_task }.to change { champ.reload.value }.from(value).to(expected_value) } - end - - shared_examples "a result checker" do |external_id, value, expected_external_id, expected_value| - before do - champ.update_columns(external_id:, value:) - run_task - end - - it { expect(champ.reload.external_id).to eq(expected_external_id) } - - it { expect(champ.reload.value).to eq(expected_value) } - end - - it_behaves_like "a non-changer", nil, nil - it_behaves_like "an external_id nullifier", '', nil - it_behaves_like "a value nullifier", nil, '' - it_behaves_like "an external_id and value nullifier", '', '' - it_behaves_like "an external_id updater", nil, 'Ain', '01' - it_behaves_like "an external_id updater", '', 'Ain', '01' - it_behaves_like "a value updater", '01', nil, 'Ain' - it_behaves_like "a value updater", '01', '', 'Ain' - it_behaves_like "an external_id and value updater", nil, '01 - Ain', '01', 'Ain' - it_behaves_like "an external_id and value updater", '', '01 - Ain', '01', 'Ain' - - # Integrity data check: - it_behaves_like "a result checker", "972", nil, "972", "Martinique" - it_behaves_like "a result checker", "92", nil, "92", "Hauts-de-Seine" - it_behaves_like "a result checker", "01", nil, "01", "Ain" - it_behaves_like "a result checker", "82", nil, "82", "Tarn-et-Garonne" - it_behaves_like "a result checker", "01", "01 - Ain", "01", "Ain" - it_behaves_like "a result checker", '', "01 - Ain", "01", "Ain" - it_behaves_like "a result checker", nil, "01 - Ain", "01", "Ain" - it_behaves_like "a result checker", "02", "02 - Aisne", "02", "Aisne" - it_behaves_like "a result checker", nil, "02 - Aisne", "02", "Aisne" - it_behaves_like "a result checker", '', "02 - Aisne", "02", "Aisne" - it_behaves_like "a result checker", "03", "03 - Allier", "03", "Allier" - it_behaves_like "a result checker", nil, "03 - Allier", "03", "Allier" - it_behaves_like "a result checker", '', "03 - Allier", "03", "Allier" - it_behaves_like "a result checker", "04", "04 - Alpes-de-Haute-Provence", "04", "Alpes-de-Haute-Provence" - it_behaves_like "a result checker", nil, "04 - Alpes-de-Haute-Provence", "04", "Alpes-de-Haute-Provence" - it_behaves_like "a result checker", '', "04 - Alpes-de-Haute-Provence", "04", "Alpes-de-Haute-Provence" - it_behaves_like "a result checker", "05", "05 - Hautes-Alpes", "05", "Hautes-Alpes" - it_behaves_like "a result checker", nil, "05 - Hautes-Alpes", "05", "Hautes-Alpes" - it_behaves_like "a result checker", '', "05 - Hautes-Alpes", "05", "Hautes-Alpes" - it_behaves_like "a result checker", nil, "06 - Alpes-Maritimes", "06", "Alpes-Maritimes" - it_behaves_like "a result checker", "06", "06 - Alpes-Maritimes", "06", "Alpes-Maritimes" - it_behaves_like "a result checker", '', "06 - Alpes-Maritimes", "06", "Alpes-Maritimes" - it_behaves_like "a result checker", "07", "07 - Ardèche", "07", "Ardèche" - it_behaves_like "a result checker", nil, "07 - Ardèche", "07", "Ardèche" - it_behaves_like "a result checker", '', "07 - Ardèche", "07", "Ardèche" - it_behaves_like "a result checker", "08", "08 - Ardennes", "08", "Ardennes" - it_behaves_like "a result checker", nil, "08 - Ardennes", "08", "Ardennes" - it_behaves_like "a result checker", '', "08 - Ardennes", "08", "Ardennes" - it_behaves_like "a result checker", "09", "09 - Ariège", "09", "Ariège" - it_behaves_like "a result checker", nil, "09 - Ariège", "09", "Ariège" - it_behaves_like "a result checker", '', "09 - Ariège", "09", "Ariège" - it_behaves_like "a result checker", nil, "10 - Aube", "10", "Aube" - it_behaves_like "a result checker", "10", "10 - Aube", "10", "Aube" - it_behaves_like "a result checker", '', "10 - Aube", "10", "Aube" - it_behaves_like "a result checker", "11", "11 - Aude", "11", "Aude" - it_behaves_like "a result checker", nil, "11 - Aude", "11", "Aude" - it_behaves_like "a result checker", '', "11 - Aude", "11", "Aude" - it_behaves_like "a result checker", "12", "12 - Aveyron", "12", "Aveyron" - it_behaves_like "a result checker", nil, "12 - Aveyron", "12", "Aveyron" - it_behaves_like "a result checker", '', "12 - Aveyron", "12", "Aveyron" - it_behaves_like "a result checker", "13", "13 - Bouches-du-Rhône", "13", "Bouches-du-Rhône" - it_behaves_like "a result checker", nil, "13 - Bouches-du-Rhône", "13", "Bouches-du-Rhône" - it_behaves_like "a result checker", '', "13 - Bouches-du-Rhône", "13", "Bouches-du-Rhône" - it_behaves_like "a result checker", "14", "14 - Calvados", "14", "Calvados" - it_behaves_like "a result checker", nil, "14 - Calvados", "14", "Calvados" - it_behaves_like "a result checker", '', "14 - Calvados", "14", "Calvados" - it_behaves_like "a result checker", "15", "15 - Cantal", "15", "Cantal" - it_behaves_like "a result checker", nil, "15 - Cantal", "15", "Cantal" - it_behaves_like "a result checker", '', "15 - Cantal", "15", "Cantal" - it_behaves_like "a result checker", "16", "16 - Charente", "16", "Charente" - it_behaves_like "a result checker", nil, "16 - Charente", "16", "Charente" - it_behaves_like "a result checker", '', "16 - Charente", "16", "Charente" - it_behaves_like "a result checker", "17", "17 - Charente-Maritime", "17", "Charente-Maritime" - it_behaves_like "a result checker", nil, "17 - Charente-Maritime", "17", "Charente-Maritime" - it_behaves_like "a result checker", '', "17 - Charente-Maritime", "17", "Charente-Maritime" - it_behaves_like "a result checker", "18", "18 - Cher", "18", "Cher" - it_behaves_like "a result checker", nil, "18 - Cher", "18", "Cher" - it_behaves_like "a result checker", '', "18 - Cher", "18", "Cher" - it_behaves_like "a result checker", "19", "19 - Corrèze", "19", "Corrèze" - it_behaves_like "a result checker", nil, "19 - Corrèze", "19", "Corrèze" - it_behaves_like "a result checker", '', "19 - Corrèze", "19", "Corrèze" - it_behaves_like "a result checker", "21", "21 - Côte-d’Or", "21", "Côte-d’Or" - it_behaves_like "a result checker", '', "21 - Côte-d’Or", "21", "Côte-d’Or" - it_behaves_like "a result checker", nil, "21 - Côte-d’Or", "21", "Côte-d’Or" - it_behaves_like "a result checker", "22", "22 - Côtes-d’Armor", "22", "Côtes-d’Armor" - it_behaves_like "a result checker", nil, "22 - Côtes-d’Armor", "22", "Côtes-d’Armor" - it_behaves_like "a result checker", '', "22 - Côtes-d’Armor", "22", "Côtes-d’Armor" - it_behaves_like "a result checker", "23", "23 - Creuse", "23", "Creuse" - it_behaves_like "a result checker", nil, "23 - Creuse", "23", "Creuse" - it_behaves_like "a result checker", '', "23 - Creuse", "23", "Creuse" - it_behaves_like "a result checker", "24", "24 - Dordogne", "24", "Dordogne" - it_behaves_like "a result checker", nil, "24 - Dordogne", "24", "Dordogne" - it_behaves_like "a result checker", '', "24 - Dordogne", "24", "Dordogne" - it_behaves_like "a result checker", "25", "25 - Doubs", "25", "Doubs" - it_behaves_like "a result checker", nil, "25 - Doubs", "25", "Doubs" - it_behaves_like "a result checker", '', "25 - Doubs", "25", "Doubs" - it_behaves_like "a result checker", "26", "26 - Drôme", "26", "Drôme" - it_behaves_like "a result checker", nil, "26 - Drôme", "26", "Drôme" - it_behaves_like "a result checker", '', "26 - Drôme", "26", "Drôme" - it_behaves_like "a result checker", "27", "27 - Eure", "27", "Eure" - it_behaves_like "a result checker", nil, "27 - Eure", "27", "Eure" - it_behaves_like "a result checker", '', "27 - Eure", "27", "Eure" - it_behaves_like "a result checker", "28", "28 - Eure-et-Loir", "28", "Eure-et-Loir" - it_behaves_like "a result checker", nil, "28 - Eure-et-Loir", "28", "Eure-et-Loir" - it_behaves_like "a result checker", '', "28 - Eure-et-Loir", "28", "Eure-et-Loir" - it_behaves_like "a result checker", "29", "29 - Finistère", "29", "Finistère" - it_behaves_like "a result checker", nil, "29 - Finistère", "29", "Finistère" - it_behaves_like "a result checker", '', "29 - Finistère", "29", "Finistère" - it_behaves_like "a result checker", "2A", "2A - Corse-du-Sud", "2A", "Corse-du-Sud" - it_behaves_like "a result checker", nil, "2A - Corse-du-Sud", "2A", "Corse-du-Sud" - it_behaves_like "a result checker", '', "2A - Corse-du-Sud", "2A", "Corse-du-Sud" - it_behaves_like "a result checker", "2B", "2B - Haute-Corse", "2B", "Haute-Corse" - it_behaves_like "a result checker", nil, "2B - Haute-Corse", "2B", "Haute-Corse" - it_behaves_like "a result checker", '', "2B - Haute-Corse", "2B", "Haute-Corse" - it_behaves_like "a result checker", "30", "30 - Gard", "30", "Gard" - it_behaves_like "a result checker", nil, "30 - Gard", "30", "Gard" - it_behaves_like "a result checker", '', "30 - Gard", "30", "Gard" - it_behaves_like "a result checker", "31", "31 - Haute-Garonne", "31", "Haute-Garonne" - it_behaves_like "a result checker", nil, "31 - Haute-Garonne", "31", "Haute-Garonne" - it_behaves_like "a result checker", '', "31 - Haute-Garonne", "31", "Haute-Garonne" - it_behaves_like "a result checker", "32", "32 - Gers", "32", "Gers" - it_behaves_like "a result checker", nil, "32 - Gers", "32", "Gers" - it_behaves_like "a result checker", '', "32 - Gers", "32", "Gers" - it_behaves_like "a result checker", "33", "33 - Gironde", "33", "Gironde" - it_behaves_like "a result checker", nil, "33 - Gironde", "33", "Gironde" - it_behaves_like "a result checker", '', "33 - Gironde", "33", "Gironde" - it_behaves_like "a result checker", "34", "34 - Hérault", "34", "Hérault" - it_behaves_like "a result checker", nil, "34 - Hérault", "34", "Hérault" - it_behaves_like "a result checker", '', "34 - Hérault", "34", "Hérault" - it_behaves_like "a result checker", "35", "35 - Ille-et-Vilaine", "35", "Ille-et-Vilaine" - it_behaves_like "a result checker", nil, "35 - Ille-et-Vilaine", "35", "Ille-et-Vilaine" - it_behaves_like "a result checker", '', "35 - Ille-et-Vilaine", "35", "Ille-et-Vilaine" - it_behaves_like "a result checker", "36", "36 - Indre", "36", "Indre" - it_behaves_like "a result checker", nil, "36 - Indre", "36", "Indre" - it_behaves_like "a result checker", '', "36 - Indre", "36", "Indre" - it_behaves_like "a result checker", "37", "37 - Indre-et-Loire", "37", "Indre-et-Loire" - it_behaves_like "a result checker", nil, "37 - Indre-et-Loire", "37", "Indre-et-Loire" - it_behaves_like "a result checker", '', "37 - Indre-et-Loire", "37", "Indre-et-Loire" - it_behaves_like "a result checker", "38", "38 - Isère", "38", "Isère" - it_behaves_like "a result checker", nil, "38 - Isère", "38", "Isère" - it_behaves_like "a result checker", '', "38 - Isère", "38", "Isère" - it_behaves_like "a result checker", "39", "39 - Jura", "39", "Jura" - it_behaves_like "a result checker", nil, "39 - Jura", "39", "Jura" - it_behaves_like "a result checker", '', "39 - Jura", "39", "Jura" - it_behaves_like "a result checker", "40", "40 - Landes", "40", "Landes" - it_behaves_like "a result checker", nil, "40 - Landes", "40", "Landes" - it_behaves_like "a result checker", '', "40 - Landes", "40", "Landes" - it_behaves_like "a result checker", "41", "41 - Loir-et-Cher", "41", "Loir-et-Cher" - it_behaves_like "a result checker", nil, "41 - Loir-et-Cher", "41", "Loir-et-Cher" - it_behaves_like "a result checker", '', "41 - Loir-et-Cher", "41", "Loir-et-Cher" - it_behaves_like "a result checker", "42", "42 - Loire", "42", "Loire" - it_behaves_like "a result checker", nil, "42 - Loire", "42", "Loire" - it_behaves_like "a result checker", '', "42 - Loire", "42", "Loire" - it_behaves_like "a result checker", "43", "43 - Haute-Loire", "43", "Haute-Loire" - it_behaves_like "a result checker", nil, "43 - Haute-Loire", "43", "Haute-Loire" - it_behaves_like "a result checker", '', "43 - Haute-Loire", "43", "Haute-Loire" - it_behaves_like "a result checker", "44", "44 - Loire-Atlantique", "44", "Loire-Atlantique" - it_behaves_like "a result checker", nil, "44 - Loire-Atlantique", "44", "Loire-Atlantique" - it_behaves_like "a result checker", '', "44 - Loire-Atlantique", "44", "Loire-Atlantique" - it_behaves_like "a result checker", "45", "45 - Loiret", "45", "Loiret" - it_behaves_like "a result checker", nil, "45 - Loiret", "45", "Loiret" - it_behaves_like "a result checker", '', "45 - Loiret", "45", "Loiret" - it_behaves_like "a result checker", "46", "46 - Lot", "46", "Lot" - it_behaves_like "a result checker", nil, "46 - Lot", "46", "Lot" - it_behaves_like "a result checker", '', "46 - Lot", "46", "Lot" - it_behaves_like "a result checker", "47", "47 - Lot-et-Garonne", "47", "Lot-et-Garonne" - it_behaves_like "a result checker", nil, "47 - Lot-et-Garonne", "47", "Lot-et-Garonne" - it_behaves_like "a result checker", '', "47 - Lot-et-Garonne", "47", "Lot-et-Garonne" - it_behaves_like "a result checker", "48", "48 - Lozère", "48", "Lozère" - it_behaves_like "a result checker", nil, "48 - Lozère", "48", "Lozère" - it_behaves_like "a result checker", '', "48 - Lozère", "48", "Lozère" - it_behaves_like "a result checker", "49", "49 - Maine-et-Loire", "49", "Maine-et-Loire" - it_behaves_like "a result checker", nil, "49 - Maine-et-Loire", "49", "Maine-et-Loire" - it_behaves_like "a result checker", '', "49 - Maine-et-Loire", "49", "Maine-et-Loire" - it_behaves_like "a result checker", "50", "50 - Manche", "50", "Manche" - it_behaves_like "a result checker", nil, "50 - Manche", "50", "Manche" - it_behaves_like "a result checker", '', "50 - Manche", "50", "Manche" - it_behaves_like "a result checker", "51", "51 - Marne", "51", "Marne" - it_behaves_like "a result checker", '', "51 - Marne", "51", "Marne" - it_behaves_like "a result checker", nil, "51 - Marne", "51", "Marne" - it_behaves_like "a result checker", "52", "52 - Haute-Marne", "52", "Haute-Marne" - it_behaves_like "a result checker", nil, "52 - Haute-Marne", "52", "Haute-Marne" - it_behaves_like "a result checker", '', "52 - Haute-Marne", "52", "Haute-Marne" - it_behaves_like "a result checker", "53", "53 - Mayenne", "53", "Mayenne" - it_behaves_like "a result checker", nil, "53 - Mayenne", "53", "Mayenne" - it_behaves_like "a result checker", '', "53 - Mayenne", "53", "Mayenne" - it_behaves_like "a result checker", "54", "54 - Meurthe-et-Moselle", "54", "Meurthe-et-Moselle" - it_behaves_like "a result checker", '', "54 - Meurthe-et-Moselle", "54", "Meurthe-et-Moselle" - it_behaves_like "a result checker", nil, "54 - Meurthe-et-Moselle", "54", "Meurthe-et-Moselle" - it_behaves_like "a result checker", nil, "55 - Meuse", "55", "Meuse" - it_behaves_like "a result checker", "55", "55 - Meuse", "55", "Meuse" - it_behaves_like "a result checker", '', "55 - Meuse", "55", "Meuse" - it_behaves_like "a result checker", "56", "56 - Morbihan", "56", "Morbihan" - it_behaves_like "a result checker", nil, "56 - Morbihan", "56", "Morbihan" - it_behaves_like "a result checker", '', "56 - Morbihan", "56", "Morbihan" - it_behaves_like "a result checker", "57", "57 - Moselle", "57", "Moselle" - it_behaves_like "a result checker", nil, "57 - Moselle", "57", "Moselle" - it_behaves_like "a result checker", '', "57 - Moselle", "57", "Moselle" - it_behaves_like "a result checker", "58", "58 - Nièvre", "58", "Nièvre" - it_behaves_like "a result checker", nil, "58 - Nièvre", "58", "Nièvre" - it_behaves_like "a result checker", '', "58 - Nièvre", "58", "Nièvre" - it_behaves_like "a result checker", "59", "59 - Nord", "59", "Nord" - it_behaves_like "a result checker", nil, "59 - Nord", "59", "Nord" - it_behaves_like "a result checker", '', "59 - Nord", "59", "Nord" - it_behaves_like "a result checker", "60", "60 - Oise", "60", "Oise" - it_behaves_like "a result checker", nil, "60 - Oise", "60", "Oise" - it_behaves_like "a result checker", '', "60 - Oise", "60", "Oise" - it_behaves_like "a result checker", "61", "61 - Orne", "61", "Orne" - it_behaves_like "a result checker", nil, "61 - Orne", "61", "Orne" - it_behaves_like "a result checker", '', "61 - Orne", "61", "Orne" - it_behaves_like "a result checker", nil, "62 - Pas-de-Calais", "62", "Pas-de-Calais" - it_behaves_like "a result checker", "62", "62 - Pas-de-Calais", "62", "Pas-de-Calais" - it_behaves_like "a result checker", '', "62 - Pas-de-Calais", "62", "Pas-de-Calais" - it_behaves_like "a result checker", "63", "63 - Puy-de-Dôme", "63", "Puy-de-Dôme" - it_behaves_like "a result checker", nil, "63 - Puy-de-Dôme", "63", "Puy-de-Dôme" - it_behaves_like "a result checker", '', "63 - Puy-de-Dôme", "63", "Puy-de-Dôme" - it_behaves_like "a result checker", "64", "64 - Pyrénées-Atlantiques", "64", "Pyrénées-Atlantiques" - it_behaves_like "a result checker", nil, "64 - Pyrénées-Atlantiques", "64", "Pyrénées-Atlantiques" - it_behaves_like "a result checker", '', "64 - Pyrénées-Atlantiques", "64", "Pyrénées-Atlantiques" - it_behaves_like "a result checker", "65", "65 - Hautes-Pyrénées", "65", "Hautes-Pyrénées" - it_behaves_like "a result checker", nil, "65 - Hautes-Pyrénées", "65", "Hautes-Pyrénées" - it_behaves_like "a result checker", '', "65 - Hautes-Pyrénées", "65", "Hautes-Pyrénées" - it_behaves_like "a result checker", "66", "66 - Pyrénées-Orientales", "66", "Pyrénées-Orientales" - it_behaves_like "a result checker", nil, "66 - Pyrénées-Orientales", "66", "Pyrénées-Orientales" - it_behaves_like "a result checker", '', "66 - Pyrénées-Orientales", "66", "Pyrénées-Orientales" - it_behaves_like "a result checker", "67", "67 - Bas-Rhin", "67", "Bas-Rhin" - it_behaves_like "a result checker", nil, "67 - Bas-Rhin", "67", "Bas-Rhin" - it_behaves_like "a result checker", '', "67 - Bas-Rhin", "67", "Bas-Rhin" - it_behaves_like "a result checker", "68", "68 - Haut-Rhin", "68", "Haut-Rhin" - it_behaves_like "a result checker", nil, "68 - Haut-Rhin", "68", "Haut-Rhin" - it_behaves_like "a result checker", '', "68 - Haut-Rhin", "68", "Haut-Rhin" - it_behaves_like "a result checker", "69", "69 - Rhône", "69", "Rhône" - it_behaves_like "a result checker", nil, "69 - Rhône", "69", "Rhône" - it_behaves_like "a result checker", '', "69 - Rhône", "69", "Rhône" - it_behaves_like "a result checker", "70", "70 - Haute-Saône", "70", "Haute-Saône" - it_behaves_like "a result checker", nil, "70 - Haute-Saône", "70", "Haute-Saône" - it_behaves_like "a result checker", '', "70 - Haute-Saône", "70", "Haute-Saône" - it_behaves_like "a result checker", "71", "71 - Saône-et-Loire", "71", "Saône-et-Loire" - it_behaves_like "a result checker", nil, "71 - Saône-et-Loire", "71", "Saône-et-Loire" - it_behaves_like "a result checker", '', "71 - Saône-et-Loire", "71", "Saône-et-Loire" - it_behaves_like "a result checker", "72", "72 - Sarthe", "72", "Sarthe" - it_behaves_like "a result checker", nil, "72 - Sarthe", "72", "Sarthe" - it_behaves_like "a result checker", '', "72 - Sarthe", "72", "Sarthe" - it_behaves_like "a result checker", "73", "73 - Savoie", "73", "Savoie" - it_behaves_like "a result checker", nil, "73 - Savoie", "73", "Savoie" - it_behaves_like "a result checker", '', "73 - Savoie", "73", "Savoie" - it_behaves_like "a result checker", "74", "74 - Haute-Savoie", "74", "Haute-Savoie" - it_behaves_like "a result checker", nil, "74 - Haute-Savoie", "74", "Haute-Savoie" - it_behaves_like "a result checker", '', "74 - Haute-Savoie", "74", "Haute-Savoie" - it_behaves_like "a result checker", "75", "75 - Paris", "75", "Paris" - it_behaves_like "a result checker", nil, "75 - Paris", "75", "Paris" - it_behaves_like "a result checker", '', "75 - Paris", "75", "Paris" - it_behaves_like "a result checker", "76", "76 - Seine-Maritime", "76", "Seine-Maritime" - it_behaves_like "a result checker", nil, "76 - Seine-Maritime", "76", "Seine-Maritime" - it_behaves_like "a result checker", '', "76 - Seine-Maritime", "76", "Seine-Maritime" - it_behaves_like "a result checker", "77", "77 - Seine-et-Marne", "77", "Seine-et-Marne" - it_behaves_like "a result checker", nil, "77 - Seine-et-Marne", "77", "Seine-et-Marne" - it_behaves_like "a result checker", '', "77 - Seine-et-Marne", "77", "Seine-et-Marne" - it_behaves_like "a result checker", "78", "78 - Yvelines", "78", "Yvelines" - it_behaves_like "a result checker", '', "78 - Yvelines", "78", "Yvelines" - it_behaves_like "a result checker", nil, "78 - Yvelines", "78", "Yvelines" - it_behaves_like "a result checker", "79", "79 - Deux-Sèvres", "79", "Deux-Sèvres" - it_behaves_like "a result checker", nil, "79 - Deux-Sèvres", "79", "Deux-Sèvres" - it_behaves_like "a result checker", '', "79 - Deux-Sèvres", "79", "Deux-Sèvres" - it_behaves_like "a result checker", "80", "80 - Somme", "80", "Somme" - it_behaves_like "a result checker", nil, "80 - Somme", "80", "Somme" - it_behaves_like "a result checker", '', "80 - Somme", "80", "Somme" - it_behaves_like "a result checker", "81", "81 - Tarn", "81", "Tarn" - it_behaves_like "a result checker", '', "81 - Tarn", "81", "Tarn" - it_behaves_like "a result checker", nil, "81 - Tarn", "81", "Tarn" - it_behaves_like "a result checker", "82", "82 - Tarn-et-Garonne", "82", "Tarn-et-Garonne" - it_behaves_like "a result checker", nil, "82 - Tarn-et-Garonne", "82", "Tarn-et-Garonne" - it_behaves_like "a result checker", '', "82 - Tarn-et-Garonne", "82", "Tarn-et-Garonne" - it_behaves_like "a result checker", "83", "83 - Var", "83", "Var" - it_behaves_like "a result checker", nil, "83 - Var", "83", "Var" - it_behaves_like "a result checker", '', "83 - Var", "83", "Var" - it_behaves_like "a result checker", "84", "84 - Vaucluse", "84", "Vaucluse" - it_behaves_like "a result checker", nil, "84 - Vaucluse", "84", "Vaucluse" - it_behaves_like "a result checker", '', "84 - Vaucluse", "84", "Vaucluse" - it_behaves_like "a result checker", nil, "85", "85", "Vendée" - it_behaves_like "a result checker", "85", "85 - Vendée", "85", "Vendée" - it_behaves_like "a result checker", nil, "85 - Vendée", "85", "Vendée" - it_behaves_like "a result checker", '', "85 - Vendée", "85", "Vendée" - it_behaves_like "a result checker", "86", "86 - Vienne", "86", "Vienne" - it_behaves_like "a result checker", nil, "86 - Vienne", "86", "Vienne" - it_behaves_like "a result checker", '', "86 - Vienne", "86", "Vienne" - it_behaves_like "a result checker", "87", "87 - Haute-Vienne", "87", "Haute-Vienne" - it_behaves_like "a result checker", nil, "87 - Haute-Vienne", "87", "Haute-Vienne" - it_behaves_like "a result checker", '', "87 - Haute-Vienne", "87", "Haute-Vienne" - it_behaves_like "a result checker", "88", "88 - Vosges", "88", "Vosges" - it_behaves_like "a result checker", nil, "88 - Vosges", "88", "Vosges" - it_behaves_like "a result checker", '', "88 - Vosges", "88", "Vosges" - it_behaves_like "a result checker", "89", "89 - Yonne", "89", "Yonne" - it_behaves_like "a result checker", nil, "89 - Yonne", "89", "Yonne" - it_behaves_like "a result checker", '', "89 - Yonne", "89", "Yonne" - it_behaves_like "a result checker", "90", "90 - Territoire de Belfort", "90", "Territoire de Belfort" - it_behaves_like "a result checker", nil, "90 - Territoire de Belfort", "90", "Territoire de Belfort" - it_behaves_like "a result checker", '', "90 - Territoire de Belfort", "90", "Territoire de Belfort" - it_behaves_like "a result checker", "91", "91 - Essonne", "91", "Essonne" - it_behaves_like "a result checker", nil, "91 - Essonne", "91", "Essonne" - it_behaves_like "a result checker", '', "91 - Essonne", "91", "Essonne" - it_behaves_like "a result checker", "92", "92 - Hauts-de-Seine", "92", "Hauts-de-Seine" - it_behaves_like "a result checker", nil, "92 - Hauts-de-Seine", "92", "Hauts-de-Seine" - it_behaves_like "a result checker", '', "92 - Hauts-de-Seine", "92", "Hauts-de-Seine" - it_behaves_like "a result checker", "93", "93 - Seine-Saint-Denis", "93", "Seine-Saint-Denis" - it_behaves_like "a result checker", nil, "93 - Seine-Saint-Denis", "93", "Seine-Saint-Denis" - it_behaves_like "a result checker", '', "93 - Seine-Saint-Denis", "93", "Seine-Saint-Denis" - it_behaves_like "a result checker", "94", "94 - Val-de-Marne", "94", "Val-de-Marne" - it_behaves_like "a result checker", nil, "94 - Val-de-Marne", "94", "Val-de-Marne" - it_behaves_like "a result checker", '', "94 - Val-de-Marne", "94", "Val-de-Marne" - it_behaves_like "a result checker", "95", "95 - Val-d’Oise", "95", "Val-d’Oise" - it_behaves_like "a result checker", '', "95 - Val-d’Oise", "95", "Val-d’Oise" - it_behaves_like "a result checker", nil, "95 - Val-d’Oise", "95", "Val-d’Oise" - it_behaves_like "a result checker", "971", "971 - Guadeloupe", "971", "Guadeloupe" - it_behaves_like "a result checker", nil, "971 - Guadeloupe", "971", "Guadeloupe" - it_behaves_like "a result checker", '', "971 - Guadeloupe", "971", "Guadeloupe" - it_behaves_like "a result checker", "972", "972 - Martinique", "972", "Martinique" - it_behaves_like "a result checker", nil, "972 - Martinique", "972", "Martinique" - it_behaves_like "a result checker", '', "972 - Martinique", "972", "Martinique" - it_behaves_like "a result checker", "973", "973 - Guyane", "973", "Guyane" - it_behaves_like "a result checker", nil, "973 - Guyane", "973", "Guyane" - it_behaves_like "a result checker", '', "973 - Guyane", "973", "Guyane" - it_behaves_like "a result checker", "974", "974 - La Réunion", "974", "La Réunion" - it_behaves_like "a result checker", nil, "974 - La Réunion", "974", "La Réunion" - it_behaves_like "a result checker", '', "974 - La Réunion", "974", "La Réunion" - it_behaves_like "a result checker", "976", "976 - Mayotte", "976", "Mayotte" - it_behaves_like "a result checker", nil, "976 - Mayotte", "976", "Mayotte" - it_behaves_like "a result checker", '', "976 - Mayotte", "976", "Mayotte" - it_behaves_like "a result checker", "99", "99 - Etranger", "99", "Etranger" - it_behaves_like "a result checker", nil, "99 - Etranger", "99", "Etranger" - it_behaves_like "a result checker", '', "99 - Etranger", "99", "Etranger" - it_behaves_like "a result checker", '', "99 - Étranger", "99", "Etranger" - it_behaves_like "a result checker", nil, "99 - Étranger", "99", "Etranger" -end From 68de0c656650105b3331b59853389680859d4751 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 17 Feb 2023 14:39:59 +0100 Subject: [PATCH 081/202] =?UTF-8?q?correctif(procedures#filtres):=20le=20f?= =?UTF-8?q?iltre=20par=20departement=20ne=20fonctionnait=20plus.=20utilise?= =?UTF-8?q?=20un=20enum=20sur=20la=20collection=20des=20departements=20pos?= =?UTF-8?q?sible=20normalis=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/champ.rb | 1 + app/models/procedure_presentation.rb | 6 +++-- app/models/type_de_champ.rb | 10 +++++++ spec/factories/champ.rb | 5 ++-- .../instructeurs/procedure_filters_spec.rb | 26 ++++++++++++++++++- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/app/models/champ.rb b/app/models/champ.rb index 02e6053e8..4f2f9533f 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -53,6 +53,7 @@ class Champ < ApplicationRecord :repetition?, :block?, :dossier_link?, + :departement?, :titre_identite?, :header_section?, :simple_drop_down_list?, diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 4f536db34..8ea92b5f0 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -89,8 +89,8 @@ class ProcedurePresentation < ApplicationRecord end fields.concat procedure.types_de_champ_for_procedure_presentation - .pluck(:libelle, :private, :stable_id) - .map { |(libelle, is_private, stable_id)| field_hash(is_private ? TYPE_DE_CHAMP_PRIVATE : TYPE_DE_CHAMP, stable_id.to_s, label: libelle, type: :text) } + .pluck(:type_champ, :libelle, :private, :stable_id) + .map { |(type_champ, libelle, is_private, stable_id)| field_hash(is_private ? TYPE_DE_CHAMP_PRIVATE : TYPE_DE_CHAMP, stable_id.to_s, label: libelle, type: (type_champ == TypeDeChamp.type_champs.fetch(:departements) ? :enum : :text)) } fields end @@ -335,6 +335,8 @@ class ProcedurePresentation < ApplicationRecord [_1.label, _1.id] end end + else + TypeDeChamp.find(field['column']).options_for_select end end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 354711ef2..33c37148a 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -369,6 +369,10 @@ class TypeDeChamp < ApplicationRecord type_champ == TypeDeChamp.type_champs.fetch(:pole_emploi) end + def departement? + type_champ == TypeDeChamp.type_champs.fetch(:departements) + end + def mesri? type_champ == TypeDeChamp.type_champs.fetch(:mesri) end @@ -405,6 +409,12 @@ class TypeDeChamp < ApplicationRecord self.drop_down_options = parse_drop_down_list_value(value) end + def options_for_select + if departement? + APIGeoService.departements.map { ["#{_1[:code]} – #{_1[:name]}", _1[:name]] } + end + end + # historicaly we added a blank ("") option by default to avoid wrong selection # see self.parse_drop_down_list_value # then rails decided to add this blank ("") option when the select is required diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index d1f4448f8..52f740f5b 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -117,12 +117,13 @@ FactoryBot.define do factory :champ_departements, class: 'Champs::DepartementChamp' do type_de_champ { association :type_de_champ_departements, procedure: dossier.procedure } - value { '01' } end factory :champ_communes, class: 'Champs::CommuneChamp' do type_de_champ { association :type_de_champ_communes, procedure: dossier.procedure } - value { 'Paris' } + value { 'Coye-la-Forêt (60580)' } + value_json { { "departement" => "Oise", "code_departement" => "60" } } + external_id { { external_id: "60172" } } end factory :champ_epci, class: 'Champs::EpciChamp' do diff --git a/spec/system/instructeurs/procedure_filters_spec.rb b/spec/system/instructeurs/procedure_filters_spec.rb index 86430d082..b8bdd10b0 100644 --- a/spec/system/instructeurs/procedure_filters_spec.rb +++ b/spec/system/instructeurs/procedure_filters_spec.rb @@ -1,6 +1,6 @@ describe "procedure filters" do let(:instructeur) { create(:instructeur) } - let(:procedure) { create(:procedure, :published, :with_type_de_champ, instructeurs: [instructeur]) } + let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_departement, instructeurs: [instructeur]) } let!(:type_de_champ) { procedure.active_revision.types_de_champ_public.first } let!(:new_unfollow_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) } let!(:champ) { Champ.find_by(type_de_champ_id: type_de_champ.id, dossier_id: new_unfollow_dossier.id) } @@ -94,6 +94,30 @@ describe "procedure filters" do click_button "Ajouter le filtre" end + describe 'with a vcr cassette', vcr: { cassette_name: 'api_geo_departements' } do + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + + scenario "should be able to find by departements with custom enum lookup", js: true do + departement_champ = new_unfollow_dossier.champs.find(&:departement?) + departement_champ.update!(value: 'Oise', external_id: '60') + departement_champ.reload + champ_select_value = "#{departement_champ.external_id} – #{departement_champ.value}" + + click_on 'Sélectionner un filtre' + select departement_champ.libelle, from: "Colonne" + find("select#value", visible: true) + select champ_select_value, from: "Valeur" + click_button "Ajouter le filtre" + find("select#value", visible: false) # w8 for filter to be applied + expect(page).to have_link(new_unfollow_dossier.id.to_s) + end + end + scenario "should be able to add and remove two filters for the same field", js: true do add_filter(type_de_champ.libelle, champ.value) add_filter(type_de_champ.libelle, champ_2.value) From b0a757a89d3c7640273f51a377d7bb04779f24e0 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 17 Feb 2023 14:41:13 +0100 Subject: [PATCH 082/202] =?UTF-8?q?chore(db):=20supprime=20un=20index=20no?= =?UTF-8?q?n=20utilis=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0230216041517_remove_champs_external_id_index.rb | 13 +++++++++++++ db/schema.rb | 3 +-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20230216041517_remove_champs_external_id_index.rb diff --git a/db/migrate/20230216041517_remove_champs_external_id_index.rb b/db/migrate/20230216041517_remove_champs_external_id_index.rb new file mode 100644 index 000000000..1e5863b25 --- /dev/null +++ b/db/migrate/20230216041517_remove_champs_external_id_index.rb @@ -0,0 +1,13 @@ +class RemoveChampsExternalIdIndex < ActiveRecord::Migration[6.1] + include Database::MigrationHelpers + + disable_ddl_transaction! + + def up + remove_index :champs, column: :external_id + end + + def down + add_concurrent_index :champs, :external_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 14d0f9498..8719631c4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -227,13 +227,12 @@ ActiveRecord::Schema.define(version: 2023_02_16_141558) do t.datetime "rebased_at" t.string "row_id" t.string "type" - t.integer "type_de_champ_id", null: false + t.integer "type_de_champ_id" t.datetime "updated_at" t.string "value" t.jsonb "value_json" t.index ["dossier_id"], name: "index_champs_on_dossier_id" t.index ["etablissement_id"], name: "index_champs_on_etablissement_id" - t.index ["external_id"], name: "index_champs_on_external_id" t.index ["parent_id"], name: "index_champs_on_parent_id" t.index ["private"], name: "index_champs_on_private" t.index ["row_id"], name: "index_champs_on_row_id" From a89f5d373f016d28643cec7352678a32e954e7b0 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 20 Feb 2023 06:40:57 +0100 Subject: [PATCH 083/202] =?UTF-8?q?amelioration(procedures#filtres):=20fil?= =?UTF-8?q?tre=20par=20regions=20avec=20un=20enum=20sur=20la=20collection?= =?UTF-8?q?=20des=20regions=20normalis=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/champ.rb | 1 + app/models/procedure_presentation.rb | 2 +- app/models/type_de_champ.rb | 13 ++++++++++ spec/factories/procedure.rb | 6 +++++ .../instructeurs/procedure_filters_spec.rb | 26 ++++++++++++++++++- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/models/champ.rb b/app/models/champ.rb index 4f2f9533f..ab8a9e252 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -54,6 +54,7 @@ class Champ < ApplicationRecord :block?, :dossier_link?, :departement?, + :region?, :titre_identite?, :header_section?, :simple_drop_down_list?, diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 8ea92b5f0..9c2942884 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -90,7 +90,7 @@ class ProcedurePresentation < ApplicationRecord fields.concat procedure.types_de_champ_for_procedure_presentation .pluck(:type_champ, :libelle, :private, :stable_id) - .map { |(type_champ, libelle, is_private, stable_id)| field_hash(is_private ? TYPE_DE_CHAMP_PRIVATE : TYPE_DE_CHAMP, stable_id.to_s, label: libelle, type: (type_champ == TypeDeChamp.type_champs.fetch(:departements) ? :enum : :text)) } + .map { |(type_champ, libelle, is_private, stable_id)| field_hash(is_private ? TYPE_DE_CHAMP_PRIVATE : TYPE_DE_CHAMP, stable_id.to_s, label: libelle, type: (TypeDeChamp.options_for_select?(type_champ) ? :enum : :text)) } fields end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 33c37148a..ca681b213 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -373,6 +373,10 @@ class TypeDeChamp < ApplicationRecord type_champ == TypeDeChamp.type_champs.fetch(:departements) end + def region? + type_champ == TypeDeChamp.type_champs.fetch(:regions) + end + def mesri? type_champ == TypeDeChamp.type_champs.fetch(:mesri) end @@ -409,9 +413,18 @@ class TypeDeChamp < ApplicationRecord self.drop_down_options = parse_drop_down_list_value(value) end + def self.options_for_select?(type_champs) + [ + TypeDeChamp.type_champs.fetch(:departements), + TypeDeChamp.type_champs.fetch(:regions) + ].include?(type_champs) + end + def options_for_select if departement? APIGeoService.departements.map { ["#{_1[:code]} – #{_1[:name]}", _1[:name]] } + elsif region? + APIGeoService.regions.map { [_1[:name], _1[:name]] } end end diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index 2d700d783..263af1076 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -206,6 +206,12 @@ FactoryBot.define do end end + trait :with_region do + after(:build) do |procedure, _evaluator| + build(:type_de_champ_regions, procedure: procedure) + end + end + trait :with_piece_justificative do after(:build) do |procedure, _evaluator| build(:type_de_champ_piece_justificative, procedure: procedure) diff --git a/spec/system/instructeurs/procedure_filters_spec.rb b/spec/system/instructeurs/procedure_filters_spec.rb index b8bdd10b0..fedfbee29 100644 --- a/spec/system/instructeurs/procedure_filters_spec.rb +++ b/spec/system/instructeurs/procedure_filters_spec.rb @@ -1,6 +1,6 @@ describe "procedure filters" do let(:instructeur) { create(:instructeur) } - let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_departement, instructeurs: [instructeur]) } + let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_departement, :with_region, instructeurs: [instructeur]) } let!(:type_de_champ) { procedure.active_revision.types_de_champ_public.first } let!(:new_unfollow_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) } let!(:champ) { Champ.find_by(type_de_champ_id: type_de_champ.id, dossier_id: new_unfollow_dossier.id) } @@ -118,6 +118,30 @@ describe "procedure filters" do end end + describe 'with a vcr cassette', vcr: { cassette_name: 'api_geo_regions' } do + let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } + + before do + allow(Rails).to receive(:cache).and_return(memory_store) + Rails.cache.clear + end + + scenario "should be able to find by departements with custom enum lookup", js: true do + region_champ = new_unfollow_dossier.champs.find(&:regions?) + region_champ.update!(value: 'Bretagne', external_id: '53') + region_champ.reload + champ_select_value = "#{region_champ.external_id} – #{region_champ.value}" + + click_on 'Sélectionner un filtre' + select region_champ.libelle, from: "Colonne" + find("select#value", visible: true) + select champ_select_value, from: "Valeur" + click_button "Ajouter le filtre" + find("select#value", visible: false) # w8 for filter to be applied + expect(page).to have_link(new_unfollow_dossier.id.to_s) + end + end + scenario "should be able to add and remove two filters for the same field", js: true do add_filter(type_de_champ.libelle, champ.value) add_filter(type_de_champ.libelle, champ_2.value) From 5e10e680dc1bbf5725a743326ab605ef2177a731 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 20 Feb 2023 06:57:41 +0100 Subject: [PATCH 084/202] =?UTF-8?q?chore(spec):=20corrige=20les=20spec=20c?= =?UTF-8?q?ass=C3=A9es=20et=20remanie=20un=20peu=20des=20specs=20ecrites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/factories/champ.rb | 3 ++- .../instructeurs/procedure_filters_spec.rb | 20 +++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index 52f740f5b..ca13c4325 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -117,13 +117,14 @@ FactoryBot.define do factory :champ_departements, class: 'Champs::DepartementChamp' do type_de_champ { association :type_de_champ_departements, procedure: dossier.procedure } + value { '01' } end factory :champ_communes, class: 'Champs::CommuneChamp' do type_de_champ { association :type_de_champ_communes, procedure: dossier.procedure } value { 'Coye-la-Forêt (60580)' } value_json { { "departement" => "Oise", "code_departement" => "60" } } - external_id { { external_id: "60172" } } + external_id { "60172" } end factory :champ_epci, class: 'Champs::EpciChamp' do diff --git a/spec/system/instructeurs/procedure_filters_spec.rb b/spec/system/instructeurs/procedure_filters_spec.rb index fedfbee29..5bfdc175e 100644 --- a/spec/system/instructeurs/procedure_filters_spec.rb +++ b/spec/system/instructeurs/procedure_filters_spec.rb @@ -94,7 +94,7 @@ describe "procedure filters" do click_button "Ajouter le filtre" end - describe 'with a vcr cassette', vcr: { cassette_name: 'api_geo_departements' } do + describe 'with a vcr cached cassette' do let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } before do @@ -102,7 +102,7 @@ describe "procedure filters" do Rails.cache.clear end - scenario "should be able to find by departements with custom enum lookup", js: true do + scenario "should be able to find by departements with custom enum lookup", js: true, vcr: { cassette_name: 'api_geo_departements' } do departement_champ = new_unfollow_dossier.champs.find(&:departement?) departement_champ.update!(value: 'Oise', external_id: '60') departement_champ.reload @@ -116,26 +116,16 @@ describe "procedure filters" do find("select#value", visible: false) # w8 for filter to be applied expect(page).to have_link(new_unfollow_dossier.id.to_s) end - end - describe 'with a vcr cassette', vcr: { cassette_name: 'api_geo_regions' } do - let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } - - before do - allow(Rails).to receive(:cache).and_return(memory_store) - Rails.cache.clear - end - - scenario "should be able to find by departements with custom enum lookup", js: true do - region_champ = new_unfollow_dossier.champs.find(&:regions?) + scenario "should be able to find by region with custom enum lookup", js: true, vcr: { cassette_name: 'api_geo_regions' } do + region_champ = new_unfollow_dossier.champs.find(&:region?) region_champ.update!(value: 'Bretagne', external_id: '53') region_champ.reload - champ_select_value = "#{region_champ.external_id} – #{region_champ.value}" click_on 'Sélectionner un filtre' select region_champ.libelle, from: "Colonne" find("select#value", visible: true) - select champ_select_value, from: "Valeur" + select region_champ.value, from: "Valeur" click_button "Ajouter le filtre" find("select#value", visible: false) # w8 for filter to be applied expect(page).to have_link(new_unfollow_dossier.id.to_s) From 7c86df9d35b3567c0aeaac0511ab31ba14ca49fc Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 17 Feb 2023 14:27:03 +0100 Subject: [PATCH 085/202] clean(groupe instructeur mailer): remove unused view --- .../add_instructeurs.html.haml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 app/views/groupe_instructeur_mailer/add_instructeurs.html.haml diff --git a/app/views/groupe_instructeur_mailer/add_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/add_instructeurs.html.haml deleted file mode 100644 index ee899f212..000000000 --- a/app/views/groupe_instructeur_mailer/add_instructeurs.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -%p - Bonjour, - -%p - = t('administrateurs.groupe_instructeurs.add_instructeur.email_body', count: @new_instructeur_emails.size, emails: @new_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) - -%p - Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : - = link_to(@group.label, admin_procedure_groupe_instructeur_url(@group.procedure, @group)) - -= render partial: "layouts/mailers/signature" From 343cb5fce6e77c66e5b1cddb3fcb1b455b53a2fc Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 17 Feb 2023 15:24:12 +0100 Subject: [PATCH 086/202] feat(groupe instructeur mailer): add emailing to added instructeurs --- .../groupe_instructeurs_controller.rb | 4 +++ .../groupe_instructeurs_controller.rb | 4 +++ app/mailers/groupe_instructeur_mailer.rb | 18 ++++++++++-- .../notify_added_instructeurs.html.haml | 8 +++++ .../groupe_instructeurs/en.yml | 8 +++-- .../groupe_instructeurs/fr.yml | 8 +++-- .../groupe_instructeurs_controller_spec.rb | 13 ++++++++- .../mailers/groupe_instructeur_mailer_spec.rb | 29 +++++++++++++++++-- .../groupe_instructeur_mailer_preview.rb | 8 +++++ .../instructeurs/instructeur_creation_spec.rb | 2 +- 10 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 6ac2e3205..199b53b19 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -130,6 +130,10 @@ module Administrateurs else "Les instructeurs ont bien été affectés à la démarche" end + + GroupeInstructeurMailer + .notify_added_instructeurs(groupe_instructeur, instructeurs, current_administrateur.email) + .deliver_later end if procedure.routing_enabled? diff --git a/app/controllers/instructeurs/groupe_instructeurs_controller.rb b/app/controllers/instructeurs/groupe_instructeurs_controller.rb index 631b37afc..398cb35ba 100644 --- a/app/controllers/instructeurs/groupe_instructeurs_controller.rb +++ b/app/controllers/instructeurs/groupe_instructeurs_controller.rb @@ -22,6 +22,10 @@ module Instructeurs else groupe_instructeur.add(instructeur) flash[:notice] = "L’instructeur « #{instructeur_email} » a été affecté au groupe." + + GroupeInstructeurMailer + .notify_added_instructeurs(groupe_instructeur, [instructeur], current_user.email) + .deliver_later end redirect_to instructeur_groupe_path(procedure, groupe_instructeur) diff --git a/app/mailers/groupe_instructeur_mailer.rb b/app/mailers/groupe_instructeur_mailer.rb index fa438399d..f28fd5ca5 100644 --- a/app/mailers/groupe_instructeur_mailer.rb +++ b/app/mailers/groupe_instructeur_mailer.rb @@ -17,11 +17,25 @@ class GroupeInstructeurMailer < ApplicationMailer @current_instructeur_email = current_instructeur_email @still_assigned_to_procedure = removed_instructeur.in?(group.procedure.instructeurs) subject = if @still_assigned_to_procedure - "Vous avez été retiré du groupe \"#{group.label}\" de la démarche \"#{group.procedure.libelle}\"" + "Vous avez été retiré(e) du groupe \"#{group.label}\" de la démarche \"#{group.procedure.libelle}\"" else - "Vous avez été désaffecté de la démarche \"#{group.procedure.libelle}\"" + "Vous avez été désaffecté(e) de la démarche \"#{group.procedure.libelle}\"" end mail(to: removed_instructeur.email, subject: subject) end + + def notify_added_instructeurs(group, added_instructeurs, current_instructeur_email) + added_instructeur_emails = added_instructeurs.map(&:email) + @group = group + @current_instructeur_email = current_instructeur_email + + subject = if group.procedure.groupe_instructeurs.many? + "Vous avez été ajouté(e) au groupe \"#{group.label}\" de la démarche \"#{group.procedure.libelle}\"" + else + "Vous avez été affecté(e) à la démarche \"#{group.procedure.libelle}\"" + end + + mail(bcc: added_instructeur_emails, subject: subject) + end end diff --git a/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml new file mode 100644 index 000000000..5640e8d25 --- /dev/null +++ b/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml @@ -0,0 +1,8 @@ +%p + Bonjour, + +%p + - number_of_groups = @group.procedure.groupe_instructeurs.many? ? 'many_groups' : 'one_group' + = t("administrateurs.groupe_instructeurs.notify_added_instructeurs.#{number_of_groups}.email_body", groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + += render partial: "layouts/mailers/signature" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/en.yml b/config/locales/views/administrateurs/groupe_instructeurs/en.yml index 49b54a6db..0b7e05743 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/en.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/en.yml @@ -16,9 +16,6 @@ en: assignment: one: "The instructor %{emails} was assigned to the group « %{groupe} »." other: "The instructors %{emails} were assigned to the group « %{groupe} »." - email_body: - one: "The instructor %{emails} was assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - other: "The instructors %{emails} were assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." notify_group_when_instructeurs_removed: email_body: one: "The instructor %{emails} was removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." @@ -28,6 +25,11 @@ en: email_body: "You were unassigned from the procedure « %{procedure} » by « %{email} »." assigned: email_body: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." + notify_added_instructeurs: + one_group: + email_body: "You were assigned to the procedure « %{procedure} » by « %{email} »." + many_groups: + email_body: "You were assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} group exist" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml index 7ebd44c7d..686b9051d 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml @@ -22,9 +22,6 @@ fr: assignment: one: "L’instructeur %{emails} a été affecté au groupe « %{groupe} »." other: "Les instructeurs %{emails} ont été affectés au groupe « %{groupe} »." - email_body: - one: "L’instructeur %{emails} a été affecté au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - other: "Les instructeurs %{emails} ont été affectés au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." notify_group_when_instructeurs_removed: email_body: one: "L’instructeur %{emails} a été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." @@ -34,6 +31,11 @@ fr: email_body: "Vous avez été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." unassigned: email_body: "Vous avez été désaffecté de la démarche « %{procedure} » par « %{email} »." + notify_added_instructeurs: + one_group: + email_body: "Vous avez été affecté à la démarche « %{procedure} » par « %{email} »." + many_groups: + email_body: "Vous avez été ajouté au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} groupe existe" diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index 384a24756..b3b1f1a2f 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -268,11 +268,22 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do context 'of a news instructeurs' do let(:new_instructeur_emails) { ['new_i1@mail.com', 'new_i2@mail.com'] } - before { do_request } + before do + allow(GroupeInstructeurMailer).to receive(:notify_added_instructeurs) + .and_return(double(deliver_later: true)) + do_request + end it { expect(gi_1_2.instructeurs.pluck(:email)).to include(*new_instructeur_emails) } it { expect(flash.notice).to be_present } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) } it { expect(procedure.routing_enabled?).to be_truthy } + it "calls GroupeInstructeurMailer with the right params" do + expect(GroupeInstructeurMailer).to have_received(:notify_added_instructeurs).with( + gi_1_2, + gi_1_2.instructeurs.last(2), + admin.email + ) + end end context 'of an instructeur already in the group' do diff --git a/spec/mailers/groupe_instructeur_mailer_spec.rb b/spec/mailers/groupe_instructeur_mailer_spec.rb index 6cf1ed75e..73a1abaa9 100644 --- a/spec/mailers/groupe_instructeur_mailer_spec.rb +++ b/spec/mailers/groupe_instructeur_mailer_spec.rb @@ -39,7 +39,7 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do before { groupe_instructeur.remove(instructeur_to_remove) } context 'when instructeur is fully removed form procedure' do - it { expect(subject.body).to include('Vous avez été désaffecté de la démarche') } + it { expect(subject.body).to include('Vous avez été désaffecté(e) de la démarche') } it { expect(subject.to).to include('int3@g') } it { expect(subject.to).not_to include('int1@g', 'int2@g') } end @@ -51,9 +51,34 @@ RSpec.describe GroupeInstructeurMailer, type: :mailer do gi2 end - it { expect(subject.body).to include('Vous avez été retiré du groupe « gi1 » par « toto@email.com »') } + it { expect(subject.body).to include('Vous avez été retiré(e) du groupe « gi1 » par « toto@email.com »') } it { expect(subject.to).to include('int3@g') } it { expect(subject.to).not_to include('int1@g', 'int2@g') } end end + + describe '#notify_added_instructeurs' do + let(:procedure) { create(:procedure) } + + let(:instructeurs_to_add) { [create(:instructeur, email: 'int3@g'), create(:instructeur, email: 'int4@g')] } + + let(:current_instructeur_email) { 'toto@email.com' } + + subject { described_class.notify_added_instructeurs(procedure.defaut_groupe_instructeur, instructeurs_to_add, current_instructeur_email) } + + before { instructeurs_to_add.each { procedure.defaut_groupe_instructeur.add(_1) } } + + context 'when there is only one group on procedure' do + it { expect(subject.body).to include('Vous avez été affecté(e) à la démarche') } + it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) } + end + + context 'when there are many groups on procedure' do + let!(:groupe_instructeur_2) do + GroupeInstructeur.create(label: 'gi2', procedure: procedure) + end + it { expect(subject.body).to include('Vous avez été ajouté(e) au groupe') } + it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) } + end + end end diff --git a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb index 4eab9b7d7..d423b0b25 100644 --- a/spec/mailers/previews/groupe_instructeur_mailer_preview.rb +++ b/spec/mailers/previews/groupe_instructeur_mailer_preview.rb @@ -14,4 +14,12 @@ class GroupeInstructeurMailerPreview < ActionMailer::Preview instructeur = Instructeur.last GroupeInstructeurMailer.notify_removed_instructeur(groupe, instructeur, current_instructeur_email) end + + def notify_added_instructeurs + procedure = Procedure.new(id: 1, libelle: 'une superbe procedure') + groupe = GroupeInstructeur.new(id: 1, label: 'Val-De-Marne', procedure:) + current_instructeur_email = 'admin@dgfip.com' + instructeurs = Instructeur.limit(2) + GroupeInstructeurMailer.notify_added_instructeurs(groupe, instructeurs, current_instructeur_email) + end end diff --git a/spec/system/instructeurs/instructeur_creation_spec.rb b/spec/system/instructeurs/instructeur_creation_spec.rb index b3a78c574..04b2ed243 100644 --- a/spec/system/instructeurs/instructeur_creation_spec.rb +++ b/spec/system/instructeurs/instructeur_creation_spec.rb @@ -16,7 +16,7 @@ describe 'As an instructeur', js: true do end scenario 'I can register' do - confirmation_email = open_email(instructeur_email) + confirmation_email = emails_sent_to(instructeur_email).first token_params = confirmation_email.body.match(/token=[^"]+/) visit "users/activate?#{token_params}" From 7b12ec3ffe9b2f60785338ce4f14cf9b21424d2f Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Mon, 20 Feb 2023 15:06:47 +0100 Subject: [PATCH 087/202] refactor(groupe instructeur mailer): move translations in dedicated files --- .../notify_added_instructeurs.html.haml | 2 +- ...otify_group_when_instructeurs_removed.html.haml | 2 +- .../notify_removed_instructeur.html.haml | 2 +- .../administrateurs/groupe_instructeurs/en.yml | 14 -------------- .../administrateurs/groupe_instructeurs/fr.yml | 14 -------------- .../notify_added_instructeurs/en.yml | 6 ++++++ .../notify_added_instructeurs/fr.yml | 6 ++++++ .../notify_group_when_instructeurs_removed/en.yml | 6 ++++++ .../notify_group_when_instructeurs_removed/fr.yml | 6 ++++++ .../notify_removed_instructeur/en.yml | 6 ++++++ .../notify_removed_instructeur/fr.yml | 6 ++++++ 11 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/en.yml create mode 100644 config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/fr.yml create mode 100644 config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/en.yml create mode 100644 config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/fr.yml create mode 100644 config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/en.yml create mode 100644 config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/fr.yml diff --git a/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml index 5640e8d25..eb1d15d38 100644 --- a/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml +++ b/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml @@ -3,6 +3,6 @@ %p - number_of_groups = @group.procedure.groupe_instructeurs.many? ? 'many_groups' : 'one_group' - = t("administrateurs.groupe_instructeurs.notify_added_instructeurs.#{number_of_groups}.email_body", groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + = t(".email_body.#{number_of_groups}", groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) = render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml b/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml index 50ba415ef..3d318abb6 100644 --- a/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml +++ b/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml @@ -2,7 +2,7 @@ Bonjour, %p - = t('administrateurs.groupe_instructeurs.notify_group_when_instructeurs_removed.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + = t('.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) %p Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : diff --git a/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml b/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml index e093dadcd..334c47375 100644 --- a/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml +++ b/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml @@ -3,6 +3,6 @@ %p - assignment_state = @still_assigned_to_procedure ? 'assigned' : 'unassigned' - = t("administrateurs.groupe_instructeurs.notify_removed_instructeur.#{assignment_state}.email_body", groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) + = t(".email_body.#{assignment_state}", groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) = render partial: "layouts/mailers/signature" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/en.yml b/config/locales/views/administrateurs/groupe_instructeurs/en.yml index 0b7e05743..18a10f864 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/en.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/en.yml @@ -16,20 +16,6 @@ en: assignment: one: "The instructor %{emails} was assigned to the group « %{groupe} »." other: "The instructors %{emails} were assigned to the group « %{groupe} »." - notify_group_when_instructeurs_removed: - email_body: - one: "The instructor %{emails} was removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - other: "The instructors %{emails} were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - notify_removed_instructeur: - unassigned: - email_body: "You were unassigned from the procedure « %{procedure} » by « %{email} »." - assigned: - email_body: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." - notify_added_instructeurs: - one_group: - email_body: "You were assigned to the procedure « %{procedure} » by « %{email} »." - many_groups: - email_body: "You were assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} group exist" diff --git a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml index 686b9051d..3d1f3a256 100644 --- a/config/locales/views/administrateurs/groupe_instructeurs/fr.yml +++ b/config/locales/views/administrateurs/groupe_instructeurs/fr.yml @@ -22,20 +22,6 @@ fr: assignment: one: "L’instructeur %{emails} a été affecté au groupe « %{groupe} »." other: "Les instructeurs %{emails} ont été affectés au groupe « %{groupe} »." - notify_group_when_instructeurs_removed: - email_body: - one: "L’instructeur %{emails} a été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - other: "Les instructeurs %{emails} ont été retirés du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - notify_removed_instructeur: - assigned: - email_body: "Vous avez été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." - unassigned: - email_body: "Vous avez été désaffecté de la démarche « %{procedure} » par « %{email} »." - notify_added_instructeurs: - one_group: - email_body: "Vous avez été affecté à la démarche « %{procedure} » par « %{email} »." - many_groups: - email_body: "Vous avez été ajouté au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." reaffecter_dossiers: existing_groupe: one: "%{count} groupe existe" diff --git a/config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/en.yml b/config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/en.yml new file mode 100644 index 000000000..92b83a7dd --- /dev/null +++ b/config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/en.yml @@ -0,0 +1,6 @@ +en: + groupe_instructeur_mailer: + notify_added_instructeurs: + email_body: + one_group: "You were assigned to the procedure « %{procedure} » by « %{email} »." + many_groups: "You were assigned to the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." diff --git a/config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/fr.yml b/config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/fr.yml new file mode 100644 index 000000000..73cb5933e --- /dev/null +++ b/config/locales/views/groupe_instructeur_mailer/notify_added_instructeurs/fr.yml @@ -0,0 +1,6 @@ +fr: + groupe_instructeur_mailer: + notify_added_instructeurs: + email_body: + one_group: "Vous avez été affecté(e) à la démarche « %{procedure} » par « %{email} »." + many_groups: "Vous avez été ajouté(e) au groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." diff --git a/config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/en.yml b/config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/en.yml new file mode 100644 index 000000000..d5192fdec --- /dev/null +++ b/config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/en.yml @@ -0,0 +1,6 @@ +en: + groupe_instructeur_mailer: + notify_group_when_instructeurs_removed: + email_body: + one: "The instructor %{emails} was removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." + other: "The instructors %{emails} were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." diff --git a/config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/fr.yml b/config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/fr.yml new file mode 100644 index 000000000..a1e3e00de --- /dev/null +++ b/config/locales/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed/fr.yml @@ -0,0 +1,6 @@ +fr: + groupe_instructeur_mailer: + notify_group_when_instructeurs_removed: + email_body: + one: "L’instructeur %{emails} a été retiré du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." + other: "Les instructeurs %{emails} ont été retirés du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." diff --git a/config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/en.yml b/config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/en.yml new file mode 100644 index 000000000..e786341f3 --- /dev/null +++ b/config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/en.yml @@ -0,0 +1,6 @@ +en: + groupe_instructeur_mailer: + notify_removed_instructeur: + email_body: + assigned: "You were removed from the group « %{groupe} » by « %{email} », in charge of procedure « %{procedure} »." + unassigned: "You were unassigned from the procedure « %{procedure} » by « %{email} »." diff --git a/config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/fr.yml b/config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/fr.yml new file mode 100644 index 000000000..5a877e37c --- /dev/null +++ b/config/locales/views/groupe_instructeur_mailer/notify_removed_instructeur/fr.yml @@ -0,0 +1,6 @@ +fr: + groupe_instructeur_mailer: + notify_removed_instructeur: + email_body: + assigned: "Vous avez été retiré(e) du groupe « %{groupe} » par « %{email} », en charge de la démarche « %{procedure} »." + unassigned: "Vous avez été désaffecté(e) de la démarche « %{procedure} » par « %{email} »." From daef99109e089dcf8aaf3265e58572587e843345 Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Mon, 20 Feb 2023 15:59:42 +0100 Subject: [PATCH 088/202] fix(groupe instructeur mailer): add translation for Bonjour --- .../notify_added_instructeurs.html.haml | 3 +-- .../notify_group_when_instructeurs_removed.html.haml | 3 +-- .../notify_removed_instructeur.html.haml | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml b/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml index eb1d15d38..7cf65d373 100644 --- a/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml +++ b/app/views/groupe_instructeur_mailer/notify_added_instructeurs.html.haml @@ -1,5 +1,4 @@ -%p - Bonjour, +%p= t(:hello, scope: [:views, :shared, :greetings]) %p - number_of_groups = @group.procedure.groupe_instructeurs.many? ? 'many_groups' : 'one_group' diff --git a/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml b/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml index 3d318abb6..1c3ccf1e1 100644 --- a/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml +++ b/app/views/groupe_instructeur_mailer/notify_group_when_instructeurs_removed.html.haml @@ -1,5 +1,4 @@ -%p - Bonjour, +%p= t(:hello, scope: [:views, :shared, :greetings]) %p = t('.email_body', count: @removed_instructeur_emails.size, emails: @removed_instructeur_emails.join(', '), groupe: @group.label, email: @current_instructeur_email, procedure: @group.procedure.libelle) diff --git a/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml b/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml index 334c47375..9952a1b55 100644 --- a/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml +++ b/app/views/groupe_instructeur_mailer/notify_removed_instructeur.html.haml @@ -1,5 +1,4 @@ -%p - Bonjour, +%p= t(:hello, scope: [:views, :shared, :greetings]) %p - assignment_state = @still_assigned_to_procedure ? 'assigned' : 'unassigned' From eca44ce60e1755757af04649ce4b899d37370200 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 20 Feb 2023 10:33:47 +0100 Subject: [PATCH 089/202] =?UTF-8?q?correctif(a11y):=20il=20est=20recommand?= =?UTF-8?q?=C3=A9=20d'utilser=20un=20boutton=20pour=20la=20suppression=20d?= =?UTF-8?q?'un=20element.=20le=20bouton=20etant=20deja=20dans=20un=20form,?= =?UTF-8?q?=20nous=20ne=20pouvons=20pas=20utiliser=20le=20button=5Fto.=20E?= =?UTF-8?q?xtraction=20d'un=20form=20pour=20soumettre=20la=20suppression?= =?UTF-8?q?=20des=20pjs=20par=20des=20input=20pointant=20sur=20ce=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update app/components/attachment/edit_component/edit_component.html.haml Co-authored-by: Paul Chavard Update app/views/shared/dossiers/_edit.html.haml Co-authored-by: Paul Chavard amelioration(attachment.destroy): force l'usage de turbo --- .../attachment/edit_component/edit_component.html.haml | 3 ++- app/views/shared/dossiers/_edit.html.haml | 4 ++++ spec/components/attachment/edit_component_spec.rb | 8 ++++++-- spec/components/attachment/multiple_component_spec.rb | 4 ++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/components/attachment/edit_component/edit_component.html.haml b/app/components/attachment/edit_component/edit_component.html.haml index bc54ef9f4..dda501826 100644 --- a/app/components/attachment/edit_component/edit_component.html.haml +++ b/app/components/attachment/edit_component/edit_component.html.haml @@ -3,7 +3,8 @@ %div{ id: dom_id(attachment, :persisted_row) } .flex.flex-gap-2{ class: class_names("attachment-error": attachment.virus_scanner_error?) } - if user_can_destroy? - = link_to(t('.delete'), destroy_attachment_path, **remove_button_options, class: "fr-btn fr-btn--tertiary fr-btn--sm fr-icon-delete-line", title: t(".delete_file", filename: attachment.filename)) + = button_tag(name: "action", formaction: destroy_attachment_path, class: "fr-btn fr-btn--tertiary fr-btn--sm fr-icon-delete-line", title: t(".delete_file", filename: attachment.filename), form: dom_id(ActiveStorage::Attachment.new, :delete), data: {turbo: true, 'turbo-method': 'delete'}) do + = t('.delete') - elsif user_can_replace? = button_tag t('.replace'), **replace_button_options, class: "fr-btn fr-btn--tertiary fr-btn--sm", title: t(".replace_file", filename: attachment.filename) diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml index ed1ba11c7..b659a126d 100644 --- a/app/views/shared/dossiers/_edit.html.haml +++ b/app/views/shared/dossiers/_edit.html.haml @@ -10,6 +10,10 @@ - else - form_options = { url: modifier_dossier_url(dossier), method: :patch } + %div + = form_tag('/attachments/:id', method: :delete, data: { 'turbo-method': :delete, turbo: true }, id: dom_id(ActiveStorage::Attachment.new, :delete)) do + -# otherwise the closing tag bugs + = form_for dossier, form_options.merge({ html: { id: 'dossier-edit-form', class: 'form', multipart: true, novalidate: 'novalidate' } }) do |f| %header.mb-6 diff --git a/spec/components/attachment/edit_component_spec.rb b/spec/components/attachment/edit_component_spec.rb index 40851b3aa..6158b099d 100644 --- a/spec/components/attachment/edit_component_spec.rb +++ b/spec/components/attachment/edit_component_spec.rb @@ -103,7 +103,7 @@ RSpec.describe Attachment::EditComponent, type: :component do it 'displays the filename, but doesn’t allow to download the file' do expect(attachment.watermark_pending?).to be_truthy expect(subject).to have_text(filename) - expect(subject).to have_link('Supprimer') + expect(subject).to have_button('Supprimer') expect(subject).to have_no_link(text: filename) # don't match "Delete" link which also include filename in title attribute expect(subject).to have_text('Traitement en cours') end @@ -152,8 +152,12 @@ RSpec.describe Attachment::EditComponent, type: :component do end end - context 'when the file is scanned and safe' do + context 'when the file is scanned, watermarked_at, and viewed as download and safe' do + let(:kwargs) { { view_as: :download } } let(:virus_scan_result) { ActiveStorage::VirusScanner::SAFE } + before do + attachment.blob.touch(:watermarked_at) + end it 'allows to download the file' do expect(subject).to have_link(filename) diff --git a/spec/components/attachment/multiple_component_spec.rb b/spec/components/attachment/multiple_component_spec.rb index 54b8782b4..23f585d55 100644 --- a/spec/components/attachment/multiple_component_spec.rb +++ b/spec/components/attachment/multiple_component_spec.rb @@ -47,8 +47,8 @@ RSpec.describe Attachment::MultipleComponent, type: :component do end it 'shows the Delete button by default' do - expect(subject).to have_link(title: "Supprimer le fichier #{attached_file.attachments[0].filename}") - expect(subject).to have_link(title: "Supprimer le fichier #{attached_file.attachments[1].filename}") + expect(subject).to have_button(title: "Supprimer le fichier #{attached_file.attachments[0].filename}") + expect(subject).to have_button(title: "Supprimer le fichier #{attached_file.attachments[1].filename}") end it 'renders a form field for uploading a new file' do From bd92291f8ac1ca768aec0902aa2fe20dada75a4b Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Fri, 6 Jan 2023 17:47:28 +0100 Subject: [PATCH 090/202] cache dossiers count --- app/models/procedure.rb | 12 +++++++++++- ...and_dossiers_count_computed_at_to_procedures.rb | 6 ++++++ db/schema.rb | 4 +++- spec/models/procedure_spec.rb | 14 ++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20230217094119_add_estimated_dossiers_count_and_dossiers_count_computed_at_to_procedures.rb diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 8e8515bd6..1f8d42b72 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -15,10 +15,12 @@ # closed_at :datetime # declarative_with_state :string # description :string +# dossiers_count_computed_at :datetime # duree_conservation_dossiers_dans_ds :integer # duree_conservation_etendue_par_ds :boolean default(FALSE) # encrypted_api_particulier_token :string # estimated_duration_visible :boolean default(TRUE), not null +# estimated_dossiers_count :integer # euro_flag :boolean default(FALSE) # experts_require_administrateur_invitation :boolean default(FALSE) # for_individual :boolean default(FALSE) @@ -71,6 +73,8 @@ class Procedure < ApplicationRecord MIN_WEIGHT = 350000 + DOSSIERS_COUNT_EXPIRING = 1.hour + attr_encrypted :api_particulier_token has_many :revisions, -> { order(:id) }, class_name: 'ProcedureRevision', inverse_of: :procedure @@ -836,7 +840,13 @@ class Procedure < ApplicationRecord self.connection.query(query.to_sql).flatten end - private + def compute_dossiers_count + now = Time.zone.now + if now > (self.dossiers_count_computed_at || self.created_at) + DOSSIERS_COUNT_EXPIRING + self.update(estimated_dossiers_count: self.dossiers.visible_by_administration.count, + dossiers_count_computed_at: now) + end + end def move_new_children_to_new_parent_coordinate(new_draft) children = new_draft.revision_types_de_champ diff --git a/db/migrate/20230217094119_add_estimated_dossiers_count_and_dossiers_count_computed_at_to_procedures.rb b/db/migrate/20230217094119_add_estimated_dossiers_count_and_dossiers_count_computed_at_to_procedures.rb new file mode 100644 index 000000000..2e33dc484 --- /dev/null +++ b/db/migrate/20230217094119_add_estimated_dossiers_count_and_dossiers_count_computed_at_to_procedures.rb @@ -0,0 +1,6 @@ +class AddEstimatedDossiersCountAndDossiersCountComputedAtToProcedures < ActiveRecord::Migration[6.1] + def change + add_column :procedures, :estimated_dossiers_count, :integer + add_column :procedures, :dossiers_count_computed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 8719631c4..eda27f5c3 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: 2023_02_16_141558) do +ActiveRecord::Schema.define(version: 2023_02_17_094119) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" @@ -703,12 +703,14 @@ ActiveRecord::Schema.define(version: 2023_02_16_141558) do t.string "declarative_with_state" t.string "description" t.string "direction" + t.datetime "dossiers_count_computed_at" t.bigint "draft_revision_id" t.integer "duree_conservation_dossiers_dans_ds" t.boolean "duree_conservation_etendue_par_ds", default: false t.boolean "durees_conservation_required", default: true t.string "encrypted_api_particulier_token" t.boolean "estimated_duration_visible", default: true, null: false + t.integer "estimated_dossiers_count" t.boolean "euro_flag", default: false t.boolean "experts_require_administrateur_invitation", default: false t.boolean "for_individual", default: false diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 4a5508d2a..80159b85d 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -9,6 +9,20 @@ describe Procedure do it { expect(subject.without_continuation_mail_template).to be_a(Mails::WithoutContinuationMail) } end + describe 'compute_dossiers_count' do + let(:procedure) { create(:procedure_with_dossiers, dossiers_count: 2, dossiers_count_computed_at: Time.zone.now - Procedure::DOSSIERS_COUNT_EXPIRING) } + + it 'caches estimated_dossiers_count' do + procedure.dossiers.each(&:passer_en_construction!) + expect { procedure.compute_dossiers_count }.to change(procedure, :estimated_dossiers_count).from(nil).to(2) + expect { create(:dossier, procedure: procedure).passer_en_construction! }.not_to change(procedure, :estimated_dossiers_count) + + Timecop.freeze(Time.zone.now + Procedure::DOSSIERS_COUNT_EXPIRING) + expect { procedure.compute_dossiers_count }.to change(procedure, :estimated_dossiers_count).from(2).to(3) + Timecop.return + end + end + describe 'initiated_mail' do let(:procedure) { create(:procedure) } From c5acd82e6e815b9102dcc886238b93c445e805a0 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Fri, 6 Jan 2023 17:48:00 +0100 Subject: [PATCH 091/202] update dossiers count when passing to construction --- app/models/dossier.rb | 1 + spec/models/dossier_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 31eb93b03..020b5ff38 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -874,6 +874,7 @@ class Dossier < ApplicationRecord .passer_en_construction .processed_at save! + procedure.compute_dossiers_count end def after_passer_en_instruction(h) diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index ca87290b1..3442c61d9 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -2080,6 +2080,15 @@ describe Dossier do end end + describe 'update procedure dossiers count' do + let(:dossier) { create(:dossier, :brouillon, :with_individual) } + + it 'update procedure dossiers count when passing to construction' do + expect(dossier.procedure).to receive(:compute_dossiers_count) + dossier.passer_en_construction! + end + end + private def count_for_month(processed_by_month, month) From 9bab4f9d76a014135dfab819fe565270d1bb9c9b Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 3 Jan 2023 14:58:46 +0100 Subject: [PATCH 092/202] display dossiers count for each procedure --- app/controllers/administrateurs/procedures_controller.rb | 2 +- app/views/administrateurs/procedures/_detail.html.haml | 3 ++- app/views/administrateurs/procedures/all.html.haml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/administrateurs/procedures_controller.rb b/app/controllers/administrateurs/procedures_controller.rb index e81a3a42c..3a0baef4f 100644 --- a/app/controllers/administrateurs/procedures_controller.rb +++ b/app/controllers/administrateurs/procedures_controller.rb @@ -418,7 +418,7 @@ module Administrateurs procedures_result = procedures_result.where('unaccent(libelle) ILIKE unaccent(?)', "%#{filter.libelle}%") if filter.libelle.present? procedures_sql = procedures_result.to_sql - sql = "select id, libelle, published_at, aasm_state, count(administrateurs_procedures.administrateur_id) as admin_count from administrateurs_procedures inner join procedures on procedures.id = administrateurs_procedures.procedure_id where procedures.id in (#{procedures_sql}) group by procedures.id order by published_at desc" + sql = "select id, libelle, published_at, aasm_state, estimated_dossiers_count, count(administrateurs_procedures.administrateur_id) as admin_count from administrateurs_procedures inner join procedures on procedures.id = administrateurs_procedures.procedure_id where procedures.id in (#{procedures_sql}) group by procedures.id order by published_at desc" ActiveRecord::Base.connection.execute(sql) end diff --git a/app/views/administrateurs/procedures/_detail.html.haml b/app/views/administrateurs/procedures/_detail.html.haml index a02396a9e..2728ece46 100644 --- a/app/views/administrateurs/procedures/_detail.html.haml +++ b/app/views/administrateurs/procedures/_detail.html.haml @@ -8,6 +8,7 @@ %td= procedure.libelle %td= procedure.id + %td= procedure.estimated_dossiers_count %td= procedure.administrateurs.count %td= t procedure.aasm_state, scope: 'activerecord.attributes.procedure.aasm_state' %td= l(procedure.published_at, format: :message_date_without_time) @@ -16,7 +17,7 @@ - if show_detail %tr.procedure{ id: "procedure_detail_#{procedure.id}" } - %td.fr-highlight--beige-gris-galet{ colspan: '7' } + %td.fr-highlight--beige-gris-galet{ colspan: '8' } .fr-container .fr-grid-row .fr-col-6 diff --git a/app/views/administrateurs/procedures/all.html.haml b/app/views/administrateurs/procedures/all.html.haml index 56ad5e129..b8049b05e 100644 --- a/app/views/administrateurs/procedures/all.html.haml +++ b/app/views/administrateurs/procedures/all.html.haml @@ -41,6 +41,7 @@ %th{ scope: 'col' } %th{ scope: 'col' } Démarche %th{ scope: 'col' } № + %th{ scope: 'col' } Dossiers %th{ scope: 'col' } Administrateurs %th{ scope: 'col' } Statut %th{ scope: 'col' } Date From 61d14c2c52c2236d4bfaadd5f3450f0223984ce1 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 3 Jan 2023 15:03:19 +0100 Subject: [PATCH 093/202] compute once dossiers count for each procedure --- ...03427_update_procedure_dossiers_count.rake | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 lib/tasks/deployment/20230223103427_update_procedure_dossiers_count.rake diff --git a/lib/tasks/deployment/20230223103427_update_procedure_dossiers_count.rake b/lib/tasks/deployment/20230223103427_update_procedure_dossiers_count.rake new file mode 100644 index 000000000..e99e096bd --- /dev/null +++ b/lib/tasks/deployment/20230223103427_update_procedure_dossiers_count.rake @@ -0,0 +1,20 @@ +namespace :after_party do + desc 'Deployment task: update_procedure_dossiers_count' + task update_procedure_dossiers_count: :environment do + puts "Running deploy task 'update_procedure_dossiers_count'" + progress = ProgressReport.new(Procedure.count) + + Procedure.find_each do |p| + progress.inc + begin + p.update_columns(estimated_dossiers_count: p.dossiers.visible_by_administration.count, dossiers_count_computed_at: Time.zone.now) + rescue => e + Sentry.capture_exception(e, extra: { procedure_id: p.id }) + end + end + progress.finish + + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end From e56d51e0f6f7b369c3a01255fd6ba544933cd628 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 15 Feb 2023 18:30:02 +0100 Subject: [PATCH 094/202] fix(graphql): fix demarcheUrl --- app/graphql/api/v2/stored_query.rb | 1 + app/graphql/types/demarche_descriptor_type.rb | 6 +++++- .../api/v2/graphql_controller_stored_queries_spec.rb | 1 + spec/services/demarches_publiques_export_service_spec.rb | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/graphql/api/v2/stored_query.rb b/app/graphql/api/v2/stored_query.rb index a2e39eddf..465eaf2af 100644 --- a/app/graphql/api/v2/stored_query.rb +++ b/app/graphql/api/v2/stored_query.rb @@ -288,6 +288,7 @@ class API::V2::StoredQuery dateFermeture notice { url } deliberation { url } + demarcheUrl cadreJuridiqueUrl service @include(if: $includeService) { ...ServiceFragment diff --git a/app/graphql/types/demarche_descriptor_type.rb b/app/graphql/types/demarche_descriptor_type.rb index d0db6991a..f29c618db 100644 --- a/app/graphql/types/demarche_descriptor_type.rb +++ b/app/graphql/types/demarche_descriptor_type.rb @@ -75,7 +75,11 @@ Cela évite l’accès récursif aux dossiers." delegate :description, :opendata, :tags, to: :procedure def demarche_url - procedure.lien_demarche + if procedure.brouillon? + Rails.application.routes.url_helpers.commencer_test_url(path: procedure.path) + else + Rails.application.routes.url_helpers.commencer_url(path: procedure.path) + end end def dpo_url diff --git a/spec/controllers/api/v2/graphql_controller_stored_queries_spec.rb b/spec/controllers/api/v2/graphql_controller_stored_queries_spec.rb index 3e065f0de..ae6749376 100644 --- a/spec/controllers/api/v2/graphql_controller_stored_queries_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_stored_queries_spec.rb @@ -138,6 +138,7 @@ describe API::V2::GraphqlController do it { expect(gql_errors).to be_nil expect(gql_data[:demarcheDescriptor][:id]).to eq(procedure.to_typed_id) + expect(gql_data[:demarcheDescriptor][:demarcheUrl]).to match("commencer/#{procedure.path}") } end diff --git a/spec/services/demarches_publiques_export_service_spec.rb b/spec/services/demarches_publiques_export_service_spec.rb index 05e96a208..bec59a795 100644 --- a/spec/services/demarches_publiques_export_service_spec.rb +++ b/spec/services/demarches_publiques_export_service_spec.rb @@ -17,7 +17,7 @@ describe DemarchesPubliquesExportService do typeOrganisme: "association" }, cadreJuridiqueUrl: "un cadre juridique important", - demarcheUrl: nil, + demarcheUrl: Rails.application.routes.url_helpers.commencer_url(path: procedure.path), dpoUrl: nil, noticeUrl: nil, siteWebUrl: "https://mon-site.gouv", From a71e8d9a8115b4af97342d2a22d395c5312b6f1d Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Mon, 20 Feb 2023 09:36:27 +0100 Subject: [PATCH 095/202] chore(bundle): setup elastic_apm, disabled by default --- Gemfile | 1 + Gemfile.lock | 17 +++++++++++++++++ config/elastic_apm.yml | 8 ++++++++ 3 files changed, 26 insertions(+) create mode 100644 config/elastic_apm.yml diff --git a/Gemfile b/Gemfile index 708603839..3fcb4c749 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'devise-i18n' gem 'devise-two-factor' gem 'discard' gem 'dotenv-rails', require: 'dotenv/rails-now' # dotenv should always be loaded before rails +gem 'elastic-apm' gem 'flipper' gem 'flipper-active_record' gem 'flipper-ui' diff --git a/Gemfile.lock b/Gemfile.lock index 14db486bd..02f1dc484 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -220,6 +220,10 @@ GEM dumb_delegator (1.0.0) ecma-re-validator (0.3.0) regexp_parser (~> 2.0) + elastic-apm (4.6.0) + concurrent-ruby (~> 1.0) + http (>= 3.0) + ruby2_keywords encryptor (3.0.0) erubi (1.12.0) et-orbi (1.2.4) @@ -249,6 +253,9 @@ GEM faraday-patron (1.0.0) faraday-rack (1.0.0) ffi (1.15.5) + ffi-compiler (1.0.1) + ffi (>= 1.0.0) + rake flipper (0.26.0) concurrent-ruby (< 2) flipper-active_record (0.26.0) @@ -323,9 +330,15 @@ GEM highline (2.0.3) html_tokenizer (0.0.7) htmlentities (4.3.4) + http (5.1.1) + addressable (~> 2.8) + http-cookie (~> 1.0) + http-form_data (~> 2.2) + llhttp-ffi (~> 0.4.0) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) + http-form_data (2.3.0) http_accept_language (2.1.1) httpclient (2.8.3) i18n (1.12.0) @@ -390,6 +403,9 @@ GEM listen (3.4.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + llhttp-ffi (0.4.0) + ffi-compiler (~> 1.0) + rake (~> 13.0) lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) @@ -838,6 +854,7 @@ DEPENDENCIES devise-two-factor discard dotenv-rails + elastic-apm factory_bot flipper flipper-active_record diff --git a/config/elastic_apm.yml b/config/elastic_apm.yml new file mode 100644 index 000000000..2c65bac6b --- /dev/null +++ b/config/elastic_apm.yml @@ -0,0 +1,8 @@ +# Set options ELASTIC_APM_SERVER_URL & ELASTIC_APM_SECRET_TOKEN by env vars instead +# See https://www.elastic.co/guide/en/apm/agent/ruby/current/configuration.html +# +# server_url: '' +# secret_token: '' + +# Enable it with ELASTIC_APM_ENABLED="true" +enabled: false From 2052bc78401c887e8bed1e4156bdc8dd11714a5b Mon Sep 17 00:00:00 2001 From: Damien Le Thiec Date: Tue, 21 Feb 2023 16:00:58 +0100 Subject: [PATCH 096/202] Remove dependency type_de_champ -> procedure --- .../prefill_type_de_champs_controller.rb | 2 +- app/models/prefill_description.rb | 4 +- app/models/prefill_params.rb | 15 +++++--- app/models/procedure_revision.rb | 2 + app/models/type_de_champ.rb | 8 ---- .../prefill_repetition_type_de_champ.rb | 17 +++++---- .../types_de_champ/prefill_type_de_champ.rb | 29 ++++++++------ spec/models/prefill_description_spec.rb | 14 +++---- .../prefill_departement_type_de_champ_spec.rb | 4 +- .../prefill_epci_type_de_champ_spec.rb | 11 +++--- .../prefill_pays_type_de_champ_spec.rb | 4 +- .../prefill_region_type_de_champ_spec.rb | 4 +- .../prefill_repetition_type_de_champ_spec.rb | 12 +++--- .../prefill_type_de_champ_spec.rb | 38 ++++++++++--------- spec/system/users/dossier_prefill_get_spec.rb | 2 +- .../system/users/dossier_prefill_post_spec.rb | 2 +- 16 files changed, 87 insertions(+), 81 deletions(-) diff --git a/app/controllers/prefill_type_de_champs_controller.rb b/app/controllers/prefill_type_de_champs_controller.rb index 82817f324..af541afcf 100644 --- a/app/controllers/prefill_type_de_champs_controller.rb +++ b/app/controllers/prefill_type_de_champs_controller.rb @@ -12,6 +12,6 @@ class PrefillTypeDeChampsController < ApplicationController end def set_prefill_type_de_champ - @type_de_champ = TypesDeChamp::PrefillTypeDeChamp.build(@procedure.active_revision.types_de_champ.fillable.find(params[:id])) + @type_de_champ = TypesDeChamp::PrefillTypeDeChamp.build(@procedure.active_revision.types_de_champ.fillable.find(params[:id]), @procedure.active_revision) end end diff --git a/app/models/prefill_description.rb b/app/models/prefill_description.rb index 090e02fb8..dc5dba767 100644 --- a/app/models/prefill_description.rb +++ b/app/models/prefill_description.rb @@ -15,7 +15,7 @@ class PrefillDescription < SimpleDelegator end def types_de_champ - TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision.types_de_champ_public.fillable.partition(&:prefillable?).flatten) + TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision.types_de_champ_public.fillable.partition(&:prefillable?).flatten, active_revision) end def include?(type_de_champ_id) @@ -40,7 +40,7 @@ class PrefillDescription < SimpleDelegator end def prefilled_champs - @prefilled_champs ||= TypesDeChamp::PrefillTypeDeChamp.wrap(active_fillable_public_types_de_champ.where(id: selected_type_de_champ_ids)) + @prefilled_champs ||= TypesDeChamp::PrefillTypeDeChamp.wrap(active_fillable_public_types_de_champ.where(id: selected_type_de_champ_ids), active_revision) end private diff --git a/app/models/prefill_params.rb b/app/models/prefill_params.rb index a7b5df8f7..d048a3d2d 100644 --- a/app/models/prefill_params.rb +++ b/app/models/prefill_params.rb @@ -1,4 +1,6 @@ class PrefillParams + attr_reader :dossier, :params + def initialize(dossier, params) @dossier = dossier @params = params @@ -11,15 +13,15 @@ class PrefillParams private def build_prefill_values - value_by_stable_id = @params + value_by_stable_id = params .map { |prefixed_typed_id, value| [stable_id_from_typed_id(prefixed_typed_id), value] } .filter { |stable_id, value| stable_id.present? && value.present? } .to_h - @dossier + dossier .find_champs_by_stable_ids(value_by_stable_id.keys) .map { |champ| [champ, value_by_stable_id[champ.stable_id]] } - .map { |champ, value| PrefillValue.new(champ:, value:) } + .map { |champ, value| PrefillValue.new(champ:, value:, dossier:) } end def stable_id_from_typed_id(prefixed_typed_id) @@ -46,11 +48,12 @@ class PrefillParams TypeDeChamp.type_champs.fetch(:epci) ] - attr_reader :champ, :value + attr_reader :champ, :value, :dossier - def initialize(champ:, value:) + def initialize(champ:, value:, dossier:) @champ = champ @value = value + @dossier = dossier end def prefillable? @@ -59,7 +62,7 @@ class PrefillParams def champ_attributes TypesDeChamp::PrefillTypeDeChamp - .build(champ.type_de_champ) + .build(champ.type_de_champ, dossier.revision) .to_assignable_attributes(champ, value) end diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index ff3e587b5..0f1196477 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -32,6 +32,8 @@ class ProcedureRevision < ApplicationRecord validate :conditions_are_valid? + delegate :path, to: :procedure, prefix: true + def build_champs_public # reload: it can be out of sync in test if some tdcs are added wihtout using add_tdc types_de_champ_public.reload.map(&:build_champ) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 899b5b1b1..feb0faaa0 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -128,8 +128,6 @@ class TypeDeChamp < ApplicationRecord has_one :procedure, through: :revision delegate :estimated_fill_duration, :estimated_read_duration, :tags_for_template, :libelle_for_export, to: :dynamic_type - delegate :active_revision, to: :procedure, prefix: true - delegate :path, to: :procedure class WithIndifferentAccess def self.load(options) @@ -489,12 +487,6 @@ class TypeDeChamp < ApplicationRecord end end - def active_revision_type_de_champ - procedure_active_revision.revision_types_de_champ_public.find do |rtc| - rtc.type_de_champ_id == id - end - end - private DEFAULT_EMPTY = [''] diff --git a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb index 240ca6f6f..4a44ffd6a 100644 --- a/app/models/types_de_champ/prefill_repetition_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_repetition_type_de_champ.rb @@ -17,7 +17,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh return [] unless value.is_a?(Array) value.map.with_index do |repetition, index| - PrefillRepetitionRow.new(champ, repetition, index).to_assignable_attributes + PrefillRepetitionRow.new(champ, repetition, index, @revision).to_assignable_attributes end.reject(&:blank?) end @@ -37,27 +37,28 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh end def prefillable_subchamps - return [] unless active_revision_type_de_champ - @prefillable_subchamps ||= - TypesDeChamp::PrefillTypeDeChamp.wrap(active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ).filter(&:prefillable?)) + TypesDeChamp::PrefillTypeDeChamp.wrap(@revision.children_of(self).filter(&:prefillable?), @revision) end class PrefillRepetitionRow - def initialize(champ, repetition, index) + attr_reader :champ, :repetition, :index, :revision + + def initialize(champ, repetition, index, revision) @champ = champ @repetition = repetition @index = index + @revision = revision end def to_assignable_attributes - row = @champ.rows[@index] || @champ.add_row(@champ.dossier_revision) + row = champ.rows[index] || champ.add_row(champ.dossier_revision) - JSON.parse(@repetition).map do |key, value| + JSON.parse(repetition).map do |key, value| subchamp = row.find { |champ| champ.type_de_champ_to_typed_id == key } next unless subchamp - TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ).to_assignable_attributes(subchamp, value) + TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ, revision).to_assignable_attributes(subchamp, value) rescue JSON::ParserError # On ignore les valeurs qu'on n'arrive pas à parser end.compact end diff --git a/app/models/types_de_champ/prefill_type_de_champ.rb b/app/models/types_de_champ/prefill_type_de_champ.rb index 25c27e770..43bf2c493 100644 --- a/app/models/types_de_champ/prefill_type_de_champ.rb +++ b/app/models/types_de_champ/prefill_type_de_champ.rb @@ -4,29 +4,34 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator POSSIBLE_VALUES_THRESHOLD = 5 - def self.build(type_de_champ) + def initialize(type_de_champ, revision) + super(type_de_champ) + @revision = revision + end + + def self.build(type_de_champ, revision) case type_de_champ.type_champ when TypeDeChamp.type_champs.fetch(:drop_down_list) - TypesDeChamp::PrefillDropDownListTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillDropDownListTypeDeChamp.new(type_de_champ, revision) when TypeDeChamp.type_champs.fetch(:multiple_drop_down_list) - TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp.new(type_de_champ, revision) when TypeDeChamp.type_champs.fetch(:pays) - TypesDeChamp::PrefillPaysTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillPaysTypeDeChamp.new(type_de_champ, revision) when TypeDeChamp.type_champs.fetch(:regions) - TypesDeChamp::PrefillRegionTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillRegionTypeDeChamp.new(type_de_champ, revision) when TypeDeChamp.type_champs.fetch(:repetition) - TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ, revision) when TypeDeChamp.type_champs.fetch(:departements) - TypesDeChamp::PrefillDepartementTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillDepartementTypeDeChamp.new(type_de_champ, revision) when TypeDeChamp.type_champs.fetch(:epci) - TypesDeChamp::PrefillEpciTypeDeChamp.new(type_de_champ) + TypesDeChamp::PrefillEpciTypeDeChamp.new(type_de_champ, revision) else - new(type_de_champ) + new(type_de_champ, revision) end end - def self.wrap(collection) - collection.map { |type_de_champ| build(type_de_champ) } + def self.wrap(collection, revision) + collection.map { |type_de_champ| build(type_de_champ, revision) } end def possible_values @@ -61,7 +66,7 @@ class TypesDeChamp::PrefillTypeDeChamp < SimpleDelegator link_to( I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), - Rails.application.routes.url_helpers.prefill_type_de_champ_path(path, self), + Rails.application.routes.url_helpers.prefill_type_de_champ_path(revision.procedure_path, self), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes ) diff --git a/spec/models/prefill_description_spec.rb b/spec/models/prefill_description_spec.rb index dc8a62fa4..a88015569 100644 --- a/spec/models/prefill_description_spec.rb +++ b/spec/models/prefill_description_spec.rb @@ -22,7 +22,7 @@ RSpec.describe PrefillDescription, type: :model do it { expect(types_de_champ.count).to eq(1) } - it { expect(types_de_champ.first).to eql(TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ)) } + it { expect(types_de_champ.first).to eql(TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ, procedure.active_revision)) } shared_examples "filters out non fillable types de champ" do |type_de_champ_name| context "when the procedure has a #{type_de_champ_name} champ" do @@ -91,7 +91,7 @@ RSpec.describe PrefillDescription, type: :model do let(:type_de_champ_text) { build(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_epci) { build(:type_de_champ_epci, procedure: procedure) } let(:type_de_champ_repetition) { create(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } - let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition, procedure.active_revision).send(:prefillable_subchamps) } let(:region_repetition) { prefillable_subchamps.third } let(:prefill_description) { described_class.new(procedure) } @@ -116,9 +116,9 @@ RSpec.describe PrefillDescription, type: :model do expect(prefill_description.prefill_link).to eq( commencer_url( path: procedure.path, - "champ_#{type_de_champ_text.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_text).example_value, - "champ_#{type_de_champ_epci.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_epci).example_value, - "champ_#{type_de_champ_repetition.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value + "champ_#{type_de_champ_text.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_text, procedure.active_revision).example_value, + "champ_#{type_de_champ_epci.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_epci, procedure.active_revision).example_value, + "champ_#{type_de_champ_repetition.to_typed_id}" => TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition, procedure.active_revision).example_value ) ) end @@ -129,14 +129,14 @@ RSpec.describe PrefillDescription, type: :model do let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) } let(:type_de_champ_epci) { TypesDeChamp::PrefillTypeDeChamp.build(create(:type_de_champ_epci, procedure: procedure)) } let(:type_de_champ_repetition) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } - let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition).send(:prefillable_subchamps) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ_repetition, procedure.active_revision).send(:prefillable_subchamps) } let(:region_repetition) { prefillable_subchamps.third } let(:prefill_description) { described_class.new(procedure) } let(:expected_query) do <<~TEXT curl --request POST '#{api_public_v1_dossiers_url(procedure)}' \\ --header 'Content-Type: application/json' \\ - --data '{"champ_#{type_de_champ_text.to_typed_id}": "#{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_text).example_value}", "champ_#{type_de_champ_epci.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_epci).example_value}, "champ_#{type_de_champ_repetition.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition).example_value}}' + --data '{"champ_#{type_de_champ_text.to_typed_id}": "#{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_text, procedure.active_revision).example_value}", "champ_#{type_de_champ_epci.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_epci, procedure.active_revision).example_value}, "champ_#{type_de_champ_repetition.to_typed_id}": #{TypesDeChamp::PrefillTypeDeChamp.build(type_de_champ_repetition, procedure.active_revision).example_value}}' TEXT end diff --git a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb index a453a9e03..65c2dc484 100644 --- a/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_departement_type_de_champ_spec.rb @@ -11,7 +11,7 @@ RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do end describe 'ancestors' do - subject { described_class.build(type_de_champ) } + subject { described_class.build(type_de_champ, procedure.active_revision) } it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } end @@ -20,7 +20,7 @@ RSpec.describe TypesDeChamp::PrefillDepartementTypeDeChamp, type: :model do let(:expected_values) { "Un numéro de département
      Voir toutes les valeurs possibles" } - subject(:possible_values) { described_class.new(type_de_champ).possible_values } + subject(:possible_values) { described_class.new(type_de_champ, procedure.active_revision).possible_values } before { type_de_champ.reload } diff --git a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb index ae68772f8..25de5514b 100644 --- a/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_epci_type_de_champ_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do - let(:type_de_champ) { build(:type_de_champ_epci) } + let(:procedure) { create(:procedure) } + let(:type_de_champ) { build(:type_de_champ_epci, procedure: procedure) } let(:champ) { create(:champ_epci, type_de_champ: type_de_champ) } let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } @@ -11,7 +12,7 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do end describe 'ancestors' do - subject { described_class.new(type_de_champ) } + subject { described_class.new(type_de_champ, procedure.active_revision) } it { is_expected.to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } end @@ -20,7 +21,7 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do let(:expected_values) do departements.map { |departement| "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/epcis?codeDepartement=#{departement[:code]}" } end - subject(:possible_values) { described_class.new(type_de_champ).possible_values } + subject(:possible_values) { described_class.new(type_de_champ, procedure.active_revision).possible_values } before do VCR.insert_cassette('api_geo_departements') @@ -38,7 +39,7 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do describe '#example_value' do let(:departement_code) { departements.pick(:code) } let(:epci_code) { APIGeoService.epcis(departement_code).pick(:code) } - subject(:example_value) { described_class.new(type_de_champ).example_value } + subject(:example_value) { described_class.new(type_de_champ, procedure.active_revision).example_value } before do VCR.insert_cassette('api_geo_departements') @@ -54,7 +55,7 @@ RSpec.describe TypesDeChamp::PrefillEpciTypeDeChamp do end describe '#to_assignable_attributes' do - subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } + subject(:to_assignable_attributes) { described_class.build(type_de_champ, procedure.active_revision).to_assignable_attributes(champ, value) } context 'when the value is nil' do let(:value) { nil } diff --git a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb index 36f5aebcb..45014ec22 100644 --- a/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_pays_type_de_champ_spec.rb @@ -4,14 +4,14 @@ RSpec.describe TypesDeChamp::PrefillPaysTypeDeChamp, type: :model do let(:type_de_champ) { build(:type_de_champ_pays, procedure: procedure) } describe 'ancestors' do - subject { described_class.build(type_de_champ) } + subject { described_class.build(type_de_champ, procedure.active_revision) } it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } end describe '#possible_values' do let(:expected_values) { "Un code pays ISO 3166-2
      Voir toutes les valeurs possibles" } - subject(:possible_values) { described_class.new(type_de_champ).possible_values } + subject(:possible_values) { described_class.new(type_de_champ, procedure.active_revision).possible_values } before { type_de_champ.reload } diff --git a/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb index 3a73fedc4..1f9ddabb3 100644 --- a/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_region_type_de_champ_spec.rb @@ -11,14 +11,14 @@ RSpec.describe TypesDeChamp::PrefillRegionTypeDeChamp, type: :model do end describe 'ancestors' do - subject { described_class.build(type_de_champ) } + subject { described_class.build(type_de_champ, procedure.active_revision) } it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } end describe '#possible_values', vcr: { cassette_name: 'api_geo_regions' } do let(:expected_values) { "Un code INSEE de région
      Voir toutes les valeurs possibles" } - subject(:possible_values) { described_class.new(type_de_champ).possible_values } + subject(:possible_values) { described_class.new(type_de_champ, procedure.active_revision).possible_values } before { type_de_champ.reload } diff --git a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb index 6397953a6..d4b4d1d7a 100644 --- a/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_repetition_type_de_champ_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { cassette_name: 'api_geo_regions' } do - let(:procedure) { build(:procedure) } + let(:procedure) { create(:procedure) } let(:type_de_champ) { build(:type_de_champ_repetition, :with_types_de_champ, :with_region_types_de_champ, procedure: procedure) } let(:champ) { create(:champ_repetition, type_de_champ: type_de_champ) } - let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ).send(:prefillable_subchamps) } + let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ, procedure.active_revision).send(:prefillable_subchamps) } let(:text_repetition) { prefillable_subchamps.first } let(:integer_repetition) { prefillable_subchamps.second } let(:region_repetition) { prefillable_subchamps.third } @@ -16,13 +16,13 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { end describe 'ancestors' do - subject { described_class.build(type_de_champ) } + subject { described_class.build(type_de_champ, procedure.active_revision) } it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } end describe '#possible_values' do - subject(:possible_values) { described_class.new(type_de_champ).possible_values } + subject(:possible_values) { described_class.new(type_de_champ, procedure.active_revision).possible_values } let(:expected_value) { "Un tableau de dictionnaires avec les valeurs possibles pour chaque champ de la répétition.
      " } @@ -33,14 +33,14 @@ RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model, vcr: { end describe '#example_value' do - subject(:example_value) { described_class.new(type_de_champ).example_value } + subject(:example_value) { described_class.new(type_de_champ, procedure.active_revision).example_value } let(:expected_value) { ["{\"#{text_repetition.to_typed_id}\":\"Texte court\", \"#{integer_repetition.to_typed_id}\":\"42\", \"#{region_repetition.to_typed_id}\":\"53\"}", "{\"#{text_repetition.to_typed_id}\":\"Texte court\", \"#{integer_repetition.to_typed_id}\":\"42\", \"#{region_repetition.to_typed_id}\":\"53\"}"] } it { expect(example_value).to eq(expected_value) } end describe '#to_assignable_attributes' do - subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } + subject(:to_assignable_attributes) { described_class.build(type_de_champ, procedure.active_revision).to_assignable_attributes(champ, value) } context 'when the value is nil' do let(:value) { nil } diff --git a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb index 770203374..1a6dc0c8a 100644 --- a/spec/models/types_de_champ/prefill_type_de_champ_spec.rb +++ b/spec/models/types_de_champ/prefill_type_de_champ_spec.rb @@ -4,60 +4,62 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do include ActionView::Helpers::UrlHelper include ApplicationHelper + let(:procedure) { create(:procedure) } + describe '.build' do - subject(:built) { described_class.build(type_de_champ) } + subject(:built) { described_class.build(type_de_champ, procedure.active_revision) } context 'when the type de champ is a drop_down_list' do - let(:type_de_champ) { build(:type_de_champ_drop_down_list) } + let(:type_de_champ) { build(:type_de_champ_drop_down_list, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillDropDownListTypeDeChamp) } end context 'when the type de champ is a multiple_drop_down_list' do - let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list) } + let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillMultipleDropDownListTypeDeChamp) } end context 'when the type de champ is a pays' do - let(:type_de_champ) { build(:type_de_champ_pays) } + let(:type_de_champ) { build(:type_de_champ_pays, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillPaysTypeDeChamp) } end context 'when the type de champ is a regions' do - let(:type_de_champ) { build(:type_de_champ_regions) } + let(:type_de_champ) { build(:type_de_champ_regions, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillRegionTypeDeChamp) } end context 'when the type de champ is a repetition' do - let(:type_de_champ) { build(:type_de_champ_repetition) } + let(:type_de_champ) { build(:type_de_champ_repetition, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillRepetitionTypeDeChamp) } end context 'when the type de champ is a departements' do - let(:type_de_champ) { build(:type_de_champ_departements) } + let(:type_de_champ) { build(:type_de_champ_departements, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillDepartementTypeDeChamp) } end context 'when the type de champ is a epci' do - let(:type_de_champ) { build(:type_de_champ_epci) } + let(:type_de_champ) { build(:type_de_champ_epci, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillEpciTypeDeChamp) } end context 'when any other type de champ' do - let(:type_de_champ) { build(:type_de_champ_date) } + let(:type_de_champ) { build(:type_de_champ_date, procedure: procedure) } it { expect(built).to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) } end end describe '.wrap' do - subject(:wrapped) { described_class.wrap([build(:type_de_champ_drop_down_list), build(:type_de_champ_email)]) } + subject(:wrapped) { described_class.wrap([build(:type_de_champ_drop_down_list, procedure: procedure), build(:type_de_champ_email, procedure: procedure)], procedure.active_revision) } it 'wraps the collection' do expect(wrapped.first).to be_kind_of(TypesDeChamp::PrefillDropDownListTypeDeChamp) @@ -66,7 +68,7 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do end describe '#possible_values' do - let(:built) { described_class.build(type_de_champ) } + let(:built) { described_class.build(type_de_champ, procedure.active_revision) } subject(:possible_values) { built.possible_values } context 'when the type de champ is prefillable' do @@ -88,7 +90,7 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do let(:link_to_all_possible_values) { link_to( I18n.t("views.prefill_descriptions.edit.possible_values.link.text"), - Rails.application.routes.url_helpers.prefill_type_de_champ_path(type_de_champ.path, type_de_champ), + Rails.application.routes.url_helpers.prefill_type_de_champ_path(procedure.path, type_de_champ), title: new_tab_suffix(I18n.t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes ) @@ -113,33 +115,33 @@ RSpec.describe TypesDeChamp::PrefillTypeDeChamp, type: :model do end context 'when the type de champ is not prefillable' do - let(:type_de_champ) { build(:type_de_champ_mesri) } + let(:type_de_champ) { build(:type_de_champ_mesri, procedure: procedure) } it { expect(possible_values).to be_empty } end end describe '#example_value' do - subject(:example_value) { described_class.build(type_de_champ).example_value } + subject(:example_value) { described_class.build(type_de_champ, procedure.active_revision).example_value } context 'when the type de champ is not prefillable' do - let(:type_de_champ) { build(:type_de_champ_mesri) } + let(:type_de_champ) { build(:type_de_champ_mesri, procedure: procedure) } it { expect(example_value).to be_nil } end context 'when the type de champ is prefillable' do - let(:type_de_champ) { build(:type_de_champ_email) } + let(:type_de_champ) { build(:type_de_champ_email, procedure: procedure) } it { expect(example_value).to eq(I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")) } end end describe '#to_assignable_attributes' do - let(:type_de_champ) { build(:type_de_champ_email) } + let(:type_de_champ) { build(:type_de_champ_email, procedure: procedure) } let(:champ) { build(:champ, type_de_champ: type_de_champ) } let(:value) { "any@email.org" } - subject(:to_assignable_attributes) { described_class.build(type_de_champ).to_assignable_attributes(champ, value) } + subject(:to_assignable_attributes) { described_class.build(type_de_champ, procedure.active_revision).to_assignable_attributes(champ, value) } it { is_expected.to match({ id: champ.id, value: value }) } end diff --git a/spec/system/users/dossier_prefill_get_spec.rb b/spec/system/users/dossier_prefill_get_spec.rb index 83e4b9316..2f7274b8f 100644 --- a/spec/system/users/dossier_prefill_get_spec.rb +++ b/spec/system/users/dossier_prefill_get_spec.rb @@ -22,7 +22,7 @@ describe 'Prefilling a dossier (with a GET request):' do ] } let(:epci_value) { ['01', '200029999'] } - let(:sub_type_de_champs_repetition) { type_de_champ_repetition.active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ) } + let(:sub_type_de_champs_repetition) { procedure.active_revision.children_of(type_de_champ_repetition) } let(:text_repetition_libelle) { sub_type_de_champs_repetition.first.libelle } let(:integer_repetition_libelle) { sub_type_de_champs_repetition.second.libelle } let(:text_repetition_value) { "First repetition text" } diff --git a/spec/system/users/dossier_prefill_post_spec.rb b/spec/system/users/dossier_prefill_post_spec.rb index 2a5f65ac3..880122515 100644 --- a/spec/system/users/dossier_prefill_post_spec.rb +++ b/spec/system/users/dossier_prefill_post_spec.rb @@ -22,7 +22,7 @@ describe 'Prefilling a dossier (with a POST request):' do ] } let(:epci_value) { ['01', '200029999'] } - let(:sub_type_de_champs_repetition) { type_de_champ_repetition.active_revision_type_de_champ.revision_types_de_champ.map(&:type_de_champ) } + let(:sub_type_de_champs_repetition) { procedure.active_revision.children_of(type_de_champ_repetition) } let(:text_repetition_libelle) { sub_type_de_champs_repetition.first.libelle } let(:integer_repetition_libelle) { sub_type_de_champs_repetition.second.libelle } let(:text_repetition_value) { "First repetition text" } From 9c0cd8979a54e553b8422f19e5be5e5b0aafe683 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 21 Feb 2023 16:06:39 +0100 Subject: [PATCH 097/202] fix(graphql): context should correctly preserve demarche authorization state --- app/graphql/api/v2/context.rb | 8 ++++---- spec/graphql/demarche_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/graphql/api/v2/context.rb b/app/graphql/api/v2/context.rb index 0b23f1212..294387a61 100644 --- a/app/graphql/api/v2/context.rb +++ b/app/graphql/api/v2/context.rb @@ -42,10 +42,10 @@ class API::V2::Context < GraphQL::Query::Context return true end - # We are caching authorization logic because it is called for each node - # of the requested graph and can be expensive. Context is reset per request so it is safe. - self[:authorized] ||= Hash.new do |hash, demarche_id| - hash[demarche_id] = if self[:administrateur_id] + self[:authorized] ||= {} + + if self[:authorized][demarche.id].nil? + self[:authorized][demarche.id] = if self[:administrateur_id] demarche.administrateurs.map(&:id).include?(self[:administrateur_id]) elsif self[:token] APIToken.find_and_verify(self[:token], demarche.administrateurs).present? diff --git a/spec/graphql/demarche_spec.rb b/spec/graphql/demarche_spec.rb index 29dc4de85..db60988d0 100644 --- a/spec/graphql/demarche_spec.rb +++ b/spec/graphql/demarche_spec.rb @@ -8,6 +8,24 @@ RSpec.describe Types::DemarcheType, type: :graphql do let(:data) { subject['data'].deep_symbolize_keys } let(:errors) { subject['errors'].deep_symbolize_keys } + describe 'context should correctly preserve demarche authorization state' do + let(:query) { DEMARCHE_QUERY } + let(:admin) { create(:administrateur) } + let(:procedure) { create(:procedure, administrateurs: [admin]) } + + let(:other_admin_procedure) { create(:procedure) } + let(:context) { { administrateur_id: admin.id } } + let(:variables) { { number: procedure.id } } + + it do + result = API::V2::Schema.execute(query, variables: variables, context: context) + graphql_context = result.context + + expect(graphql_context.authorized_demarche?(procedure)).to be_truthy + expect(graphql_context.authorized_demarche?(other_admin_procedure)).to be_falsey + end + end + describe 'demarche with clone' do let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }]) } let(:procedure_clone) { procedure.clone(procedure.administrateurs.first, false) } @@ -23,6 +41,13 @@ RSpec.describe Types::DemarcheType, type: :graphql do expect(procedure.draft_revision.types_de_champ_public.first.stable_id).to eq(procedure_clone.draft_revision.types_de_champ_public.first.stable_id) } end + DEMARCHE_QUERY = <<-GRAPHQL + query($number: Int!) { + demarche(number: $number) { + number + } + } + GRAPHQL DEMARCHE_WITH_CHAMP_DESCRIPTORS_QUERY = <<-GRAPHQL query($number: Int!) { From f6fb1ceca5ebccf6f2b38b8975b54221a63abcda Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 21 Feb 2023 15:49:13 +0100 Subject: [PATCH 098/202] correctif(attachement_edit): ajoute le cmmposant
      pour supprimer un attachment via le nouveau