From ad3c5a74933476a203af58f0b01d3e14537b9341 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 29 May 2024 11:31:31 +0200 Subject: [PATCH 01/17] chore(schema): add attestation_templates#state --- app/models/attestation_template.rb | 5 +++++ ...40514164228_add_state_to_attestation_templates.rb | 7 +++++++ ...2084927_add_attestation_template_unicity_index.rb | 12 ++++++++++++ db/schema.rb | 3 ++- 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240514164228_add_state_to_attestation_templates.rb create mode 100644 db/migrate/20240522084927_add_attestation_template_unicity_index.rb diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index eea88355c..c56c030b0 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -7,6 +7,11 @@ class AttestationTemplate < ApplicationRecord has_one_attached :logo has_one_attached :signature + enum state: { + draft: 'draft', + published: 'published' + } + validates :title, tags: true, if: -> { procedure.present? && version == 1 } validates :body, tags: true, if: -> { procedure.present? && version == 1 } validates :json_body, tags: true, if: -> { procedure.present? && version == 2 } diff --git a/db/migrate/20240514164228_add_state_to_attestation_templates.rb b/db/migrate/20240514164228_add_state_to_attestation_templates.rb new file mode 100644 index 000000000..8a7842502 --- /dev/null +++ b/db/migrate/20240514164228_add_state_to_attestation_templates.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddStateToAttestationTemplates < ActiveRecord::Migration[7.0] + def change + add_column :attestation_templates, :state, :string, default: 'published' + end +end diff --git a/db/migrate/20240522084927_add_attestation_template_unicity_index.rb b/db/migrate/20240522084927_add_attestation_template_unicity_index.rb new file mode 100644 index 000000000..5dd74d831 --- /dev/null +++ b/db/migrate/20240522084927_add_attestation_template_unicity_index.rb @@ -0,0 +1,12 @@ +class AddAttestationTemplateUnicityIndex < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + + def change + # this index was not created on production + if index_exists?(:attestation_templates, [:procedure_id, :version]) + remove_index :attestation_templates, [:procedure_id, :version], unique: true, algorithm: :concurrently + end + + add_index :attestation_templates, [:procedure_id, :version, :state], name: "index_attestation_templates_on_procedure_version_state", unique: true, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index b33a10582..28c317cac 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -175,10 +175,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_27_090508) do t.string "label_logo" t.boolean "official_layout", default: true, null: false t.integer "procedure_id" + t.string "state", default: "published" t.text "title" t.datetime "updated_at", precision: nil, null: false t.integer "version", default: 1, null: false - t.index ["procedure_id", "version"], name: "index_attestation_templates_on_procedure_id_and_version", unique: true + t.index ["procedure_id", "version", "state"], name: "index_attestation_templates_on_procedure_version_state", unique: true end create_table "attestations", id: :serial, force: :cascade do |t| From 45fcfd2d1608b24ba4da662279c80d66fab8aad8 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 28 May 2024 17:54:29 +0200 Subject: [PATCH 02/17] chore(attestation): backfill all current attestation templates v2 as draft --- ...backfill_attestation_template_v2_as_draft.rake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/tasks/deployment/20240528155104_backfill_attestation_template_v2_as_draft.rake diff --git a/lib/tasks/deployment/20240528155104_backfill_attestation_template_v2_as_draft.rake b/lib/tasks/deployment/20240528155104_backfill_attestation_template_v2_as_draft.rake new file mode 100644 index 000000000..d56ad5d5f --- /dev/null +++ b/lib/tasks/deployment/20240528155104_backfill_attestation_template_v2_as_draft.rake @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +namespace :after_party do + desc 'Deployment task: attestation_template_v2_as_draft' + task backfill_attestation_template_v2_as_draft: :environment do + puts "Running deploy task 'backfill_attestation_template_v2_as_draft'" + + AttestationTemplate.v2.update_all(state: :draft) + + # 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 c5c24c01d84ed8bf1b175e9404c67663c9d9e19c Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 29 May 2024 11:31:49 +0200 Subject: [PATCH 03/17] chore(attestation): procedure#attestation_template always returns the published attestation --- app/models/attestation_template.rb | 2 +- app/models/procedure.rb | 2 +- spec/models/procedure_spec.rb | 32 ++++++++++++++++++++++-------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index c56c030b0..f415e8e8b 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -2,7 +2,7 @@ class AttestationTemplate < ApplicationRecord include ActionView::Helpers::NumberHelper include TagsSubstitutionConcern - belongs_to :procedure, inverse_of: :attestation_template_v2 + belongs_to :procedure, inverse_of: :attestation_template has_one_attached :logo has_one_attached :signature diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 7995bd49a..818b3c982 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -52,7 +52,7 @@ class Procedure < ApplicationRecord has_one :attestation_template_v1, -> { AttestationTemplate.v1 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure has_one :attestation_template_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure - has_one :attestation_template, -> { order(Arel.sql("CASE WHEN version = '1' THEN 0 ELSE 1 END")) }, dependent: :destroy, inverse_of: :procedure + has_one :attestation_template, -> { published }, dependent: :destroy, inverse_of: :procedure belongs_to :parent_procedure, class_name: 'Procedure', optional: true belongs_to :canonical_procedure, class_name: 'Procedure', optional: true diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 0b8b3f4ed..971683eb6 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -1813,23 +1813,23 @@ describe Procedure do describe "#attestation_template" do let(:procedure) { create(:procedure) } + subject { procedure.reload } - context "when there is a v2 created after v1" do + context "when there is a v2 draft and a v1" do before do create(:attestation_template, procedure: procedure) - create(:attestation_template, :v2, procedure: procedure) + create(:attestation_template, :v2, :draft, procedure: procedure) end - it { expect(procedure.attestation_template.version).to eq(1) } + it { expect(subject.attestation_template.version).to eq(1) } end - context "when there is a v2 created before v1" do + context "when there is only a v1" do before do - create(:attestation_template, :v2, procedure: procedure) - create(:attestation_template, procedure: procedure, activated: true) + create(:attestation_template, procedure: procedure) end - it { expect(procedure.attestation_template.version).to eq(1) } + it { expect(subject.attestation_template.version).to eq(1) } end context "when there is only a v2" do @@ -1837,7 +1837,23 @@ describe Procedure do create(:attestation_template, :v2, procedure: procedure) end - it { expect(procedure.attestation_template.version).to eq(2) } + it { expect(subject.attestation_template.version).to eq(2) } + end + + context "when there is a v2 draft" do + before do + create(:attestation_template, :v2, :draft, procedure: procedure) + end + + it { expect(subject.attestation_template).to be_nil } + + context "and a published" do + before do + create(:attestation_template, :v2, :published, procedure: procedure) + end + + it { expect(subject.attestation_template).to be_published } + end end end From 4af6957237e9070f6818fe369c252985462fe9ca Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 28 May 2024 16:22:47 +0200 Subject: [PATCH 04/17] fix(attestation): dup keep all attributes --- app/models/attestation_template.rb | 2 +- spec/models/attestation_template_spec.rb | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index f415e8e8b..4c542b939 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -96,7 +96,7 @@ class AttestationTemplate < ApplicationRecord end def dup - attestation_template = AttestationTemplate.new(title: title, body: body, footer: footer, activated: activated) + attestation_template = super ClonePiecesJustificativesService.clone_attachments(self, attestation_template) attestation_template end diff --git a/spec/models/attestation_template_spec.rb b/spec/models/attestation_template_spec.rb index 638da8b4f..2807b3f64 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -21,9 +21,11 @@ describe AttestationTemplate, type: :model do context 'with an attestation without images' do let(:attributes) { attributes_for(:attestation_template) } - it { is_expected.to have_attributes(attributes) } - it { is_expected.to have_attributes(id: nil) } - it { expect(subject.logo.attached?).to be_falsey } + it "works" do + is_expected.to have_attributes(attributes) + is_expected.to have_attributes(id: nil) + expect(subject.logo.attached?).to be_falsey + end end context 'with an attestation with images' do From 11d29f55745cc6e5f20c7815d75a239ff323cd41 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 28 May 2024 17:47:05 +0200 Subject: [PATCH 05/17] chore(attestation): publish a v2 attestation --- .../attestation_template_v2s_controller.rb | 38 ++++++++++++++++--- .../administrateurs/procedures_controller.rb | 2 +- app/models/procedure.rb | 2 +- .../_fixed_footer.html.haml | 18 +++++++++ .../attestation_template_v2s/edit.html.haml | 32 +++++----------- .../update.turbo_stream.haml | 5 ++- ...ttestation_template_v2s_controller_spec.rb | 32 +++++++++++++--- .../procedure_attestation_template_spec.rb | 10 +++-- 8 files changed, 98 insertions(+), 41 deletions(-) create mode 100644 app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml diff --git a/app/controllers/administrateurs/attestation_template_v2s_controller.rb b/app/controllers/administrateurs/attestation_template_v2s_controller.rb index f5ace97c7..eb2c45743 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -77,6 +77,13 @@ module Administrateurs def update attestation_params = editor_params + + if @attestation_template.published? + @attestation_template = @attestation_template.dup + @attestation_template.state = :draft + @attestation_template.procedure = @procedure + end + logo_file = attestation_params.delete(:logo) signature_file = attestation_params.delete(:signature) @@ -88,11 +95,29 @@ module Administrateurs attestation_params[:signature] = uninterlace_png(signature_file) 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 + @attestation_template.assign_attributes(attestation_params) - render :update + if @attestation_template.invalid? + flash.alert = "L’attestation contient des erreurs et n'a pas pu être enregistrée. Corriger les erreurs." + else + # - draft just published + if @attestation_template.published? && should_edit_draft? + published = @procedure.attestation_templates.published + + @attestation_template.transaction do + were_published = published.destroy_all + @attestation_template.save! + flash.notice = were_published.any? ? "La nouvelle version de l’attestation a été publiée." : "L’attestation a été publiée." + end + + redirect_to edit_admin_procedure_attestation_template_v2_path(@procedure) + else + # - draft updated + # - or, attestation already published, without need for publication (draft procedure) + @attestation_template.save! + render :update + end + end end def create = update @@ -104,11 +129,12 @@ module Administrateurs end def retrieve_attestation_template - @attestation_template = @procedure.attestation_template_v2 || @procedure.build_attestation_template_v2(json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT) + v2s = @procedure.attestation_templates_v2 + @attestation_template = v2s.find(&:draft?) || v2s.find(&:published?) || @procedure.build_attestation_template(version: 2, json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, state: :draft) end def editor_params - params.required(:attestation_template).permit(:official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated) + params.required(:attestation_template).permit(:official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated, :state) end end end diff --git a/app/controllers/administrateurs/procedures_controller.rb b/app/controllers/administrateurs/procedures_controller.rb index 1c68058ea..874290617 100644 --- a/app/controllers/administrateurs/procedures_controller.rb +++ b/app/controllers/administrateurs/procedures_controller.rb @@ -112,7 +112,7 @@ module Administrateurs revision_types_de_champ: { type_de_champ: { piece_justificative_template_attachment: :blob } } }, attestation_template_v1: [], - attestation_template_v2: [], + attestation_templates_v2: [], initiated_mail: [], received_mail: [], closed_mail: [], diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 818b3c982..db13205c8 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -50,7 +50,7 @@ class Procedure < ApplicationRecord has_one :module_api_carto, dependent: :destroy has_many :attestation_templates, dependent: :destroy has_one :attestation_template_v1, -> { AttestationTemplate.v1 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure - has_one :attestation_template_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure + has_many :attestation_templates_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure has_one :attestation_template, -> { published }, dependent: :destroy, inverse_of: :procedure diff --git a/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml b/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml new file mode 100644 index 000000000..ec8f4d655 --- /dev/null +++ b/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml @@ -0,0 +1,18 @@ +.fr-container + .fr-grid-row.fr-grid-row--middle.fr-pb-3v + .fr-col-12.fr-col-md-4 + = link_to admin_procedure_path(id: procedure), class: 'fr-link' do + %span.fr-icon-arrow-left-line.fr-icon--sm + Revenir à l’écran de gestion + + .fr-col-12.fr-col-md-8.text-right + + - if attestation_template.published? + %p.fr-hint-text Cette attestation est actuellement délivrée aux usagers. + + %span#autosave-notice + + - if procedure.feature_enabled?(:attestation_v2) && attestation_template.draft? + %button.fr-btn.fr-ml-2w{ name: field_name(:attestation_template, :state), value: "published", + data: { 'disable-with': "Publication en cours…", controller: 'autosave-submit' } } + Publier diff --git a/app/views/administrateurs/attestation_template_v2s/edit.html.haml b/app/views/administrateurs/attestation_template_v2s/edit.html.haml index 8ef9c6cfc..8b0f3ecb1 100644 --- a/app/views/administrateurs/attestation_template_v2s/edit.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/edit.html.haml @@ -19,11 +19,12 @@ tout en respectant la charte de l’état. Essayez-la et donnez-nous votre avis en nous envoyant un email à #{mail_to(Current.contact_email, subject: "Feedback attestation v2")}. %br - %strong Les attestations délivrées suivent encore l’ancien format : - l’activation des attestations basées sur ce format sera bientôt disponible. - %br + - if !@procedure.feature_enabled?(:attestation_v2) + %strong Les attestations délivrées suivent encore l’ancien format : + l’activation des attestations basées sur ce format sera bientôt disponible. + %br - = link_to("Suivez ce lien pour revenir aux attestations actuellement délivrées", edit_admin_procedure_attestation_template_path(@procedure)) + = link_to("Suivez ce lien pour revenir aux attestations actuellement délivrées", edit_admin_procedure_attestation_template_path(@procedure)) .fr-grid-row.fr-grid-row--gutters .fr-col-12.fr-col-lg-7 @@ -77,10 +78,10 @@ %button.fr-btn.fr-btn--secondary.fr-btn--sm{ type: 'button', title: label, class: icon == :hidden ? "hidden" : "fr-icon-#{icon}", data: { action: 'click->tiptap#menuButton', tiptap_target: 'button', tiptap_action: action } } = label - #editor.tiptap-editor{ data: { tiptap_target: 'editor' }, aria: { describedby: dom_id(f.object, "json-body-messages")} } + #editor.tiptap-editor{ data: { tiptap_target: 'editor' }, aria: { describedby: "attestation-template-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)) } + .fr-error-text{ id: "attestation-template-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 } @@ -118,20 +119,5 @@ Pour générer un aperçu fidèle avec tous les champs et les dates, créez-vous un dossier et acceptez-le : l’aperçu l’utilisera. .padded-fixed-footer - .fixed-footer - .fr-container - .fr-grid-row - .fr-col-12.fr-col-md-7 - %ul.fr-btns-group.fr-btns-group--inline-md - %li - = link_to admin_procedure_path(id: @procedure), class: 'fr-btn fr-btn--secondary' do - %span.fr-icon-arrow-go-back-line.fr-icon--sm.fr-mr-1v - Revenir à la démarche - - .fr-col-12.fr-col-md-5 - -# .fr-toggle - -# = f.check_box :activated, class: "fr-toggle-input", disabled: true, id: dom_id(@attestation_template, :activated) - -# %label.fr-toggle__label{ for: dom_id(@attestation_template, :activated), data: { fr_checked_label: "Attestation activée", fr_unchecked_label: "Attestation désactivée" } } - .text-right - %span#autosave-notice - %p.fr-hint-text L’activation de cette attestation sera bientôt disponible. + .fixed-footer#fixed_footer + = render partial: "fixed_footer", locals: { procedure: @procedure, attestation_template: @attestation_template } diff --git a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml index 67ef140a1..69447ce28 100644 --- a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml +++ b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml @@ -1,3 +1,6 @@ +- if @attestation_template.previously_new_record? || @attestation_template.state_changed? + = turbo_stream.update "fixed_footer", render(partial: "fixed_footer", locals: { procedure: @procedure, attestation_template: @attestation_template }) + = turbo_stream.show 'autosave-notice' = turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice', locals: { success: !@attestation_template.changed? }) = turbo_stream.hide 'autosave-notice', delay: 15000 @@ -10,7 +13,7 @@ = 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") +- body_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 } diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index d04d7aa89..6541c0ddd 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -140,8 +140,9 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do it "create template" do subject - attestation_template = procedure.reload.attestation_template + attestation_template = procedure.reload.attestation_templates.first + expect(attestation_template).to be_draft expect(attestation_template.official_layout).to eq(true) expect(attestation_template.label_logo).to eq("Ministère des specs") expect(attestation_template.label_direction).to eq("RSPEC") @@ -157,7 +158,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do it "upload files" do subject - attestation_template = procedure.reload.attestation_template + attestation_template = procedure.reload.attestation_templates.first expect(attestation_template.logo.download).to eq(logo.read) expect(attestation_template.signature.download).to eq(signature.read) @@ -174,10 +175,16 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do end context 'when attestation template is valid' do - it "update template" do - subject - attestation_template.reload + it "create a draft template" do + expect { subject }.to change { procedure.attestation_templates.count }.by(1) + # published remains inchanged + expect(attestation_template.reload).to be_published + expect(attestation_template.label_logo).to eq("Ministère des devs") + + attestation_template = procedure.attestation_templates.draft.first + + expect(attestation_template).to be_draft expect(attestation_template.official_layout).to eq(true) expect(attestation_template.label_logo).to eq("Ministère des specs") expect(attestation_template.label_direction).to eq("RSPEC") @@ -186,6 +193,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body]) expect(response.body).to include("Formulaire enregistré") + expect(response.body).to include("Publier") end context "with files" do @@ -193,7 +201,8 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do it "upload files" do subject - attestation_template.reload + + attestation_template = procedure.attestation_templates.draft.first expect(attestation_template.logo.download).to eq(logo.read) expect(attestation_template.signature.download).to eq(signature.read) @@ -211,6 +220,17 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do expect(response.body).to include('Supprimer cette balise') end end + + context "publishing a draft" do + let(:attestation_template) { build(:attestation_template, :draft, :v2) } + let(:update_params) { super().merge(state: :published) } + + it "publish and redirect with notice" do + subject + expect(attestation_template.reload).to be_published + expect(flash.notice).to eq("L’attestation a été publiée.") + end + end end end end diff --git a/spec/system/administrateurs/procedure_attestation_template_spec.rb b/spec/system/administrateurs/procedure_attestation_template_spec.rb index a54d2e4e3..f2be6e451 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -81,7 +81,7 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', find("a").click end - expect(procedure.reload.attestation_template_v2).to be_nil + expect(procedure.reload.attestation_templates.v2).to be_empty expect(page).to have_css("label", text: "Logo additionnel") @@ -90,7 +90,7 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', attestation = nil wait_until { - attestation = procedure.reload.attestation_template_v2 + attestation = procedure.reload.attestation_templates.v2.draft.first attestation.present? } expect(attestation.label_logo).to eq("System Test") @@ -130,6 +130,10 @@ 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\nline3\nline4") + + click_on "Publier" + expect(page).to have_text("L’attestation a été publiée") + expect(attestation.reload).to be_published end context "tag in error" do @@ -137,7 +141,7 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', 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 = procedure.build_attestation_template(version: 2, 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! From 151b3f83ed3925ddc0d7c8c7d02bb6a6f5847a3f Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 29 May 2024 18:37:38 +0200 Subject: [PATCH 06/17] chore(attestation): publish button on sticky header --- app/assets/stylesheets/forms.scss | 30 ------------ app/assets/stylesheets/sticky.scss | 49 +++++++++++++++++++ .../_fixed_footer.html.haml | 5 -- .../_sticky_header.html.haml | 10 ++++ .../attestation_template_v2s/edit.html.haml | 7 ++- .../update.turbo_stream.haml | 1 + app/views/layouts/application.html.haml | 3 ++ 7 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 app/assets/stylesheets/sticky.scss create mode 100644 app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss index ebc1ad87c..fa82639c5 100644 --- a/app/assets/stylesheets/forms.scss +++ b/app/assets/stylesheets/forms.scss @@ -623,40 +623,10 @@ textarea::placeholder { color: $dark-grey; } -@media (max-width: 62em) { - - .padded-fixed-footer { - padding-top: 120px; - } -} - -@media (min-width: 62em) { - - .padded-fixed-footer { - padding-top: 60px; - } -} - -[data-fr-theme="dark"] .fixed-footer { - border-top: 2px solid var(--background-action-low-blue-france-hover); - background-color: var(--background-action-low-blue-france); -} - .mandatory { fill: currentColor; } -.fixed-footer { - border-top: 2px solid $blue-france-500; - position: fixed; - bottom: 0; - left: 0; - right: 0; - padding-top: $default-padding; - background-color: $white; - z-index: 2; -} - .fr-menu__list { padding: $default-spacer; overflow-y: auto; diff --git a/app/assets/stylesheets/sticky.scss b/app/assets/stylesheets/sticky.scss new file mode 100644 index 000000000..c9920a4ff --- /dev/null +++ b/app/assets/stylesheets/sticky.scss @@ -0,0 +1,49 @@ +@import "constants"; + +.fixed-footer { + border-top: 2px solid var(--border-plain-blue-france); + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding-top: $default-padding; + background-color: var(--background-default-grey); + z-index: 2; +} + +@media (max-width: 62em) { + .padded-fixed-footer { + padding-top: 120px; + } +} + +@media (min-width: 62em) { + .padded-fixed-footer { + padding-top: 60px; + } +} + +[data-fr-theme="dark"] .fixed-footer { + background-color: var(--background-action-low-blue-france); +} + +.sticky-header { + padding-top: $default-padding; + padding-bottom: $default-padding; + + &-container { + position: sticky; + top: 0; + left: 0; + right: 0; + z-index: 800; + } + + &-warning { + background-color: var(--background-contrast-warning); + } + + p { + margin: 0; + } +} diff --git a/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml b/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml index ec8f4d655..67d778563 100644 --- a/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml @@ -11,8 +11,3 @@ %p.fr-hint-text Cette attestation est actuellement délivrée aux usagers. %span#autosave-notice - - - if procedure.feature_enabled?(:attestation_v2) && attestation_template.draft? - %button.fr-btn.fr-ml-2w{ name: field_name(:attestation_template, :state), value: "published", - data: { 'disable-with': "Publication en cours…", controller: 'autosave-submit' } } - Publier diff --git a/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml b/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml new file mode 100644 index 000000000..d5f2312d6 --- /dev/null +++ b/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml @@ -0,0 +1,10 @@ +.sticky-header.sticky-header-warning + .fr-container + %p.flex.justify-between.align-center.fr-text-default--warning + %span + = dsfr_icon("fr-icon-warning-fill fr-mr-1v") + Les modifications effectuées ne seront appliquées qu’une fois que vous aurez publié cette version de l’attestation. + %span + %button.fr-btn.fr-ml-2w{ form: "attestation-template", name: field_name(:attestation_template, :state), value: "published", + data: { 'disable-with': "Publication en cours…", controller: 'autosave-submit' } } + Publier l’attestation diff --git a/app/views/administrateurs/attestation_template_v2s/edit.html.haml b/app/views/administrateurs/attestation_template_v2s/edit.html.haml index 8b0f3ecb1..0630f4cd4 100644 --- a/app/views/administrateurs/attestation_template_v2s/edit.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/edit.html.haml @@ -4,7 +4,8 @@ ['Attestation']] } = render NestedForms::FormOwnerComponent.new -= form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure), html: { multipart: true }, += form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure), + html: { multipart: true , id: "attestation-template" }, data: { turbo: 'true', controller: 'autosubmit attestation', autosubmit_debounce_delay_value: 1000, @@ -118,6 +119,10 @@ L’aperçu est mis à jour automatiquement après chaque modification. Pour générer un aperçu fidèle avec tous les champs et les dates, créez-vous un dossier et acceptez-le : l’aperçu l’utilisera. + - if @procedure.feature_enabled?(:attestation_v2) && @attestation_template.draft? + - content_for(:sticky_header) do + = render partial: "sticky_header" + .padded-fixed-footer .fixed-footer#fixed_footer = render partial: "fixed_footer", locals: { procedure: @procedure, attestation_template: @attestation_template } diff --git a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml index 69447ce28..0bf0da34a 100644 --- a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml +++ b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml @@ -1,5 +1,6 @@ - if @attestation_template.previously_new_record? || @attestation_template.state_changed? = turbo_stream.update "fixed_footer", render(partial: "fixed_footer", locals: { procedure: @procedure, attestation_template: @attestation_template }) + = turbo_stream.update "sticky-header", render(partial: "sticky_header") = turbo_stream.show 'autosave-notice' = turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice', locals: { success: !@attestation_template.changed? }) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 36755078f..ceb84ff47 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -45,6 +45,9 @@ #beta Env Test + #sticky-header.sticky-header-container + = content_for(:sticky_header) + = render partial: "layouts/header" %main#contenu{ role: :main } = render partial: "layouts/flash_messages" From ffd8c5617a2bfa320a04eb6475f35dc8d3c78fe8 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 29 May 2024 21:47:09 +0200 Subject: [PATCH 07/17] chore(attestation): card link to v2 if exist --- app/components/procedure/card/attestation_component.rb | 8 ++++++++ .../attestation_component/attestation_component.html.haml | 2 +- .../procedure_attestation_template_spec.rb | 8 +++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/components/procedure/card/attestation_component.rb b/app/components/procedure/card/attestation_component.rb index 386864a6f..5cbac3dff 100644 --- a/app/components/procedure/card/attestation_component.rb +++ b/app/components/procedure/card/attestation_component.rb @@ -5,6 +5,14 @@ class Procedure::Card::AttestationComponent < ApplicationComponent private + def edit_attestation_path + if @procedure.attestation_templates_v2.any? || @procedure.feature_enabled?(:attestation_v2) + helpers.edit_admin_procedure_attestation_template_v2_path(@procedure) + else + helpers.edit_admin_procedure_attestation_template_path(@procedure) + end + end + def error_messages @procedure.errors.messages_for(:attestation_template).to_sentence end diff --git a/app/components/procedure/card/attestation_component/attestation_component.html.haml b/app/components/procedure/card/attestation_component/attestation_component.html.haml index 0b86b5997..40a68535e 100644 --- a/app/components/procedure/card/attestation_component/attestation_component.html.haml +++ b/app/components/procedure/card/attestation_component/attestation_component.html.haml @@ -1,5 +1,5 @@ .fr-col-6.fr-col-md-4.fr-col-lg-3 - = link_to edit_admin_procedure_attestation_template_path(@procedure), class: 'fr-tile fr-enlarge-link' do + = link_to edit_attestation_path, class: 'fr-tile fr-enlarge-link' do .fr-tile__body.flex.column.align-center.justify-between - if @procedure.attestation_template&.activated? %div diff --git a/spec/system/administrateurs/procedure_attestation_template_spec.rb b/spec/system/administrateurs/procedure_attestation_template_spec.rb index f2be6e451..2dde1080d 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -14,8 +14,14 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', before { login_as(administrateur.user, scope: :user) } def find_attestation_card(with_nested_selector: nil) + attestation_path = if procedure.attestation_template&.version == 2 || procedure.feature_enabled?(:attestation_v2) + edit_admin_procedure_attestation_template_v2_path(procedure) + else + edit_admin_procedure_attestation_template_path(procedure) + end + full_selector = [ - "a[href=\"#{edit_admin_procedure_attestation_template_path(procedure)}\"]", + "a[href=\"#{attestation_path}\"]", with_nested_selector ].compact.join(" ") page.find(full_selector) From a540f8dccb8268741723faf9c6a8f4bbc3e17661 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 29 May 2024 23:00:34 +0200 Subject: [PATCH 08/17] feat(attestation): can render & attach attestations v2 --- .../attestation_template_v2s_controller.rb | 22 +--- .../instructeurs/dossiers_controller.rb | 7 +- app/models/attestation_template.rb | 37 +++++- .../concerns/tags_substitution_concern.rb | 2 +- app/models/export_template.rb | 5 +- app/services/tiptap_service.rb | 26 ++-- app/services/weasyprint_service.rb | 23 ++++ spec/models/attestation_template_spec.rb | 113 +++++++++--------- spec/services/tiptap_service_spec.rb | 2 +- spec/services/weasyprint_service_spec.rb | 21 ++++ 10 files changed, 153 insertions(+), 105 deletions(-) create mode 100644 app/services/weasyprint_service.rb create mode 100644 spec/services/weasyprint_service_spec.rb diff --git a/app/controllers/administrateurs/attestation_template_v2s_controller.rb b/app/controllers/administrateurs/attestation_template_v2s_controller.rb index eb2c45743..5ae2e63c1 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -20,27 +20,9 @@ module Administrateurs format.pdf do html = render_to_string('/administrateurs/attestation_template_v2s/show', layout: 'attestation', formats: [:html]) - headers = { - 'Content-Type' => 'application/json', - 'X-Request-Id' => Current.request_id - } + pdf = WeasyprintService.generate_pdf(html, procedure_id: @procedure.id, path: request.path, user_id: current_user.id) - body = { - html: html, - upstream_context: { - procedure_id: @procedure.id, - path: request.path, - user_id: current_user.id - } - }.to_json - - response = Typhoeus.post(WEASYPRINT_URL, headers:, body:) - - if response.success? - send_data(response.body, filename: 'attestation.pdf', type: 'application/pdf', disposition: 'inline') - else - raise StandardError.new("PDF Generation failed: #{response.return_code} #{response.status_message}") - end + send_data(pdf, filename: 'attestation.pdf', type: 'application/pdf', disposition: 'inline') end end end diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 3fa84c917..189477bff 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -30,9 +30,10 @@ module Instructeurs end def apercu_attestation - @attestation = dossier.attestation_template.render_attributes_for(dossier: dossier) - - render 'administrateurs/attestation_templates/show', formats: [:pdf] + send_data dossier.attestation_template.send(:build_pdf, dossier), + filename: 'attestation.pdf', + type: 'application/pdf', + disposition: 'inline' end def bilans_bdf diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index 4c542b939..f5f3c047d 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -72,9 +72,10 @@ class AttestationTemplate < ApplicationRecord }.freeze def attestation_for(dossier) - attestation = Attestation.new(title: replace_tags(title, dossier, escape: false)) + attestation = Attestation.new + attestation.title = replace_tags(title, dossier, escape: false) if version == 1 attestation.pdf.attach( - io: build_pdf(dossier), + io: StringIO.new(build_pdf(dossier)), filename: "attestation-dossier-#{dossier.id}.pdf", content_type: 'application/pdf', # we don't want to run virus scanner on this file @@ -184,7 +185,7 @@ class AttestationTemplate < ApplicationRecord if dossier.present? # 2x faster this way than with `replace_tags` which would reparse text - used_tags = tiptap.used_tags_and_libelle_for(json.deep_symbolize_keys) + used_tags = TiptapService.used_tags_and_libelle_for(json.deep_symbolize_keys) substitutions = tags_substitutions(used_tags, dossier, escape: false) body = tiptap.to_html(json, substitutions) @@ -207,17 +208,41 @@ class AttestationTemplate < ApplicationRecord end def used_tags - used_tags_for(title) + used_tags_for(body) + if version == 2 + json = json_body&.deep_symbolize_keys + TiptapService.used_tags_and_libelle_for(json.deep_symbolize_keys) + else + used_tags_for(title) + used_tags_for(body) + end end def build_pdf(dossier) + if version == 2 + build_v2_pdf(dossier) + else + build_v1_pdf(dossier) + end + end + + def build_v1_pdf(dossier) attestation = render_attributes_for(dossier: dossier) - attestation_view = ApplicationController.render( + ApplicationController.render( template: 'administrateurs/attestation_templates/show', formats: :pdf, assigns: { attestation: attestation } ) + end - StringIO.new(attestation_view) + def build_v2_pdf(dossier) + body = render_attributes_for(dossier:).fetch(:body) + + html = ApplicationController.render( + template: '/administrateurs/attestation_template_v2s/show', + formats: [:html], + layout: 'attestation', + assigns: { attestation_template: self, body: body } + ) + + WeasyprintService.generate_pdf(html, { procedure_id: procedure.id, dossier_id: dossier.id }) end end diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index ae899dc04..6ccef4219 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -257,7 +257,7 @@ module TagsSubstitutionConcern 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) + TiptapService.used_tags_and_libelle_for(text_or_tiptap.deep_symbolize_keys) else used_tags_and_libelle_for(text_or_tiptap.to_s) end diff --git a/app/models/export_template.rb b/app/models/export_template.rb index 550bb57cd..f3ee41235 100644 --- a/app/models/export_template.rb +++ b/app/models/export_template.rb @@ -66,11 +66,10 @@ class ExportTemplate < ApplicationRecord end def render_attributes_for(content_for, dossier, attachment = nil) - tiptap = TiptapService.new - used_tags = tiptap.used_tags_and_libelle_for(content_for.deep_symbolize_keys) + used_tags = TiptapService.used_tags_and_libelle_for(content_for.deep_symbolize_keys) substitutions = tags_substitutions(used_tags, dossier, escape: false, memoize: true) substitutions['original-filename'] = attachment.filename.base if attachment - tiptap.to_path(content_for.deep_symbolize_keys, substitutions) + TiptapService.new.to_path(content_for.deep_symbolize_keys, substitutions) end def specific_tags diff --git a/app/services/tiptap_service.rb b/app/services/tiptap_service.rb index 5d0cb4325..3bbee3494 100644 --- a/app/services/tiptap_service.rb +++ b/app/services/tiptap_service.rb @@ -1,18 +1,6 @@ class TiptapService - def to_html(node, substitutions = {}) - return '' if node.nil? - - children(node[:content], substitutions, 0) - end - - def to_path(node, substitutions = {}) - return '' if node.nil? - - children_path(node[:content], substitutions) - end - # NOTE: node must be deep symbolized keys - def used_tags_and_libelle_for(node, tags = Set.new) + def self.used_tags_and_libelle_for(node, tags = Set.new) case node in type: 'mention', attrs: { id:, label: }, **rest tags << [id, label] @@ -25,6 +13,18 @@ class TiptapService tags end + def to_html(node, substitutions = {}) + return '' if node.nil? + + children(node[:content], substitutions, 0) + end + + def to_path(node, substitutions = {}) + return '' if node.nil? + + children_path(node[:content], substitutions) + end + private def initialize diff --git a/app/services/weasyprint_service.rb b/app/services/weasyprint_service.rb new file mode 100644 index 000000000..d0ab2944b --- /dev/null +++ b/app/services/weasyprint_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class WeasyprintService + def self.generate_pdf(html, options = {}) + headers = { + 'Content-Type' => 'application/json', + 'X-Request-Id' => Current.request_id + } + + body = { + html:, + upstream_context: options + }.to_json + + response = Typhoeus.post(WEASYPRINT_URL, headers:, body:) + + if response.success? + response.body + else + raise StandardError, "PDF Generation failed: #{response.code} #{response.status_message}" + end + end +end diff --git a/spec/models/attestation_template_spec.rb b/spec/models/attestation_template_spec.rb index 2807b3f64..58835bf2e 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -58,81 +58,78 @@ describe AttestationTemplate, type: :model do create(:procedure, types_de_champ_public: types_de_champ, types_de_champ_private: types_de_champ_private, - for_individual: for_individual, attestation_template: attestation_template) end - let(:for_individual) { false } - let(:individual) { nil } let(:etablissement) { create(:etablissement) } let(:types_de_champ) { [] } let(:types_de_champ_private) { [] } - let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) } - let(:template_title) { 'title' } - let(:template_body) { 'body' } - let(:attestation_template) do - build(:attestation_template, - title: template_title, - body: template_body, - logo: @logo, - signature: @signature) + let(:dossier) { create(:dossier, :accepte, procedure:) } + + let(:types_de_champ) do + [ + { libelle: 'libelleA' }, + { libelle: 'libelleB' } + ] end before do - Timecop.freeze(Time.zone.now) - end + dossier.champs_public + .find { |champ| champ.libelle == 'libelleA' } + .update(value: 'libelle1') - after do - Timecop.return - end - - let(:view_args) do - arguments = nil - - allow(ApplicationController).to receive(:render).and_wrap_original do |m, *args| - arguments = args.first[:assigns] - m.call(*args) - end - - attestation_template.attestation_for(dossier) - - arguments + dossier.champs_public + .find { |champ| champ.libelle == 'libelleB' } + .update(value: 'libelle2') end let(:attestation) { attestation_template.attestation_for(dossier) } - context 'when the procedure has a type de champ named libelleA et libelleB' do - let(:types_de_champ) do - [ - { libelle: 'libelleA' }, - { libelle: 'libelleB' } - ] + context 'attestation v1' do + let(:template_title) { 'title --libelleA--' } + let(:template_body) { 'body --libelleB--' } + let(:attestation_template) do + build(:attestation_template, + title: template_title, + body: template_body) end - context 'and the are used in the template title and body' do - let(:template_title) { 'title --libelleA--' } - let(:template_body) { 'body --libelleB--' } + let(:view_args) do + arguments = nil - context 'and their value in the dossier are not nil' do - before do - dossier.champs_public - .find { |champ| champ.libelle == 'libelleA' } - .update(value: 'libelle1') - - dossier.champs_public - .find { |champ| champ.libelle == 'libelleB' } - .update(value: 'libelle2') - end - - it 'passes the correct parameters to the view' do - expect(view_args[:attestation][:title]).to eq('title libelle1') - expect(view_args[:attestation][:body]).to eq('body libelle2') - end - - it 'generates an attestation' do - expect(attestation.title).to eq('title libelle1') - expect(attestation.pdf).to be_attached - end + allow(ApplicationController).to receive(:render).and_wrap_original do |m, *args| + arguments = args.first[:assigns] + m.call(*args) end + + attestation_template.attestation_for(dossier) + + arguments + end + + it 'passes the correct parameters and generates an attestation' do + expect(view_args[:attestation][:title]).to eq('title libelle1') + expect(view_args[:attestation][:body]).to eq('body libelle2') + expect(attestation.title).to eq('title libelle1') + expect(attestation.pdf).to be_attached + end + end + + context 'attestation v2' do + let(:attestation_template) do + build(:attestation_template, :v2, :with_files, label_logo: "Ministère des specs") + end + + before do + stub_request(:post, WEASYPRINT_URL) + .with(body: { + html: /Ministère des specs.+Mon titre pour #{procedure.libelle}.+Dossier: n° #{dossier.id}/m, + upstream_context: { procedure_id: procedure.id, dossier_id: dossier.id } + }) + .to_return(body: 'PDF_DATA') + end + + it 'generates an attestation' do + expect(attestation.pdf).to be_attached end end end diff --git a/spec/services/tiptap_service_spec.rb b/spec/services/tiptap_service_spec.rb index da47220f2..1ec0468b2 100644 --- a/spec/services/tiptap_service_spec.rb +++ b/spec/services/tiptap_service_spec.rb @@ -189,7 +189,7 @@ RSpec.describe TiptapService do describe '#used_tags' do it 'returns used tags' do - expect(described_class.new.used_tags_and_libelle_for(json)).to eq(Set.new([['name', 'Nom']])) + expect(described_class.used_tags_and_libelle_for(json)).to eq(Set.new([['name', 'Nom']])) end end diff --git a/spec/services/weasyprint_service_spec.rb b/spec/services/weasyprint_service_spec.rb new file mode 100644 index 000000000..591f88233 --- /dev/null +++ b/spec/services/weasyprint_service_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +describe WeasyprintService do + let(:html) { 'Hello, World!' } + let(:options) { { procedure_id: 1, dossier_id: 2 } } + + describe '#generate_pdf' do + context 'when the Weasyprint API responds successfully' do + before do + stub_request(:post, WEASYPRINT_URL) + .with(body: { html: html, upstream_context: options }) + .to_return(body: 'PDF_DATA') + end + + it 'returns a StringIO object with the PDF data' do + pdf = described_class.generate_pdf(html, options) + expect(pdf).to eq('PDF_DATA') + end + end + end +end From cd07ee173f1fc2b37244dcf36b69528ad36f6dbf Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Fri, 31 May 2024 10:03:23 +0200 Subject: [PATCH 09/17] feat(attestation): can reset draft attestation --- .../attestation_template_v2s_controller.rb | 7 +++++++ .../_sticky_header.html.haml | 9 ++++++-- config/routes.rb | 4 +++- ...ttestation_template_v2s_controller_spec.rb | 21 +++++++++++++++++++ .../procedure_attestation_template_spec.rb | 1 - 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/controllers/administrateurs/attestation_template_v2s_controller.rb b/app/controllers/administrateurs/attestation_template_v2s_controller.rb index 5ae2e63c1..13905a581 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -104,6 +104,13 @@ module Administrateurs def create = update + def reset + @procedure.attestation_templates_v2.draft&.destroy_all + + flash.notice = "Les modifications ont été réinitialisées." + redirect_to edit_admin_procedure_attestation_template_v2_path(@procedure) + end + private def ensure_feature_active diff --git a/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml b/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml index d5f2312d6..226a35228 100644 --- a/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml @@ -3,8 +3,13 @@ %p.flex.justify-between.align-center.fr-text-default--warning %span = dsfr_icon("fr-icon-warning-fill fr-mr-1v") - Les modifications effectuées ne seront appliquées qu’une fois que vous aurez publié cette version de l’attestation. + Les modifications effectuées ne seront appliquées qu’à la prochaine publication. %span + + + = link_to reset_admin_procedure_attestation_template_v2_path(@procedure), class: "fr-btn fr-btn--secondary fr-ml-2w", method: :post do + Réinitialiser les modifications + %button.fr-btn.fr-ml-2w{ form: "attestation-template", name: field_name(:attestation_template, :state), value: "published", data: { 'disable-with': "Publication en cours…", controller: 'autosave-submit' } } - Publier l’attestation + Publier les modifications diff --git a/config/routes.rb b/config/routes.rb index d16b5f778..75a44fd05 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -684,7 +684,9 @@ Rails.application.routes.draw do get 'add_champ_engagement_juridique' end - resource :attestation_template_v2, only: [:show, :edit, :update, :create] + resource :attestation_template_v2, only: [:show, :edit, :update, :create] do + post :reset + end resource :dossier_submitted_message, only: [:edit, :update, :create] # ADDED TO ACCESS IT FROM THE IFRAME diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index 6541c0ddd..4e69f2a23 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -233,4 +233,25 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do end end end + + describe 'POST reset' do + render_views + + before { + create(:attestation_template, :v2, :draft, procedure:) + } + + subject do + patch :reset, params: { procedure_id: procedure.id } + response.body + end + + it "delete draft, keep published" do + expect(procedure.attestation_templates.count).to eq(2) + expect(subject).to redirect_to(edit_admin_procedure_attestation_template_v2_path(procedure)) + expect(flash.notice).to include("réinitialisées") + expect(procedure.attestation_templates.count).to eq(1) + expect(procedure.attestation_templates.first).to eq(attestation_template) + end + end end diff --git a/spec/system/administrateurs/procedure_attestation_template_spec.rb b/spec/system/administrateurs/procedure_attestation_template_spec.rb index 2dde1080d..bab1d796f 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -138,7 +138,6 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', expect(page).to have_field("Contenu du pied de page", with: "line1\nline2\nline3\nline4") click_on "Publier" - expect(page).to have_text("L’attestation a été publiée") expect(attestation.reload).to be_published end From cf58c48843f7b56098fa90f44cd703a2b5d1ee10 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Sat, 1 Jun 2024 22:39:04 +0200 Subject: [PATCH 10/17] feat(attestation): can toggle activation --- app/components/dsfr/callout_component.rb | 2 ++ .../attestation_template_v2s_controller.rb | 8 ++++- .../_fixed_footer.html.haml | 4 --- .../attestation_template_v2s/edit.html.haml | 14 +++++++-- .../update.turbo_stream.haml | 3 +- ...ttestation_template_v2s_controller_spec.rb | 31 ++++++++++++++++--- 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/components/dsfr/callout_component.rb b/app/components/dsfr/callout_component.rb index 2d394d939..3e343011b 100644 --- a/app/components/dsfr/callout_component.rb +++ b/app/components/dsfr/callout_component.rb @@ -26,6 +26,8 @@ class Dsfr::CalloutComponent < ApplicationComponent "fr-callout--brown-caramel" when :success "fr-callout--green-emeraude" + when :neutral + # default else "fr-background-alt--blue-france" end diff --git a/app/controllers/administrateurs/attestation_template_v2s_controller.rb b/app/controllers/administrateurs/attestation_template_v2s_controller.rb index 13905a581..6e3447f70 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -60,6 +60,12 @@ module Administrateurs def update attestation_params = editor_params + # toggle activation + if @attestation_template.persisted? && @attestation_template.activated? != cast_bool(attestation_params[:activated]) + @procedure.attestation_templates.v2.update_all(activated: attestation_params[:activated]) + render :update && return + end + if @attestation_template.published? @attestation_template = @attestation_template.dup @attestation_template.state = :draft @@ -123,7 +129,7 @@ module Administrateurs end def editor_params - params.required(:attestation_template).permit(:official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated, :state) + params.required(:attestation_template).permit(:activated, :official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated, :state) end end end diff --git a/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml b/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml index 67d778563..e66ef2949 100644 --- a/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/_fixed_footer.html.haml @@ -6,8 +6,4 @@ Revenir à l’écran de gestion .fr-col-12.fr-col-md-8.text-right - - - if attestation_template.published? - %p.fr-hint-text Cette attestation est actuellement délivrée aux usagers. - %span#autosave-notice diff --git a/app/views/administrateurs/attestation_template_v2s/edit.html.haml b/app/views/administrateurs/attestation_template_v2s/edit.html.haml index 0630f4cd4..43eb99c8e 100644 --- a/app/views/administrateurs/attestation_template_v2s/edit.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/edit.html.haml @@ -36,13 +36,23 @@ L’attestation est émise au moment où un dossier est accepté, elle est jointe à l’email d’accusé d’acceptation. Elle est également disponible au téléchargement depuis l’espace personnel de l’usager. + .fr-fieldset__element + = render Dsfr::CalloutComponent.new(title: "Activation de la délivrance de l’attestation", theme: :neutral) do |c| + - c.with_html_body do + .fr-toggle.fr-toggle--label-left + = f.check_box :activated, class: "fr-toggle__input", id: dom_id(@attestation_template, :activated) + %label.fr-toggle__label{ for: dom_id(@attestation_template, :activated), + data: { fr_checked_label: "Activée", fr_unchecked_label: "Désactivée" } } + Activer cette option permet la délivrance automatique de l’attestation dès l’acceptation du dossier. + Désactiver cette option arrête immédiatement l’émission de nouvelles attestations. + .fr-fieldset__element %h2.fr-h4 En-tête .fr-fieldset__element .fr-toggle.fr-toggle--label-left = f.check_box :official_layout, class: "fr-toggle__input", id: dom_id(@attestation_template, :official_layout), data: { "attestation-target": "layoutToggle"} - %label.fr-toggle__label{ for: dom_id(@attestation_template, :official_layout), data: { fr_checked_label: "Activé", fr_unchecked_label: "Désactivé" } } + %label.fr-toggle__label{ for: dom_id(@attestation_template, :official_layout), data: { fr_checked_label: "Oui", fr_unchecked_label: "Non" } } Je souhaite générer une attestation à la charte de l’état (logo avec Marianne) .fr-fieldset__element{ class: class_names("hidden" => !@attestation_template.official_layout?), data: { "attestation-target": 'logoMarianneLabelFieldset'} } @@ -125,4 +135,4 @@ .padded-fixed-footer .fixed-footer#fixed_footer - = render partial: "fixed_footer", locals: { procedure: @procedure, attestation_template: @attestation_template } + = render partial: "fixed_footer", locals: { procedure: @procedure } diff --git a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml index 0bf0da34a..4941218fb 100644 --- a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml +++ b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml @@ -1,5 +1,4 @@ -- if @attestation_template.previously_new_record? || @attestation_template.state_changed? - = turbo_stream.update "fixed_footer", render(partial: "fixed_footer", locals: { procedure: @procedure, attestation_template: @attestation_template }) +- if @attestation_template.draft? = turbo_stream.update "sticky-header", render(partial: "sticky_header") = turbo_stream.show 'autosave-notice' diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index 4e69f2a23..4fbb4ccfd 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -1,7 +1,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do let(:admin) { create(:administrateur) } let(:attestation_template) { build(:attestation_template, :v2) } - let!(:procedure) { create(:procedure, administrateur: admin, attestation_template: attestation_template, libelle: "Ma démarche") } + let(:procedure) { create(:procedure, administrateur: admin, attestation_template:, libelle: "Ma démarche") } let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } let(:signature) { fixture_file_upload('spec/fixtures/files/black.png', 'image/png') } @@ -11,7 +11,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do label_logo: "Ministère des specs", label_direction: "RSPEC", footer: "en bas", - activated: false, + activated: true, tiptap_body: { type: :doc, content: [ @@ -147,7 +147,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do expect(attestation_template.label_logo).to eq("Ministère des specs") expect(attestation_template.label_direction).to eq("RSPEC") expect(attestation_template.footer).to eq("en bas") - expect(attestation_template.activated).to eq(false) + expect(attestation_template.activated).to eq(true) expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body]) expect(response.body).to include("Formulaire enregistré") @@ -189,7 +189,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do expect(attestation_template.label_logo).to eq("Ministère des specs") expect(attestation_template.label_direction).to eq("RSPEC") expect(attestation_template.footer).to eq("en bas") - expect(attestation_template.activated).to eq(false) + expect(attestation_template.activated).to eq(true) expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body]) expect(response.body).to include("Formulaire enregistré") @@ -232,6 +232,29 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do end end end + + context 'toggle activation' do + let(:update_params) { super().merge(activated: false) } + + it 'toggle attribute of current published attestation' do + subject + expect(procedure.attestation_templates.v2.count).to eq(1) + expect(procedure.attestation_templates.v2.first.activated?).to eq(false) + expect(flash.notice).to be_nil + end + + context 'when there is a draft' do + before { + create(:attestation_template, :v2, :draft, procedure:) + } + + it 'toggle attribute of both draft & published v2 attestations' do + subject + expect(procedure.attestation_templates.v2.count).to eq(2) + expect(procedure.attestation_templates.v2.all?(&:activated?)).to eq(false) + end + end + end end describe 'POST reset' do From 5bda9d0c63c59e0ef162763d02356d482fce3d03 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 6 Jun 2024 14:47:36 +0200 Subject: [PATCH 11/17] test(attestation): fix wording after rebase --- .../attestation_template_v2s_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index 4fbb4ccfd..993681fe6 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -214,10 +214,10 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do super().merge(tiptap_body: { type: :doc, content: [{ type: :mention, attrs: { id: "tdc12", label: "oops" } }] }.to_json) end - it "render error" do + it "renders error" do subject expect(response.body).to include("Formulaire en erreur") - expect(response.body).to include('Supprimer cette balise') + expect(response.body).to include('Supprimer la balise') end end From ce998f7864bad2f932baf1f67bdfa6db489bc152 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 6 Jun 2024 14:48:50 +0200 Subject: [PATCH 12/17] feat(attestation): already published without publication when procedure is draft --- .../attestation_template_v2s_controller.rb | 11 +++- .../_sticky_header.html.haml | 18 ++++--- ...ttestation_template_v2s_controller_spec.rb | 52 +++++++++++++++++-- .../procedure_attestation_template_spec.rb | 15 +++++- 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/app/controllers/administrateurs/attestation_template_v2s_controller.rb b/app/controllers/administrateurs/attestation_template_v2s_controller.rb index 6e3447f70..269373773 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -66,7 +66,7 @@ module Administrateurs render :update && return end - if @attestation_template.published? + if @attestation_template.published? && should_edit_draft? @attestation_template = @attestation_template.dup @attestation_template.state = :draft @attestation_template.procedure = @procedure @@ -125,9 +125,16 @@ module Administrateurs def retrieve_attestation_template v2s = @procedure.attestation_templates_v2 - @attestation_template = v2s.find(&:draft?) || v2s.find(&:published?) || @procedure.build_attestation_template(version: 2, json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, state: :draft) + @attestation_template = v2s.find(&:draft?) || v2s.find(&:published?) || build_default_attestation end + def build_default_attestation + state = should_edit_draft? ? :draft : :published + @procedure.build_attestation_template(version: 2, json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, activated: true, state:) + end + + def should_edit_draft? = !@procedure.brouillon? + def editor_params params.required(:attestation_template).permit(:activated, :official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated, :state) end diff --git a/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml b/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml index 226a35228..d209f5d4d 100644 --- a/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/_sticky_header.html.haml @@ -3,13 +3,19 @@ %p.flex.justify-between.align-center.fr-text-default--warning %span = dsfr_icon("fr-icon-warning-fill fr-mr-1v") - Les modifications effectuées ne seront appliquées qu’à la prochaine publication. - %span + - if @procedure.attestation_templates.many? + Les modifications effectuées ne seront appliquées qu’à la prochaine publication. + - else + L’attestation ne sera délivrée qu’après sa publication. - - = link_to reset_admin_procedure_attestation_template_v2_path(@procedure), class: "fr-btn fr-btn--secondary fr-ml-2w", method: :post do - Réinitialiser les modifications + %span.no-wrap + - if @procedure.attestation_templates.many? + = link_to reset_admin_procedure_attestation_template_v2_path(@procedure), class: "fr-btn fr-btn--secondary fr-ml-2w", method: :post do + Réinitialiser les modifications %button.fr-btn.fr-ml-2w{ form: "attestation-template", name: field_name(:attestation_template, :state), value: "published", data: { 'disable-with': "Publication en cours…", controller: 'autosave-submit' } } - Publier les modifications + - if @procedure.attestation_templates.many? + Publier les modifications + - else + Publier diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index 993681fe6..2d081212b 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -1,7 +1,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do let(:admin) { create(:administrateur) } let(:attestation_template) { build(:attestation_template, :v2) } - let(:procedure) { create(:procedure, administrateur: admin, attestation_template:, libelle: "Ma démarche") } + let(:procedure) { create(:procedure, :published, administrateur: admin, attestation_template:, libelle: "Ma démarche") } let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } let(:signature) { fixture_file_upload('spec/fixtures/files/black.png', 'image/png') } @@ -96,17 +96,21 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do end describe 'GET edit' do + render_views + let(:attestation_template) { nil } + subject do get :edit, params: { procedure_id: procedure.id } response.body end context 'if an attestation template does not exists yet on the procedure' do - let(:attestation_template) { nil } - it 'creates new v2 attestation template' do subject expect(assigns(:attestation_template).version).to eq(2) + expect(assigns(:attestation_template)).to be_draft + expect(response.body).to have_button("Publier") + expect(response.body).not_to have_link("Réinitialiser les modifications") end end @@ -116,13 +120,51 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do it 'build new v2 attestation template' do subject expect(assigns(:attestation_template).version).to eq(2) + expect(assigns(:attestation_template)).to be_draft end end - context 'if attestation template already exist on v2' do - it 'assigns v2 attestation template' do + context 'attestation template published exist without draft' do + let(:attestation_template) { build(:attestation_template, :v2, :published) } + + it 'mention publication' do subject expect(assigns(:attestation_template)).to eq(attestation_template) + expect(response.body).not_to have_link("Réinitialiser les modifications") + expect(response.body).not_to have_button("Publier les modifications") + end + end + + context 'attestation template draft already exist on v2' do + let(:attestation_template) { build(:attestation_template, :v2, :draft) } + + it 'assigns this draft' do + subject + expect(assigns(:attestation_template)).to eq(attestation_template) + expect(response.body).not_to have_link("Réinitialiser les modifications") + expect(response.body).to have_button("Publier") + end + + context 'and a published template also exists' do + before { create(:attestation_template, :v2, :published, procedure:) } + + it 'mention publication' do + subject + expect(assigns(:attestation_template)).to eq(attestation_template) + expect(response.body).to have_link("Réinitialiser les modifications") + expect(response.body).to have_button("Publier les modifications") + end + end + end + + context 'when procedure is draft' do + let(:procedure) { create(:procedure, :draft, administrateur: admin, attestation_template:, libelle: "Ma démarche") } + + it 'built template is already live (published)' do + subject + expect(assigns(:attestation_template).version).to eq(2) + expect(assigns(:attestation_template)).to be_published + expect(response.body).not_to have_button(/Publier/) end end end diff --git a/spec/system/administrateurs/procedure_attestation_template_spec.rb b/spec/system/administrateurs/procedure_attestation_template_spec.rb index bab1d796f..2834e9900 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -71,6 +71,13 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', end context 'Update attestation v2' do + let(:procedure) do + create(:procedure, :published, + administrateurs: [administrateur], + libelle: 'libellé de la procédure', + path: 'libelle-de-la-procedure') + end + before do Flipper.enable(:attestation_v2) @@ -100,7 +107,7 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', attestation.present? } expect(attestation.label_logo).to eq("System Test") - expect(attestation.activated?).to be_falsey + expect(attestation.activated?).to be_truthy expect(page).to have_content("Formulaire enregistré") click_on "date de décision" @@ -139,6 +146,12 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', click_on "Publier" expect(attestation.reload).to be_published + expect(page).to have_text("L’attestation a été publiée") + + fill_in "Intitulé de la direction", with: "plop" + click_on "Publier les modifications" + expect(procedure.reload.attestation_template.label_direction).to eq("plop") + expect(page).to have_text(/La nouvelle version de l’attestation/) end context "tag in error" do From 70aee9c9de65e877719b134785cfbcb1a0f6451a Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Fri, 7 Jun 2024 10:22:01 +0200 Subject: [PATCH 13/17] refactor(autosave): extract in component, better wording for attestation --- app/components/autosave_notice_component.rb | 16 ++++++++++++++++ .../autosave_notice_component.en.yml | 8 ++++++++ .../autosave_notice_component.fr.yml | 8 ++++++++ .../autosave_notice_component.html.haml | 2 ++ .../administrateurs/_autosave_notice.html.haml | 2 -- .../update.turbo_stream.haml | 2 +- .../types_de_champ/_insert.turbo_stream.haml | 2 +- .../views/administrateurs/procedures/en.yml | 3 --- .../views/administrateurs/procedures/fr.yml | 3 --- .../attestation_template_v2s_controller_spec.rb | 6 +++--- .../procedure_attestation_template_spec.rb | 8 ++++---- 11 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 app/components/autosave_notice_component.rb create mode 100644 app/components/autosave_notice_component/autosave_notice_component.en.yml create mode 100644 app/components/autosave_notice_component/autosave_notice_component.fr.yml create mode 100644 app/components/autosave_notice_component/autosave_notice_component.html.haml delete mode 100644 app/views/administrateurs/_autosave_notice.html.haml diff --git a/app/components/autosave_notice_component.rb b/app/components/autosave_notice_component.rb new file mode 100644 index 000000000..7d78c119d --- /dev/null +++ b/app/components/autosave_notice_component.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AutosaveNoticeComponent < ApplicationComponent + attr_reader :label_scope + + def initialize(success:, label_scope:) + @success = success + @label_scope = label_scope + end + + def success? = @success + + def label + success? ? t(".#{label_scope}.saved") : t(".#{label_scope}.error") + end +end diff --git a/app/components/autosave_notice_component/autosave_notice_component.en.yml b/app/components/autosave_notice_component/autosave_notice_component.en.yml new file mode 100644 index 000000000..ec721bd1a --- /dev/null +++ b/app/components/autosave_notice_component/autosave_notice_component.en.yml @@ -0,0 +1,8 @@ +--- +en: + form: + saved: 'Form saved' + error: 'Form in error' + attestation: + saved: 'Attestation saved' + error: 'Attestation in error' diff --git a/app/components/autosave_notice_component/autosave_notice_component.fr.yml b/app/components/autosave_notice_component/autosave_notice_component.fr.yml new file mode 100644 index 000000000..9f56b2f82 --- /dev/null +++ b/app/components/autosave_notice_component/autosave_notice_component.fr.yml @@ -0,0 +1,8 @@ +--- +fr: + form: + saved: 'Formulaire enregistré' + error: 'Formulaire en erreur' + attestation: + saved: 'Attestation enregistrée' + error: 'Attestation en erreur' diff --git a/app/components/autosave_notice_component/autosave_notice_component.html.haml b/app/components/autosave_notice_component/autosave_notice_component.html.haml new file mode 100644 index 000000000..2c59662fa --- /dev/null +++ b/app/components/autosave_notice_component/autosave_notice_component.html.haml @@ -0,0 +1,2 @@ +#autosave-notice.fr-badge.fr-badge--sm{ class: class_names("fr-badge--success" => success?, "fr-badge--error" => !success?) } + = label diff --git a/app/views/administrateurs/_autosave_notice.html.haml b/app/views/administrateurs/_autosave_notice.html.haml deleted file mode 100644 index 989e3970d..000000000 --- a/app/views/administrateurs/_autosave_notice.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -- 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") diff --git a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml index 4941218fb..ce033ac46 100644 --- a/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml +++ b/app/views/administrateurs/attestation_template_v2s/update.turbo_stream.haml @@ -2,7 +2,7 @@ = turbo_stream.update "sticky-header", render(partial: "sticky_header") = turbo_stream.show 'autosave-notice' -= turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice', locals: { success: !@attestation_template.changed? }) += turbo_stream.replace('autosave-notice', render(AutosaveNoticeComponent.new(success: !@attestation_template.changed?, label_scope: :attestation))) = turbo_stream.hide 'autosave-notice', delay: 15000 - if @attestation_template.logo_blob&.previously_new_record? diff --git a/app/views/administrateurs/types_de_champ/_insert.turbo_stream.haml b/app/views/administrateurs/types_de_champ/_insert.turbo_stream.haml index ad121dedd..7d66a8851 100644 --- a/app/views/administrateurs/types_de_champ/_insert.turbo_stream.haml +++ b/app/views/administrateurs/types_de_champ/_insert.turbo_stream.haml @@ -18,7 +18,7 @@ - unless flash.alert = turbo_stream.show 'autosave-notice' - = turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice') + = turbo_stream.replace 'autosave-notice', render(AutosaveNoticeComponent.new(success: true, label_scope: :form)) = turbo_stream.hide 'autosave-notice', delay: 30000 - if @destroyed.present? diff --git a/config/locales/views/administrateurs/procedures/en.yml b/config/locales/views/administrateurs/procedures/en.yml index db0bf6b5a..bcbf8daac 100644 --- a/config/locales/views/administrateurs/procedures/en.yml +++ b/config/locales/views/administrateurs/procedures/en.yml @@ -71,6 +71,3 @@ en: path_not_available: owner: This URL is identical to another of your published procedures. If you publish this procedure, the old one will be unpublished and will no longer be accessible to the public. not_owner: This URL is identical to another procedure, you must modify it. - autosave_notice: - form_saved: "Form saved" - form_error: "Form in error" diff --git a/config/locales/views/administrateurs/procedures/fr.yml b/config/locales/views/administrateurs/procedures/fr.yml index 0fcc5ad96..90fb3f4f7 100644 --- a/config/locales/views/administrateurs/procedures/fr.yml +++ b/config/locales/views/administrateurs/procedures/fr.yml @@ -71,6 +71,3 @@ fr: path_not_available: owner: Cette url est identique à celle d’une autre de vos démarches publiées. Si vous publiez cette démarche, l’ancienne sera dépubliée et ne sera plus accessible au public. not_owner: Cette url est identique à celle d’une autre démarche, vous devez la modifier afin de pouvoir publier votre démarche. - autosave_notice: - form_saved: "Formulaire enregistré" - form_error: "Formulaire en erreur" diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index 2d081212b..0b2edcca3 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -192,7 +192,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do expect(attestation_template.activated).to eq(true) expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body]) - expect(response.body).to include("Formulaire enregistré") + expect(response.body).to include("Attestation enregistrée") end context "with files" do @@ -234,7 +234,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do expect(attestation_template.activated).to eq(true) expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body]) - expect(response.body).to include("Formulaire enregistré") + expect(response.body).to include("Attestation enregistrée") expect(response.body).to include("Publier") end @@ -258,7 +258,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do it "renders error" do subject - expect(response.body).to include("Formulaire en erreur") + expect(response.body).to include("Attestation en erreur") expect(response.body).to include('Supprimer la balise') end end diff --git a/spec/system/administrateurs/procedure_attestation_template_spec.rb b/spec/system/administrateurs/procedure_attestation_template_spec.rb index 2834e9900..2eac577b7 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -106,9 +106,9 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', attestation = procedure.reload.attestation_templates.v2.draft.first attestation.present? } + expect(page).to have_content("Attestation enregistrée") expect(attestation.label_logo).to eq("System Test") expect(attestation.activated?).to be_truthy - expect(page).to have_content("Formulaire enregistré") click_on "date de décision" @@ -172,14 +172,14 @@ describe 'As an administrateur, I want to manage the procedure’s attestation', click_on "date de décision" - expect(page).to have_content("Formulaire en erreur") + expect(page).to have_content("Attestation 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).to have_content("Attestation enregistrée") + expect(page).not_to have_content("Attestation en erreur") expect(page).not_to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"") end end From a2c0379127e9ad3d6f50f035373c7996567b89e1 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Fri, 7 Jun 2024 12:06:41 +0200 Subject: [PATCH 14/17] test(attestation): preview pdf --- ...ttestation_template_v2s_controller_spec.rb | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb index 0b2edcca3..d736aa9c5 100644 --- a/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb +++ b/spec/controllers/administrateurs/attestation_template_v2s_controller_spec.rb @@ -31,11 +31,12 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do describe 'GET #show' do subject do - get :show, params: { procedure_id: procedure.id } + get :show, params: { procedure_id: procedure.id, format: } response.body end - context 'if an attestation template exists on the procedure' do + context 'html' do + let(:format) { :html } render_views context 'with preview dossier' do @@ -93,6 +94,24 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do end end end + + context 'pdf' do + render_views + let(:format) { :pdf } + let(:attestation_template) { build(:attestation_template, :v2, signature:) } + let(:dossier) { create(:dossier, :en_construction, procedure:, for_procedure_preview: true) } + + before do + html_content = /Ministère des devs.+Mon titre pour Ma démarche.+n° #{dossier.id}/m + context = { procedure_id: procedure.id } + + allow(WeasyprintService).to receive(:generate_pdf).with(a_string_matching(html_content), hash_including(context)).and_return('PDF_DATA') + end + + it do + is_expected.to eq('PDF_DATA') + end + end end describe 'GET edit' do From f2669fbca8e4eaf2ce24d4a6c83a14e31cfd8c96 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Fri, 7 Jun 2024 13:19:04 +0200 Subject: [PATCH 15/17] fix(attestation): fix unspecified_attestation_champs for v2 --- app/models/attestation_template.rb | 2 +- spec/factories/attestation_template.rb | 8 +-- spec/models/dossier_spec.rb | 78 ++++++++++++++++++-------- 3 files changed, 60 insertions(+), 28 deletions(-) diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index f5f3c047d..98a5dbe00 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -210,7 +210,7 @@ class AttestationTemplate < ApplicationRecord def used_tags if version == 2 json = json_body&.deep_symbolize_keys - TiptapService.used_tags_and_libelle_for(json.deep_symbolize_keys) + TiptapService.used_tags_and_libelle_for(json.deep_symbolize_keys).map(&:first) else used_tags_for(title) + used_tags_for(body) end diff --git a/spec/factories/attestation_template.rb b/spec/factories/attestation_template.rb index be4c39626..d18770402 100644 --- a/spec/factories/attestation_template.rb +++ b/spec/factories/attestation_template.rb @@ -32,10 +32,10 @@ FactoryBot.define do { "type" => "paragraph", "attrs" => { "textAlign" => "left" }, "content" => [{ "text" => "Dossier: n° ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }, { "type" => "paragraph", - "content" => [ - { "text" => "Nom: ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "individual_last_name", "label" => "prénom" } }, { "text" => " ", "type" => "text" }, - { "type" => "mention", "attrs" => { "id" => "individual_first_name", "label" => "nom" } }, { "text" => " ", "type" => "text" } - ] + "content" => [ + { "text" => "Nom: ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "individual_last_name", "label" => "prénom" } }, { "text" => " ", "type" => "text" }, + { "type" => "mention", "attrs" => { "id" => "individual_first_name", "label" => "nom" } }, { "text" => " ", "type" => "text" } + ] } ] } diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 92f0b0944..76fb5414a 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -685,8 +685,24 @@ describe Dossier, type: :model do describe "#unspecified_attestation_champs" do let(:procedure) { create(:procedure, attestation_template: attestation_template, types_de_champ_public: types_de_champ, types_de_champ_private: types_de_champ_private) } let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } - let(:types_de_champ) { [] } - let(:types_de_champ_private) { [] } + + let(:types_de_champ) { [tdc_1, tdc_2, tdc_3, tdc_4] } + let(:types_de_champ_private) { [tdc_5, tdc_6, tdc_7, tdc_8] } + + let(:tdc_1) { { libelle: "specified champ-in-title" } } + let(:tdc_2) { { libelle: "unspecified champ-in-title" } } + let(:tdc_3) { { libelle: "specified champ-in-body" } } + let(:tdc_4) { { libelle: "unspecified champ-in-body" } } + let(:tdc_5) { { libelle: "specified annotation privée-in-title" } } + let(:tdc_6) { { libelle: "unspecified annotation privée-in-title" } } + let(:tdc_7) { { libelle: "specified annotation privée-in-body" } } + let(:tdc_8) { { libelle: "unspecified annotation privée-in-body" } } + + before do + (dossier.champs_public + dossier.champs_private) + .filter { |c| c.libelle.match?(/^specified/) } + .each { |c| c.update_attribute(:value, "specified") } + end subject { dossier.unspecified_attestation_champs.map(&:libelle) } @@ -696,11 +712,11 @@ describe Dossier, type: :model do it { is_expected.to eq([]) } end - context "with attestation template" do + context "with attestation template v1" do # Test all combinations: # - with tag specified and unspecified # - with tag in body and tag in title - # - with tag correponsing to a champ and an annotation privée + # - with tag correponding to a champ and an annotation privée # - with a dash in the champ libelle / tag let(:title) { "voici --specified champ-in-title-- un --unspecified champ-in-title-- beau --specified annotation privée-in-title-- titre --unspecified annotation privée-in-title-- non --numéro du dossier--" } let(:body) { "voici --specified champ-in-body-- un --unspecified champ-in-body-- beau --specified annotation privée-in-body-- body --unspecified annotation privée-in-body-- non ?" } @@ -712,27 +728,9 @@ describe Dossier, type: :model do it { is_expected.to eq([]) } end - context "wich is enabled" do + context "which is enabled" do let(:activated) { true } - let(:types_de_champ) { [tdc_1, tdc_2, tdc_3, tdc_4] } - let(:types_de_champ_private) { [tdc_5, tdc_6, tdc_7, tdc_8] } - - let(:tdc_1) { { libelle: "specified champ-in-title" } } - let(:tdc_2) { { libelle: "unspecified champ-in-title" } } - let(:tdc_3) { { libelle: "specified champ-in-body" } } - let(:tdc_4) { { libelle: "unspecified champ-in-body" } } - let(:tdc_5) { { libelle: "specified annotation privée-in-title" } } - let(:tdc_6) { { libelle: "unspecified annotation privée-in-title" } } - let(:tdc_7) { { libelle: "specified annotation privée-in-body" } } - let(:tdc_8) { { libelle: "unspecified annotation privée-in-body" } } - - before do - (dossier.champs_public + dossier.champs_private) - .filter { |c| c.libelle.match?(/^specified/) } - .each { |c| c.update_attribute(:value, "specified") } - end - it do is_expected.to eq([ "unspecified champ-in-title", @@ -743,6 +741,40 @@ describe Dossier, type: :model do end end end + + context "with attestation template v2" do + # Test all combinations: + # - with tag specified and unspecified + # - with tag correponding to a champ and an annotation privée + let(:body) { + [ + { "type" => "mention", "attrs" => { "id" => "tdc#{procedure.types_de_champ_for_tags.find {  _1.libelle == "unspecified champ-in-body" }.stable_id}", "label" => "unspecified champ-in-body" } } + ] + } + let(:attestation_template) { build(:attestation_template, :v2) } + + before do + tdc_content = (types_de_champ + types_de_champ_private).filter_map do |tdc_config| + next if tdc_config[:libelle].include?("in-title") + + { + "type" => "mention", + "attrs" => { "id" => "tdc#{procedure.types_de_champ_for_tags.find { _1.libelle == tdc_config[:libelle] }.stable_id}", "label" => tdc_config[:libelle] } + } + end + + json_body = attestation_template.json_body["content"] + attestation_template.json_body["content"][-1]["content"].concat(tdc_content) + attestation_template.save! + end + + it do + is_expected.to eq([ + "unspecified champ-in-body", + "unspecified annotation privée-in-body" + ]) + end + end end describe '#build_attestation' do From 666b51fa9c563d7d2726a9cc48bd30a9cc7b0072 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 19 Jun 2024 21:50:38 +0200 Subject: [PATCH 16/17] fix(textarea): merge data controllers from opts & autoresize --- app/components/dsfr/input_errorable.rb | 4 +--- .../administrateurs/procedure_attestation_template_spec.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/components/dsfr/input_errorable.rb b/app/components/dsfr/input_errorable.rb index 836ab9c1b..efe7de775 100644 --- a/app/components/dsfr/input_errorable.rb +++ b/app/components/dsfr/input_errorable.rb @@ -98,9 +98,7 @@ module Dsfr }) end - if autoresize? - @opts.deep_merge!(data: { controller: 'autoresize' }) - end + @opts.deep_merge!(data: { controller: token_list(@opts.dig(:data, :controller), 'autoresize' => autoresize?) }) @opts end diff --git a/spec/system/administrateurs/procedure_attestation_template_spec.rb b/spec/system/administrateurs/procedure_attestation_template_spec.rb index 2eac577b7..ce8ae5998 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -142,7 +142,7 @@ 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\nline3\nline4") + expect(page).to have_field("Contenu du pied de page", with: "line1\nline2\nline3line4") click_on "Publier" expect(attestation.reload).to be_published From 617c0e4c8f876cc05c167d766f64d61cadeb03e5 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 20 Jun 2024 11:08:05 +0200 Subject: [PATCH 17/17] fix(attestation): sticky header don't overlap above preview --- .../controllers/sticky_top_controller.ts | 43 +++++++++++++++++++ .../attestation_template_v2s/edit.html.haml | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 app/javascript/controllers/sticky_top_controller.ts diff --git a/app/javascript/controllers/sticky_top_controller.ts b/app/javascript/controllers/sticky_top_controller.ts new file mode 100644 index 000000000..c8490cea0 --- /dev/null +++ b/app/javascript/controllers/sticky_top_controller.ts @@ -0,0 +1,43 @@ +import { ApplicationController } from './application_controller'; + +export class StickyTopController extends ApplicationController { + // Ajusts top of sticky top components when there is a sticky header. + + connect(): void { + const header = document.getElementById('sticky-header'); + + if (!header) { + return; + } + + this.adjustTop(header); + + window.addEventListener('resize', () => this.adjustTop(header)); + + this.listenHeaderMutations(header); + } + + private listenHeaderMutations(header: HTMLElement) { + const config = { childList: true, subtree: true }; + + const callback: MutationCallback = (mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === 'childList') { + this.adjustTop(header); + break; + } + } + }; + + const observer = new MutationObserver(callback); + observer.observe(header, config); + } + + private adjustTop(header: HTMLElement) { + const headerHeight = header.clientHeight; + + if (headerHeight > 0) { + (this.element as HTMLElement).style.top = `${headerHeight + 8}px`; + } + } +} diff --git a/app/views/administrateurs/attestation_template_v2s/edit.html.haml b/app/views/administrateurs/attestation_template_v2s/edit.html.haml index 43eb99c8e..7c995e6b2 100644 --- a/app/views/administrateurs/attestation_template_v2s/edit.html.haml +++ b/app/views/administrateurs/attestation_template_v2s/edit.html.haml @@ -120,7 +120,7 @@ - c.with_hint { "Exemple: 20 avenue de Ségur, 75007 Paris" } #preview-column.fr-col-12.fr-col-lg-5.fr-background-alt--blue-france - .sticky--top.fr-px-1w + .sticky--top.fr-px-1w{ data: { controller: "sticky-top" } } .flex.justify-between.align-center %h2.fr-h4 Aperçu %p= link_to 'Prévisualiser en taille réelle', admin_procedure_attestation_template_v2_path(@procedure, format: :pdf), class: 'fr-link', target: '_blank', rel: 'noopener'