Merge pull request #9721 from tchak/feat-tags-id

feat(tags): all tags should have ids
This commit is contained in:
Paul Chavard 2023-11-21 13:07:20 +00:00 committed by GitHub
commit db8c57aa2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 47 deletions

View file

@ -61,42 +61,49 @@ module TagsSubstitutionConcern
DOSSIER_TAGS = [ DOSSIER_TAGS = [
{ {
id: 'dossier_motivation',
libelle: 'motivation', libelle: 'motivation',
description: 'Motivation facultative associée à la décision finale dacceptation, refus ou classement sans suite', description: 'Motivation facultative associée à la décision finale dacceptation, refus ou classement sans suite',
target: :motivation, target: :motivation,
available_for_states: Dossier::TERMINE available_for_states: Dossier::TERMINE
}, },
{ {
id: 'dossier_depose_at',
libelle: 'date de dépôt', libelle: 'date de dépôt',
description: 'Date de dépôt du dossier par lusager', description: 'Date de dépôt du dossier par lusager',
lambda: -> (d) { format_date(d.depose_at) }, lambda: -> (d) { format_date(d.depose_at) },
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'dossier_en_instruction_at',
libelle: 'date de passage en instruction', libelle: 'date de passage en instruction',
description: '', description: '',
lambda: -> (d) { format_date(d.en_instruction_at) }, lambda: -> (d) { format_date(d.en_instruction_at) },
available_for_states: Dossier::INSTRUCTION_COMMENCEE available_for_states: Dossier::INSTRUCTION_COMMENCEE
}, },
{ {
id: 'dossier_processed_at',
libelle: 'date de décision', libelle: 'date de décision',
description: 'Date de la décision dacceptation, refus, ou classement sans suite', description: 'Date de la décision dacceptation, refus, ou classement sans suite',
lambda: -> (d) { format_date(d.processed_at) }, lambda: -> (d) { format_date(d.processed_at) },
available_for_states: Dossier::TERMINE available_for_states: Dossier::TERMINE
}, },
{ {
id: 'dossier_procedure_libelle',
libelle: 'libellé démarche', libelle: 'libellé démarche',
description: '', description: '',
lambda: -> (d) { d.procedure.libelle }, lambda: -> (d) { d.procedure.libelle },
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'dossier_number',
libelle: 'numéro du dossier', libelle: 'numéro du dossier',
description: '', description: '',
target: :id, target: :id,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'dossier_service_name',
libelle: 'nom du service', libelle: 'nom du service',
description: 'Le nom du service instructeur qui traite le dossier', description: 'Le nom du service instructeur qui traite le dossier',
lambda: -> (d) { d.procedure.organisation_name || '' }, lambda: -> (d) { d.procedure.organisation_name || '' },
@ -106,18 +113,21 @@ module TagsSubstitutionConcern
DOSSIER_TAGS_FOR_MAIL = [ DOSSIER_TAGS_FOR_MAIL = [
{ {
id: 'dossier_url',
libelle: 'lien dossier', libelle: 'lien dossier',
description: '', description: '',
lambda: -> (d) { external_link(dossier_url(d)) }, lambda: -> (d) { external_link(dossier_url(d)) },
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'dossier_attestation_url',
libelle: 'lien attestation', libelle: 'lien attestation',
description: '', description: '',
lambda: -> (d) { external_link(attestation_dossier_url(d)) }, lambda: -> (d) { external_link(attestation_dossier_url(d)) },
available_for_states: [Dossier.states.fetch(:accepte)] available_for_states: [Dossier.states.fetch(:accepte)]
}, },
{ {
id: 'dossier_motivation_url',
libelle: 'lien document justificatif', libelle: 'lien document justificatif',
description: '', description: '',
lambda: -> (d) { lambda: -> (d) {
@ -133,18 +143,21 @@ module TagsSubstitutionConcern
INDIVIDUAL_TAGS = [ INDIVIDUAL_TAGS = [
{ {
id: 'individual_gender',
libelle: 'civilité', libelle: 'civilité',
description: 'M., Mme', description: 'M., Mme',
target: :gender, target: :gender,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'individual_first_name',
libelle: 'nom', libelle: 'nom',
description: "nom de l'usager", description: "nom de l'usager",
target: :nom, target: :nom,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'individual_last_name',
libelle: 'prénom', libelle: 'prénom',
description: "prénom de l'usager", description: "prénom de l'usager",
target: :prenom, target: :prenom,
@ -154,30 +167,35 @@ module TagsSubstitutionConcern
ENTREPRISE_TAGS = [ ENTREPRISE_TAGS = [
{ {
id: 'entreprise_siren',
libelle: 'SIREN', libelle: 'SIREN',
description: '', description: '',
target: :siren, target: :siren,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'entreprise_numero_tva_intracommunautaire',
libelle: 'numéro de TVA intracommunautaire', libelle: 'numéro de TVA intracommunautaire',
description: '', description: '',
target: :numero_tva_intracommunautaire, target: :numero_tva_intracommunautaire,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'entreprise_siret_siege_social',
libelle: 'SIRET du siège social', libelle: 'SIRET du siège social',
description: '', description: '',
target: :siret_siege_social, target: :siret_siege_social,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'entreprise_raison_sociale',
libelle: 'raison sociale', libelle: 'raison sociale',
description: '', description: '',
target: :raison_sociale, target: :raison_sociale,
available_for_states: Dossier::SOUMIS available_for_states: Dossier::SOUMIS
}, },
{ {
id: 'entreprise_adresse',
libelle: 'adresse', libelle: 'adresse',
description: '', description: '',
target: :inline_adresse, target: :inline_adresse,
@ -187,6 +205,7 @@ module TagsSubstitutionConcern
ROUTAGE_TAGS = [ ROUTAGE_TAGS = [
{ {
id: 'dossier_groupe_instructeur',
libelle: 'groupe instructeur', libelle: 'groupe instructeur',
description: 'Le groupe instructeur en charge du dossier', description: 'Le groupe instructeur en charge du dossier',
lambda: -> (d) { d.groupe_instructeur&.label }, lambda: -> (d) { d.groupe_instructeur&.label },
@ -194,37 +213,40 @@ module TagsSubstitutionConcern
} }
] ]
SHARED_TAG_LIBELLES = (DOSSIER_TAGS + DOSSIER_TAGS_FOR_MAIL + INDIVIDUAL_TAGS + ENTREPRISE_TAGS + ROUTAGE_TAGS).map { |tag| tag[:libelle] } SHARED_TAG_IDS = (DOSSIER_TAGS + DOSSIER_TAGS_FOR_MAIL + INDIVIDUAL_TAGS + ENTREPRISE_TAGS + ROUTAGE_TAGS).map { _1[:id] }
def identity_tags
if procedure.for_individual?
INDIVIDUAL_TAGS
else
ENTREPRISE_TAGS
end
end
def routage_tags
if procedure.routing_enabled?
ROUTAGE_TAGS
else
[]
end
end
def tags def tags
if procedure.for_individual? tags_for_dossier_state(identity_tags + dossier_tags + champ_public_tags + champ_private_tags + routage_tags)
identity_tags = INDIVIDUAL_TAGS
else
identity_tags = ENTREPRISE_TAGS
end
routage_tags = []
if procedure.routing_enabled?
routage_tags = ROUTAGE_TAGS
end
filter_tags(identity_tags + dossier_tags + champ_public_tags + champ_private_tags + routage_tags)
end end
def used_type_de_champ_tags(text) def used_type_de_champ_tags(text)
used_tags_and_libelle_for(text).filter_map do |(tag, libelle)| used_tags_and_libelle_for(text).filter_map do |(tag, libelle)|
if !tag.in?(SHARED_TAG_LIBELLES) if tag.nil?
if tag.start_with?('tdc') [libelle]
[libelle, tag.gsub('tdc', '').to_i] elsif !tag.in?(SHARED_TAG_IDS) && tag.start_with?('tdc')
else [libelle, tag.gsub(/^tdc/, '').to_i]
[tag]
end
end end
end end
end end
def used_tags_for(text) def used_tags_for(text)
used_tags_and_libelle_for(text).map { |(tag, _)| tag } used_tags_and_libelle_for(text).map { _1.first.nil? ? _1.second : _1.first }
end end
private private
@ -252,7 +274,7 @@ module TagsSubstitutionConcern
DOSSIER_TAGS DOSSIER_TAGS
end end
def filter_tags(tags) def tags_for_dossier_state(tags)
# Implementation note: emails and attestation generations are generally # Implementation note: emails and attestation generations are generally
# triggerred by changes to the dossiers state. The email or attestation # triggerred by changes to the dossiers state. The email or attestation
# is generated right after the dossier has reached its new state. # is generated right after the dossier has reached its new state.
@ -302,7 +324,7 @@ module TagsSubstitutionConcern
[INDIVIDUAL_TAGS, dossier.individual], [INDIVIDUAL_TAGS, dossier.individual],
[ENTREPRISE_TAGS, dossier.etablissement&.entreprise] [ENTREPRISE_TAGS, dossier.etablissement&.entreprise]
].filter_map do |(tags, data)| ].filter_map do |(tags, data)|
data && [filter_tags(tags).index_by { _1[:id].presence || _1[:libelle] }, data] data && [tags_for_dossier_state(tags).index_by { _1[:id] }, data]
end end
tags_and_datas.reduce(tokens) do |tokens, (tags, data)| tags_and_datas.reduce(tokens) do |tokens, (tags, data)|
@ -337,13 +359,17 @@ module TagsSubstitutionConcern
end end
def procedure_types_de_champ_tags def procedure_types_de_champ_tags
filter_tags(types_de_champ_tags(procedure.types_de_champ_public_for_tags, Dossier::SOUMIS) + types_de_champ_tags(procedure.types_de_champ_private_for_tags, Dossier::INSTRUCTION_COMMENCEE)) tags_for_dossier_state(types_de_champ_tags(procedure.types_de_champ_public_for_tags, Dossier::SOUMIS) +
types_de_champ_tags(procedure.types_de_champ_private_for_tags, Dossier::INSTRUCTION_COMMENCEE) +
identity_tags + dossier_tags + ROUTAGE_TAGS)
end end
def parse_tags(text) def parse_tags(text)
tags = procedure_types_de_champ_tags.index_by { _1[:libelle] } tags = procedure_types_de_champ_tags.index_by { _1[:libelle] }
TagsParser.parse(text).map do |token| # MD5 should be enough and it avoids long key
tokens = Rails.cache.fetch(["parse_tags_v2", Digest::MD5.hexdigest(text)], expires_in: 1.day) { TagsParser.parse(text) }
tokens.map do |token|
case token case token
in { tag: tag } if tags.key?(tag) in { tag: tag } if tags.key?(tag)
{ tag: tag, id: tags.fetch(tag).fetch(:id) } { tag: tag, id: tags.fetch(tag).fetch(:id) }
@ -354,18 +380,15 @@ module TagsSubstitutionConcern
end end
def used_tags_and_libelle_for(text) def used_tags_and_libelle_for(text)
# MD5 should be enough and it avoids long key parse_tags(text).filter_map do |token|
Rails.cache.fetch(["parse_tags", Digest::MD5.hexdigest(text)], expires_in: 1.day) do case token
parse_tags(text).filter_map do |token| in { tag: tag, id: id }
case token [id, tag]
in { tag: tag, id: id } in { tag: tag }
[id, tag] [nil, tag]
in { tag: tag } else
[tag] nil
else end
nil
end
end
end end
end end
end end

View file

@ -4,7 +4,7 @@ RSpec.describe NotificationMailer, type: :mailer do
let(:procedure) { create(:simple_procedure, :with_service) } let(:procedure) { create(:simple_procedure, :with_service) }
describe 'send_en_construction_notification' do describe 'send_en_construction_notification' do
let(:dossier) { create(:dossier, :en_construction, :with_individual, user: user, procedure: procedure) } let(:dossier) { create(:dossier, :en_construction, :with_individual, user: user, procedure:) }
subject(:mail) { described_class.send_en_construction_notification(dossier) } subject(:mail) { described_class.send_en_construction_notification(dossier) }
@ -20,7 +20,8 @@ RSpec.describe NotificationMailer, type: :mailer do
end end
context "with a custom template" do context "with a custom template" do
let(:email_template) { create(:initiated_mail, subject: 'Email subject', body: 'Your dossier was received. Thanks.') } let(:email_template) { create(:initiated_mail, subject: 'Email subject', body: 'Your dossier was received. Thanks.', procedure:) }
before do before do
dossier.procedure.initiated_mail = email_template dossier.procedure.initiated_mail = email_template
end end
@ -34,8 +35,8 @@ RSpec.describe NotificationMailer, type: :mailer do
end end
describe 'send_en_instruction_notification' do describe 'send_en_instruction_notification' do
let(:dossier) { create(:dossier, :en_instruction, :with_individual, :with_service, user: user, procedure: procedure) } let(:dossier) { create(:dossier, :en_instruction, :with_individual, :with_service, user: user, procedure:) }
let(:email_template) { create(:received_mail, subject: 'Email subject', body: 'Your dossier was processed. Thanks.') } let(:email_template) { create(:received_mail, subject: 'Email subject', body: 'Your dossier was processed. Thanks.', procedure:) }
before do before do
dossier.procedure.received_mail = email_template dossier.procedure.received_mail = email_template
@ -55,7 +56,7 @@ RSpec.describe NotificationMailer, type: :mailer do
end end
context 'when the template body contains tags' do context 'when the template body contains tags' do
let(:email_template) { create(:received_mail, subject: 'Email subject', body: 'Hello --nom--, your dossier --lien dossier-- was processed.') } let(:email_template) { create(:received_mail, subject: 'Email subject', body: 'Hello --nom--, your dossier --lien dossier-- was processed.', procedure:) }
it 'replaces value tags with the proper value' do it 'replaces value tags with the proper value' do
expect(mail.body).to have_content(dossier.individual.nom) expect(mail.body).to have_content(dossier.individual.nom)
@ -67,7 +68,7 @@ RSpec.describe NotificationMailer, type: :mailer do
end end
context 'when the template body contains HTML' do context 'when the template body contains HTML' do
let(:email_template) { create(:received_mail, body: 'Your <b>dossier</b> was processed. <iframe src="#">Foo</iframe>') } let(:email_template) { create(:received_mail, body: 'Your <b>dossier</b> was processed. <iframe src="#">Foo</iframe>', procedure:) }
it 'allows basic formatting tags' do it 'allows basic formatting tags' do
expect(mail.body).to include('<b>dossier</b>') expect(mail.body).to include('<b>dossier</b>')
@ -85,8 +86,8 @@ RSpec.describe NotificationMailer, type: :mailer do
describe 'subject length' do describe 'subject length' do
let(:procedure) { create(:simple_procedure, libelle: "My super long title " + ("xo " * 100)) } let(:procedure) { create(:simple_procedure, libelle: "My super long title " + ("xo " * 100)) }
let(:dossier) { create(:dossier, :accepte, :with_individual, :with_service, user: user, procedure: procedure) } let(:dossier) { create(:dossier, :accepte, :with_individual, :with_service, user: user, procedure:) }
let(:email_template) { create(:closed_mail, subject:, body: 'Your dossier was accepted. Thanks.') } let(:email_template) { create(:closed_mail, subject:, body: 'Your dossier was accepted. Thanks.', procedure:) }
before do before do
dossier.procedure.closed_mail = email_template dossier.procedure.closed_mail = email_template
@ -108,8 +109,8 @@ RSpec.describe NotificationMailer, type: :mailer do
describe 'subject with apostrophe' do describe 'subject with apostrophe' do
let(:procedure) { create(:simple_procedure, libelle: "Mon titre avec l'apostrophe") } let(:procedure) { create(:simple_procedure, libelle: "Mon titre avec l'apostrophe") }
let(:dossier) { create(:dossier, :en_instruction, :with_individual, :with_service, user: user, procedure: procedure) } let(:dossier) { create(:dossier, :en_instruction, :with_individual, :with_service, user: user, procedure:) }
let(:email_template) { create(:received_mail, subject:, body: 'Your dossier was accepted. Thanks.') } let(:email_template) { create(:received_mail, subject:, body: 'Your dossier was accepted. Thanks.', procedure:) }
before do before do
dossier.procedure.received_mail = email_template dossier.procedure.received_mail = email_template

View file

@ -487,7 +487,7 @@ describe TagsSubstitutionConcern, type: :model do
] ]
end end
it { is_expected.to eq(["tdc#{procedure.draft_revision.types_de_champ.first.stable_id}", 'numéro du dossier', 'yolo']) } it { is_expected.to eq(["tdc#{procedure.draft_revision.types_de_champ.first.stable_id}", 'dossier_number', 'yolo']) }
end end
describe 'used_type_de_champ_tags' do describe 'used_type_de_champ_tags' do