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