feat(attestation): list tags errors and substitute missing tag by libelle

This commit is contained in:
Colin Darie 2024-02-01 18:55:41 +01:00
parent 6f49dd892d
commit f7484eb0e5
No known key found for this signature in database
GPG key ID: 8C76CADD40253590
16 changed files with 139 additions and 55 deletions

View file

@ -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 lattestation 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 lattestation contient des erreurs et n'a pas pu être enregistré. Corriger les erreurs."
end
render :update
end
def create = update

View file

@ -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

View file

@ -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

View file

@ -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? }

View file

@ -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")

View file

@ -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

View file

@ -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

View 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