feat(attestation): list tags errors and substitute missing tag by libelle
This commit is contained in:
parent
6f49dd892d
commit
f7484eb0e5
16 changed files with 139 additions and 55 deletions
|
@ -52,6 +52,8 @@ module Administrateurs
|
|||
['Redo', 'redo', 'arrow-go-forward-line']
|
||||
]
|
||||
]
|
||||
|
||||
@attestation_template.validate
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -67,15 +69,11 @@ module Administrateurs
|
|||
attestation_params[:signature] = uninterlace_png(signature_file)
|
||||
end
|
||||
|
||||
@attestation_template.update!(attestation_params)
|
||||
flash.notice = "Le modèle de l’attestation a été modifié"
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream
|
||||
format.html do
|
||||
redirect_to edit_admin_procedure_attestation_template_path(@procedure)
|
||||
end
|
||||
if !@attestation_template.update(attestation_params)
|
||||
flash.alert = "Le modèle de l’attestation contient des erreurs et n'a pas pu être enregistré. Corriger les erreurs."
|
||||
end
|
||||
|
||||
render :update
|
||||
end
|
||||
|
||||
def create = update
|
||||
|
|
|
@ -251,8 +251,15 @@ module TagsSubstitutionConcern
|
|||
}.reject { |_, ary| ary.empty? }
|
||||
end
|
||||
|
||||
def used_type_de_champ_tags(text)
|
||||
used_tags_and_libelle_for(text).filter_map do |(tag, libelle)|
|
||||
def used_type_de_champ_tags(text_or_tiptap)
|
||||
used_tags =
|
||||
if text_or_tiptap.respond_to?(:deconstruct_keys) # hash pattern matching
|
||||
TiptapService.new.used_tags_and_libelle_for(text_or_tiptap.deep_symbolize_keys)
|
||||
else
|
||||
used_tags_and_libelle_for(text_or_tiptap.to_s)
|
||||
end
|
||||
|
||||
used_tags.filter_map do |(tag, libelle)|
|
||||
if tag.nil?
|
||||
[libelle]
|
||||
elsif !tag.in?(SHARED_TAG_IDS) && tag.start_with?('tdc')
|
||||
|
@ -265,11 +272,11 @@ module TagsSubstitutionConcern
|
|||
used_tags_and_libelle_for(text).map { _1.first.nil? ? _1.second : _1.first }
|
||||
end
|
||||
|
||||
def tags_substitutions(tokens, dossier, escape: true)
|
||||
def tags_substitutions(tags_and_libelles, dossier, escape: true)
|
||||
# NOTE:
|
||||
# - tokens est un simple Set d'ids (pas la même structure que dans replace_tags)
|
||||
# - dans replace_tags, on fait référence à des tags avec ou sans id, mais pas ici,
|
||||
# a priori inutile car tiptap ne fait référence qu'aux ids.
|
||||
# - tags_and_libelles est un simple Set de couples (tag_id, libelle) (pas la même structure que dans replace_tags)
|
||||
# - dans `replace_tags`, on fait référence à des tags avec ou sans id, mais pas ici,
|
||||
# (inutile car tiptap ne référence que des ids)
|
||||
|
||||
@escape_unsafe_tags = escape
|
||||
|
||||
|
@ -283,12 +290,12 @@ module TagsSubstitutionConcern
|
|||
end
|
||||
end
|
||||
|
||||
tokens.index_with do |token|
|
||||
case flat_tags[token]
|
||||
tags_and_libelles.each_with_object({}) do |(tag_id, libelle), substitutions|
|
||||
substitutions[tag_id] = case flat_tags[tag_id]
|
||||
in tag, data
|
||||
replace_tag(tag, data)
|
||||
else
|
||||
token
|
||||
else # champ not in dossier, for example during preview on draft revision
|
||||
libelle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,13 +5,14 @@ class TiptapService
|
|||
children(node[:content], substitutions, 0)
|
||||
end
|
||||
|
||||
def used_tags(node, tags = Set.new)
|
||||
# NOTE: node must be deep symbolized keys
|
||||
def used_tags_and_libelle_for(node, tags = Set.new)
|
||||
case node
|
||||
in type: 'mention', attrs: { id: }
|
||||
tags << id
|
||||
in { content: } if content.is_a?(Array)
|
||||
content.each { used_tags(_1, tags) }
|
||||
else
|
||||
in type: 'mention', attrs: { id:, label: }, **rest
|
||||
tags << [id, label]
|
||||
in { content:, **rest } if content.is_a?(Array)
|
||||
content.each { used_tags_and_libelle_for(_1, tags) }
|
||||
in type:, **rest
|
||||
# noop
|
||||
end
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
class TagsValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
procedure = record.procedure
|
||||
tags = record.used_type_de_champ_tags(value.to_s)
|
||||
tags = record.used_type_de_champ_tags(value)
|
||||
|
||||
invalid_tags = tags.filter_map do |(tag, stable_id)|
|
||||
tag if stable_id.nil?
|
||||
end
|
||||
|
||||
invalid_for_draft_revision = invalid_tags_for_revision(record, attribute, tags, procedure.draft_revision)
|
||||
invalid_for_draft_revision = invalid_tags_for_revision(record, tags, procedure.draft_revision)
|
||||
|
||||
invalid_for_published_revision = if procedure.published_revision_id.present?
|
||||
invalid_tags_for_revision(record, attribute, tags, procedure.published_revision)
|
||||
invalid_tags_for_revision(record, tags, procedure.published_revision)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -18,7 +18,7 @@ class TagsValidator < ActiveModel::EachValidator
|
|||
invalid_for_previous_revision = procedure
|
||||
.revisions_with_pending_dossiers
|
||||
.flat_map do |revision|
|
||||
invalid_tags_for_revision(record, attribute, tags, revision)
|
||||
invalid_tags_for_revision(record, tags, revision)
|
||||
end.uniq
|
||||
|
||||
# champ is added in draft revision but not yet published
|
||||
|
@ -48,7 +48,7 @@ class TagsValidator < ActiveModel::EachValidator
|
|||
end
|
||||
end
|
||||
|
||||
def invalid_tags_for_revision(record, attribute, tags, revision)
|
||||
def invalid_tags_for_revision(record, tags, revision)
|
||||
revision_stable_ids = revision
|
||||
.revision_types_de_champ
|
||||
.filter { !_1.child? }
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#autosave-notice.fr-badge.fr-badge--sm.fr-badge--success= t(".form_saved")
|
||||
- success = local_assigns.fetch(:success, true)
|
||||
#autosave-notice.fr-badge.fr-badge--sm{ class: class_names("fr-badge--success" => success, "fr-badge--error" => !success) }= success ? t(".form_saved") : t(".form_error")
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
= form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure), html: { multipart: true },
|
||||
data: { turbo: 'true',
|
||||
controller: 'autosubmit attestation',
|
||||
autosubmit_debounce_delay_value: 2000,
|
||||
autosubmit_debounce_delay_value: 1000,
|
||||
attestation_logo_attachment_official_label_value: AttestationTemplate.human_attribute_name(:logo_additional),
|
||||
attestation_logo_attachment_free_label_value: AttestationTemplate.human_attribute_name(:logo) } do |f|
|
||||
|
||||
|
@ -65,12 +65,17 @@
|
|||
- c.with_hint { "Exemple: Direction interministérielle du numérique. 2 lignes maximum" }
|
||||
|
||||
.fr-fieldset__element.fr-mt-2w
|
||||
%label.fr-label.fr-h4
|
||||
= AttestationTemplate.human_attribute_name :body
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
.fr-input-group{ class: class_names("fr-input-group--error" => f.object.errors.include?(:json_body)) }
|
||||
%label.fr-label.fr-h4
|
||||
= AttestationTemplate.human_attribute_name :body
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
|
||||
.editor{ data: { tiptap_target: 'editor' } }
|
||||
= f.hidden_field :tiptap_body, data: { tiptap_target: 'input' }
|
||||
#editor.editor{ data: { tiptap_target: 'editor' }, aria: { describedby: dom_id(f.object, "json-body-messages")} }
|
||||
= f.hidden_field :tiptap_body, data: { tiptap_target: 'input' }
|
||||
|
||||
.fr-error-text{ id: dom_id(f.object, "json-body-messages"), class: class_names("hidden" => !f.object.errors.include?(:json_body)) }
|
||||
- if f.object.errors.include?(:json_body)
|
||||
= render partial: "shared/errors_list", locals: { object: f.object, attribute: :json_body }
|
||||
|
||||
.fr-fieldset__element
|
||||
.flex.flex-gap-2
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= turbo_stream.show 'autosave-notice'
|
||||
= turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice')
|
||||
= turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice', locals: { success: !@attestation_template.changed? })
|
||||
= turbo_stream.hide 'autosave-notice', delay: 15000
|
||||
|
||||
- if @attestation_template.logo_blob&.previously_new_record?
|
||||
|
@ -9,3 +9,12 @@
|
|||
- if @attestation_template.signature_blob&.previously_new_record?
|
||||
= turbo_stream.update dom_id(@attestation_template, :signature_attachment) do
|
||||
= render(Attachment::EditComponent.new(attached_file: @attestation_template.signature, direct_upload: false))
|
||||
|
||||
- body_id = dom_id(@attestation_template, "json-body-messages")
|
||||
- if @attestation_template.errors.include?(:json_body)
|
||||
= turbo_stream.update body_id do
|
||||
= render partial: "shared/errors_list", locals: { object: @attestation_template, attribute: :json_body }
|
||||
= turbo_stream.show body_id
|
||||
- else
|
||||
= turbo_stream.hide body_id
|
||||
= turbo_stream.update body_id, nil
|
||||
|
|
3
app/views/shared/_errors_list.html.haml
Normal file
3
app/views/shared/_errors_list.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
%ul.list-style-type-none.fr-pl-0
|
||||
- object.errors.full_messages_for(attribute).map do |error_message|
|
||||
%li= error_message
|
|
@ -20,8 +20,8 @@ fr:
|
|||
one: contient la balise "%{tags}" qui n’existe pas. Supprimer la balise
|
||||
other: contient %{count} balises (%{tags}) qui n’existent pas. Supprimer les balises
|
||||
champ_missing_in_draft_revision:
|
||||
one: contient la balise "%{tags}" qui a été supprimée mais la suppression n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer
|
||||
other: contient %{count} balises (%{tags}) qui ont été supprimées mais la suppression n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer
|
||||
one: contient la balise "%{tags}" qui a été supprimée dans les modifications en cours du formulaire. Supprimer cette balise ou réinitialiser les modifications du formulaire puis recommencer
|
||||
other: contient %{count} balises (%{tags}) qui ont été supprimées dans les modifications en coure du formumlaire. Supprimer cette balise ou réinitialiser les modifications du formulaire puis recommencer
|
||||
champ_missing_in_published_revision:
|
||||
one: contient la balise "%{tags}" qui n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer
|
||||
other: contient %{count} balises (%{tags}) qui ne sont pas encore publiées. Publier la nouvelle version de la démarche et recommencer
|
||||
|
@ -38,3 +38,6 @@ fr:
|
|||
body:
|
||||
format: Le champ « Contenu de l’attestation » %{message}
|
||||
<<: *tags_errors
|
||||
json_body:
|
||||
format: Le champ « Contenu de l’attestation » %{message}
|
||||
<<: *tags_errors
|
||||
|
|
|
@ -53,3 +53,4 @@ en:
|
|||
submit: Publish
|
||||
autosave_notice:
|
||||
form_saved: "Form saved"
|
||||
form_error: "Form in error"
|
||||
|
|
|
@ -53,3 +53,4 @@ fr:
|
|||
submit: Publier
|
||||
autosave_notice:
|
||||
form_saved: "Formulaire enregistré"
|
||||
form_error: "Formulaire en erreur"
|
||||
|
|
|
@ -14,12 +14,12 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
|||
activated: false,
|
||||
tiptap_body: {
|
||||
type: :doc,
|
||||
content: [
|
||||
{
|
||||
type: :paragraph,
|
||||
content: [{ text: "Yo from spec" }]
|
||||
}
|
||||
]
|
||||
content: [
|
||||
{
|
||||
type: :paragraph,
|
||||
content: [{ text: "Yo from spec", type: :text }]
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
@ -131,11 +131,13 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
|||
let(:attestation_template) { nil }
|
||||
|
||||
subject do
|
||||
post :create, params: { procedure_id: procedure.id, attestation_template: update_params }
|
||||
post :create, params: { procedure_id: procedure.id, attestation_template: update_params }, format: :turbo_stream
|
||||
response.body
|
||||
end
|
||||
|
||||
context 'when attestation template is valid' do
|
||||
render_views
|
||||
|
||||
it "create template" do
|
||||
subject
|
||||
attestation_template = procedure.reload.attestation_template
|
||||
|
@ -147,7 +149,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
|||
expect(attestation_template.activated).to eq(false)
|
||||
expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body])
|
||||
|
||||
expect(flash.notice).to be_present
|
||||
expect(response.body).to include("Formulaire enregistré")
|
||||
end
|
||||
|
||||
context "with files" do
|
||||
|
@ -165,8 +167,9 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
|||
end
|
||||
|
||||
describe 'PATCH update' do
|
||||
render_views
|
||||
subject do
|
||||
patch :update, params: { procedure_id: procedure.id, attestation_template: update_params }
|
||||
patch :update, params: { procedure_id: procedure.id, attestation_template: update_params }, format: :turbo_stream
|
||||
response.body
|
||||
end
|
||||
|
||||
|
@ -182,7 +185,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
|||
expect(attestation_template.activated).to eq(false)
|
||||
expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body])
|
||||
|
||||
expect(flash.notice).to be_present
|
||||
expect(response.body).to include("Formulaire enregistré")
|
||||
end
|
||||
|
||||
context "with files" do
|
||||
|
@ -196,6 +199,18 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
|||
expect(attestation_template.signature.download).to eq(signature.read)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with error' do
|
||||
let(:update_params) do
|
||||
super().merge(tiptap_body: { type: :doc, content: [{ type: :mention, attrs: { id: "tdc12", label: "oops" } }] }.to_json)
|
||||
end
|
||||
|
||||
it "render error" do
|
||||
subject
|
||||
expect(response.body).to include("Formulaire en erreur")
|
||||
expect(response.body).to include('Supprimer cette balise')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -241,7 +241,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
|
|||
render_views
|
||||
let(:body) { "body --#{removed_type_de_champ.libelle}--" }
|
||||
|
||||
it { expect(response.body).to have_content("Le champ « Contenu de l’attestation » contient la balise \"#{removed_type_de_champ.libelle}\" qui a été supprimée mais la suppression n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer") }
|
||||
it { expect(response.body).to have_content("Le champ « Contenu de l’attestation » contient la balise \"#{removed_type_de_champ.libelle}\" qui a été supprimée dans les modifications en cours du formulaire. Supprimer cette balise ou réinitialiser les modifications du formulaire puis recommencer") }
|
||||
end
|
||||
|
||||
context 'with removed and published' do
|
||||
|
|
|
@ -37,20 +37,29 @@ describe TagsSubstitutionConcern, type: :model do
|
|||
let(:etablissement) { create(:etablissement) }
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure:, individual:, etablissement:) }
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:tags) { Set.new(["dossier_number"]) }
|
||||
let(:tags) { Set.new([["dossier_number", "numéro de dossier"]]) }
|
||||
|
||||
subject { template_concern.tags_substitutions(tags, dossier) }
|
||||
|
||||
context 'dossiers metadata' do
|
||||
before { travel_to(Time.zone.local(2024, 1, 15, 12)) }
|
||||
let(:tags) { Set.new(["dossier_number", "dossier_depose_at", "dossier_processed_at", "dossier_procedure_libelle"]) }
|
||||
let(:tags) do
|
||||
Set.new([
|
||||
["dossier_number", "n° de dossier"],
|
||||
["dossier_depose_at", "date de dépôt"],
|
||||
["dossier_processed_at", "date d’instruction"],
|
||||
["dossier_procedure_libelle", "Nom de la démarche"],
|
||||
["tdc_123", "Un champ"]
|
||||
])
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to eq(
|
||||
"dossier_number" => dossier.id.to_s,
|
||||
"dossier_depose_at" => "15/01/2024",
|
||||
"dossier_processed_at" => "",
|
||||
"dossier_procedure_libelle" => procedure.libelle
|
||||
"dossier_procedure_libelle" => procedure.libelle,
|
||||
"tdc_123" => "Un champ"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,7 +57,7 @@ RSpec.describe TiptapService do
|
|||
},
|
||||
{
|
||||
type: 'mention',
|
||||
attrs: { id: 'name' },
|
||||
attrs: { id: 'name', label: 'Nom' },
|
||||
marks: [{ type: 'bold' }, { type: 'underline' }]
|
||||
},
|
||||
{
|
||||
|
@ -173,7 +173,7 @@ RSpec.describe TiptapService do
|
|||
|
||||
describe '#used_tags' do
|
||||
it 'returns used tags' do
|
||||
expect(described_class.new.used_tags(json)).to eq(Set.new(['name']))
|
||||
expect(described_class.new.used_tags_and_libelle_for(json)).to eq(Set.new([['name', 'Nom']]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -90,6 +90,7 @@ describe 'As an administrateur, I want to manage the procedure’s attestation',
|
|||
}
|
||||
expect(attestation.label_logo).to eq("System Test")
|
||||
expect(attestation.activated?).to be_falsey
|
||||
expect(page).to have_content("Formulaire enregistré")
|
||||
|
||||
click_on "date de décision"
|
||||
|
||||
|
@ -126,5 +127,35 @@ describe 'As an administrateur, I want to manage the procedure’s attestation',
|
|||
fill_in "Contenu du pied de page", with: ["line1", "line2", "line3", "line4"].join("\n")
|
||||
expect(page).to have_field("Contenu du pied de page", with: "line1\nline2\nline3line4")
|
||||
end
|
||||
|
||||
context "tag in error" do
|
||||
before do
|
||||
tdc = procedure.active_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age')
|
||||
procedure.publish_revision!
|
||||
|
||||
attestation = procedure.build_attestation_template_v2(json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, label_logo: "test")
|
||||
attestation.json_body["content"] << { type: :mention, attrs: { id: "tdc#{tdc.stable_id}", label: tdc.libelle } }
|
||||
attestation.save!
|
||||
|
||||
procedure.draft_revision.remove_type_de_champ(tdc)
|
||||
end
|
||||
|
||||
scenario do
|
||||
visit edit_admin_procedure_attestation_template_v2_path(procedure)
|
||||
expect(page).to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"")
|
||||
|
||||
click_on "date de décision"
|
||||
|
||||
expect(page).to have_content("Formulaire en erreur")
|
||||
expect(page).to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"")
|
||||
|
||||
page.execute_script("document.getElementById('attestation_template_tiptap_body').type = 'text'")
|
||||
fill_in "attestation_template[tiptap_body]", with: AttestationTemplate::TIPTAP_BODY_DEFAULT.to_json
|
||||
|
||||
expect(page).to have_content("Formulaire enregistré")
|
||||
expect(page).not_to have_content("Formulaire en erreur")
|
||||
expect(page).not_to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue