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 eea88355cde..c56c030b072 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 00000000000..8a78425025c --- /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 00000000000..5dd74d8316b --- /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 b33a105829e..28c317cacc6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -175,10 +175,11 @@ 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 00000000000..d56ad5d5fed --- /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 c56c030b072..f415e8e8b40 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 7995bd49abb..818b3c982e7 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 0b8b3f4ed10..971683eb6c1 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -1813,23 +1813,23 @@ 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 @@ 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 f415e8e8b40..4c542b939a7 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -96,7 +96,7 @@ def unspecified_champs_for_dossier(dossier) 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 638da8b4f9d..2807b3f64eb 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -21,9 +21,11 @@ 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 f5ace97c7b4..eb2c4574398 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -77,6 +77,13 @@ def edit 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 @@ def update 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) + + 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 - render :update + @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 @@ def ensure_feature_active 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 1c68058eaef..8742906178d 100644 --- a/app/controllers/administrateurs/procedures_controller.rb +++ b/app/controllers/administrateurs/procedures_controller.rb @@ -112,7 +112,7 @@ def show 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 818b3c982e7..db13205c8bc 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 00000000000..ec8f4d655da --- /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 8ef9c6cfc69..8b0f3ecb1a8 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 67ef140a185..69447ce28e2 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 d04d7aa893c..6541c0dddde 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 a54d2e4e397..f2be6e4515c 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -81,7 +81,7 @@ def find_attestation_card(with_nested_selector: nil) 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 @@ def find_attestation_card(with_nested_selector: nil) 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 @@ def find_attestation_card(with_nested_selector: nil) 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 @@ def find_attestation_card(with_nested_selector: nil) 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 ebc1ad87c24..fa82639c580 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 00000000000..c9920a4ffbd --- /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 ec8f4d655da..67d77856319 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 00000000000..d5f2312d62c --- /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 8b0f3ecb1a8..0630f4cd4a4 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 69447ce28e2..0bf0da34a59 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 36755078f8c..ceb84ff470c 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 386864a6fc7..5cbac3dff6d 100644 --- a/app/components/procedure/card/attestation_component.rb +++ b/app/components/procedure/card/attestation_component.rb @@ -5,6 +5,14 @@ def initialize(procedure:) 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 0b86b59977c..40a68535ef9 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 f2be6e4515c..2dde1080df3 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -14,8 +14,14 @@ 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 | 24 +--- .../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 | 107 +++++++++--------- spec/services/tiptap_service_spec.rb | 2 +- spec/services/weasyprint_service_spec.rb | 21 ++++ 10 files changed, 151 insertions(+), 103 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 eb2c4574398..5ae2e63c127 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -20,27 +20,9 @@ def show 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 - } - - 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 + pdf = WeasyprintService.generate_pdf(html, procedure_id: @procedure.id, path: request.path, user_id: current_user.id) + + 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 3fa84c917f0..189477bff91 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -30,9 +30,10 @@ def geo_data 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 4c542b939a7..f5f3c047d23 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 @@ def render_attributes_for_v2(params, base_attributes) 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 @@ def signature_to_render(groupe_instructeur) 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 + + 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 } + ) - StringIO.new(attestation_view) + 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 ae899dc0456..6ccef4219eb 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -257,7 +257,7 @@ def tags_categorized 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 550bb57cd4a..f3ee4123580 100644 --- a/app/models/export_template.rb +++ b/app/models/export_template.rb @@ -66,11 +66,10 @@ def tiptap_convert_pj(dossier, pj_stable_id, attachment = nil) 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 5d0cb43259b..3bbee349441 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 @@ def used_tags_and_libelle_for(node, tags = Set.new) 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 00000000000..d0ab2944b53 --- /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 2807b3f64eb..58835bf2e5c 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -58,81 +58,78 @@ 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 + dossier.champs_public + .find { |champ| champ.libelle == 'libelleB' } + .update(value: 'libelle2') end - let(:view_args) do - arguments = nil + let(:attestation) { attestation_template.attestation_for(dossier) } - allow(ApplicationController).to receive(:render).and_wrap_original do |m, *args| - arguments = args.first[:assigns] - m.call(*args) + 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 - attestation_template.attestation_for(dossier) + 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 + 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 - let(:attestation) { attestation_template.attestation_for(dossier) } + context 'attestation v2' do + let(:attestation_template) do + build(:attestation_template, :v2, :with_files, label_logo: "Ministère des specs") + end - context 'when the procedure has a type de champ named libelleA et libelleB' do - let(:types_de_champ) do - [ - { libelle: 'libelleA' }, - { libelle: 'libelleB' } - ] + 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 - context 'and the are used in the template title and body' do - let(:template_title) { 'title --libelleA--' } - let(:template_body) { 'body --libelleB--' } - - 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 - 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 da47220f223..1ec0468b270 100644 --- a/spec/services/tiptap_service_spec.rb +++ b/spec/services/tiptap_service_spec.rb @@ -189,7 +189,7 @@ 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 00000000000..591f882337a --- /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 5ae2e63c127..13905a581ba 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -104,6 +104,13 @@ def update 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 d5f2312d62c..226a352286c 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 d16b5f778c6..75a44fd0584 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -684,7 +684,9 @@ 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 6541c0dddde..4e69f2a2343 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 @@ 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 2dde1080df3..bab1d796fb3 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -138,7 +138,6 @@ def find_attestation_card(with_nested_selector: nil) 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 2d394d93939..3e343011b37 100644 --- a/app/components/dsfr/callout_component.rb +++ b/app/components/dsfr/callout_component.rb @@ -26,6 +26,8 @@ def theme_class "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 13905a581ba..6e3447f70cf 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -60,6 +60,12 @@ def edit 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 @@ def retrieve_attestation_template 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 67d77856319..e66ef294988 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 0630f4cd4a4..43eb99c8e5c 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 0bf0da34a59..4941218fba4 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 4e69f2a2343..4fbb4ccfd02 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 @@ 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 @@ 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 @@ 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 @@ 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 4fbb4ccfd02..993681fe6ef 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 @@ 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 6e3447f70cf..269373773ae 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -66,7 +66,7 @@ def update 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 @@ def ensure_feature_active 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 226a352286c..d209f5d4d77 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 993681fe6ef..2d081212b1d 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 @@ 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 @@ 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 bab1d796fb3..2834e99003e 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -71,6 +71,13 @@ def find_attestation_card(with_nested_selector: nil) 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 @@ def find_attestation_card(with_nested_selector: nil) 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 @@ def find_attestation_card(with_nested_selector: nil) 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 00000000000..7d78c119dea --- /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 00000000000..ec721bd1a11 --- /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 00000000000..9f56b2f8292 --- /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 00000000000..2c59662fa8c --- /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 989e3970de2..00000000000 --- 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 4941218fba4..ce033ac46b6 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 ad121dedd34..7d66a88513d 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 db0bf6b5aed..bcbf8daac4c 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 0fcc5ad9669..90fb3f4f777 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 2d081212b1d..0b2edcca377 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 @@ 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 @@ 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 @@ 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 2834e99003e..2eac577b74d 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -106,9 +106,9 @@ def find_attestation_card(with_nested_selector: nil) 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 @@ def find_attestation_card(with_nested_selector: nil) 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 0b2edcca377..d736aa9c50a 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 '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 @@ 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 f5f3c047d23..98a5dbe0013 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -210,7 +210,7 @@ def signature_to_render(groupe_instructeur) 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 be4c39626e5..d18770402d4 100644 --- a/spec/factories/attestation_template.rb +++ b/spec/factories/attestation_template.rb @@ -32,10 +32,10 @@ { "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 92f0b0944f2..76fb5414aed 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -685,8 +685,24 @@ 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 @@ 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 @@ 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 @@ 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 836ab9c1b89..efe7de775fc 100644 --- a/app/components/dsfr/input_errorable.rb +++ b/app/components/dsfr/input_errorable.rb @@ -98,9 +98,7 @@ def input_opts(other_opts = {}) }) 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 2eac577b74d..ce8ae599842 100644 --- a/spec/system/administrateurs/procedure_attestation_template_spec.rb +++ b/spec/system/administrateurs/procedure_attestation_template_spec.rb @@ -142,7 +142,7 @@ def find_attestation_card(with_nested_selector: nil) } 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 00000000000..c8490cea008 --- /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 43eb99c8e5c..7c995e6b27c 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'