From 5d2076c1c3b84795dae409112c07c45cb933d7cb Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Wed, 30 Oct 2024 10:48:03 +0100 Subject: [PATCH 01/12] add notification with eventmanager --- .../proposals_controller_override.rb | 27 ++++++++++++++++ .../proposals/proposal_published_event.rb | 31 +++++++++++++++++++ config/locales/en.yml | 5 +++ 3 files changed, 63 insertions(+) create mode 100644 app/events/decidim/proposals/proposal_published_event.rb diff --git a/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb b/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb index b627ce8e14..5181a1f412 100644 --- a/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb +++ b/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb @@ -103,6 +103,23 @@ def create end end end + def publish + enforce_permission_to :edit, :proposal, proposal: @proposal + + # Publier la proposition et envoyer la notification si succès + Decidim::Proposals::PublishProposal.call(@proposal, current_user) do + on(:ok) do + flash[:notice] = I18n.t("proposals.publish.success", scope: "decidim") + send_publication_notification # Appel de la méthode de notification + redirect_to proposal_path(@proposal) + end + + on(:invalid) do + flash.now[:alert] = I18n.t("proposals.publish.error", scope: "decidim") + render :edit_draft + end + end + end # Overridden because of a core bug when the command posts the "invalid" # signal and when rendering the form. @@ -203,6 +220,16 @@ def proposal_limit_reached?(form = form_proposal_params) def current_user_proposals(form) Decidim::Proposals::Proposal.from_author(current_user).where(component: form.current_component).except_withdrawn end + + def send_publication_notification + Decidim::EventsManager.publish( + event: "decidim.proposals.proposal_published", + event_class: Decidim::Proposals::ProposalPublishedEvent, + resource: @proposal, + affected_users: [@proposal.creator_identity], + extra: { participatory_space: true } + ) + end end end end diff --git a/app/events/decidim/proposals/proposal_published_event.rb b/app/events/decidim/proposals/proposal_published_event.rb new file mode 100644 index 0000000000..5eaf0fa74e --- /dev/null +++ b/app/events/decidim/proposals/proposal_published_event.rb @@ -0,0 +1,31 @@ +# app/events/decidim/proposals/proposal_published_event.rb +module Decidim + module Proposals + class ProposalPublishedEvent < Decidim::Events::BaseEvent + include Decidim::Events::NotificationEvent + + def initialize(resource:, **options) + @resource = resource + super(**options) + end + + def notification_title + I18n.t("decidim.proposals.notifications.proposal_published.title", proposal_title: resource_title) + end + + def notification_body + I18n.t("decidim.proposals.notifications.proposal_published.body", proposal_link: resource_path) + end + + private + + def resource_title + translated_attribute(@resource.title) + end + + def resource_path + Decidim::Engine.routes.url_helpers.proposal_path(@resource) + end + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 477839a9f7..f64f878fc5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -147,6 +147,11 @@ en: exports: awesome_private_proposals: Proposals with private fields proposal_comments: Comments + notifications: + proposal_published: + title: "Your proposal %{proposal_title} has been published!" + body: "Your proposal is now live. View it here: %{proposal_link}" + subject: "Your proposal has been published!" collaborative_drafts: new: add_file: Add file From b4ef5667df7d8a0b18ce3c0efca9a8387d65e57f Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Mon, 4 Nov 2024 16:19:01 +0100 Subject: [PATCH 02/12] base to watch the CI and see files on github --- .../proposals_controller_override.rb | 27 ------------------- .../proposals/proposal_published_event.rb | 9 ++----- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb b/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb index 5181a1f412..51fcfe5cb3 100644 --- a/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb +++ b/app/controllers/concerns/decidim/simple_proposal/proposals_controller_override.rb @@ -103,24 +103,6 @@ def create end end end - def publish - enforce_permission_to :edit, :proposal, proposal: @proposal - - # Publier la proposition et envoyer la notification si succès - Decidim::Proposals::PublishProposal.call(@proposal, current_user) do - on(:ok) do - flash[:notice] = I18n.t("proposals.publish.success", scope: "decidim") - send_publication_notification # Appel de la méthode de notification - redirect_to proposal_path(@proposal) - end - - on(:invalid) do - flash.now[:alert] = I18n.t("proposals.publish.error", scope: "decidim") - render :edit_draft - end - end - end - # Overridden because of a core bug when the command posts the "invalid" # signal and when rendering the form. def update_draft @@ -221,15 +203,6 @@ def current_user_proposals(form) Decidim::Proposals::Proposal.from_author(current_user).where(component: form.current_component).except_withdrawn end - def send_publication_notification - Decidim::EventsManager.publish( - event: "decidim.proposals.proposal_published", - event_class: Decidim::Proposals::ProposalPublishedEvent, - resource: @proposal, - affected_users: [@proposal.creator_identity], - extra: { participatory_space: true } - ) - end end end end diff --git a/app/events/decidim/proposals/proposal_published_event.rb b/app/events/decidim/proposals/proposal_published_event.rb index 5eaf0fa74e..4f68c2ca6e 100644 --- a/app/events/decidim/proposals/proposal_published_event.rb +++ b/app/events/decidim/proposals/proposal_published_event.rb @@ -4,11 +4,6 @@ module Proposals class ProposalPublishedEvent < Decidim::Events::BaseEvent include Decidim::Events::NotificationEvent - def initialize(resource:, **options) - @resource = resource - super(**options) - end - def notification_title I18n.t("decidim.proposals.notifications.proposal_published.title", proposal_title: resource_title) end @@ -20,11 +15,11 @@ def notification_body private def resource_title - translated_attribute(@resource.title) + translated_attribute(resource.title) end def resource_path - Decidim::Engine.routes.url_helpers.proposal_path(@resource) + Decidim::Engine.routes.url_helpers.proposal_path(resource) end end end From 490cc23eb9ea1c0465ec6ba17006973ec609b1c8 Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Wed, 6 Nov 2024 13:29:11 +0100 Subject: [PATCH 03/12] fix translation key & notififaction displaying --- .../decidim/proposals/proposal_published_event.rb | 10 +++++++++- config/locales/fr.yml | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/events/decidim/proposals/proposal_published_event.rb b/app/events/decidim/proposals/proposal_published_event.rb index 4f68c2ca6e..2fe8cf8bb7 100644 --- a/app/events/decidim/proposals/proposal_published_event.rb +++ b/app/events/decidim/proposals/proposal_published_event.rb @@ -4,6 +4,10 @@ module Proposals class ProposalPublishedEvent < Decidim::Events::BaseEvent include Decidim::Events::NotificationEvent + def self.model_name + ActiveModel::Name.new(self, nil, I18n.t('decidim.proposals.notifications.proposal_published.subject')) + end + def notification_title I18n.t("decidim.proposals.notifications.proposal_published.title", proposal_title: resource_title) end @@ -19,7 +23,11 @@ def resource_title end def resource_path - Decidim::Engine.routes.url_helpers.proposal_path(resource) + Decidim::Proposals::Engine.routes.url_helpers.proposal_path( + resource, + component_id: resource.component.id, + initiative_slug: resource.component.participatory_space.slug + ) end end end diff --git a/config/locales/fr.yml b/config/locales/fr.yml index a82163e17b..58f4874a10 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -158,6 +158,11 @@ fr: collaborative_drafts_list: Accéder aux brouillons collaboratifs new_proposal: Nouvelle proposition view_proposal: Voir la proposition + notifications: + proposal_published: + title: "Votre proposition %{proposal_title} a bien été publiée!" + body: "Votre proposition est en ligne. Lien ici: %{proposal_link}" + subject: "Votre proposition est publiée!" update: error: Il y a eu une erreur lors de la mise à jour de la proposition. success: Proposition mise à jour avec succès. From f8b938bf2f04ed1fb1ddea93a7edf3c011e3effd Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Wed, 6 Nov 2024 13:45:02 +0100 Subject: [PATCH 04/12] add send_pubication_notification to right file --- .../decidim/proposals/publish_proposal.rb | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 app/commands/decidim/proposals/publish_proposal.rb diff --git a/app/commands/decidim/proposals/publish_proposal.rb b/app/commands/decidim/proposals/publish_proposal.rb new file mode 100644 index 0000000000..4e6f2860f1 --- /dev/null +++ b/app/commands/decidim/proposals/publish_proposal.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Decidim + module Proposals + # A command with all the business logic when a user publishes a draft proposal. + class PublishProposal < Decidim::Command + # Public: Initializes the command. + # + # proposal - The proposal to publish. + # current_user - The current user. + def initialize(proposal, current_user) + @proposal = proposal + @current_user = current_user + end + + # Executes the command. Broadcasts these events: + # + # - :ok when everything is valid and the proposal is published. + # - :invalid if the proposal's author is not the current user. + # + # Returns nothing. + def call + return broadcast(:invalid) unless @proposal.authored_by?(@current_user) + + transaction do + publish_proposal + increment_scores + send_notification + send_notification_to_participatory_space + send_publication_notification + end + + broadcast(:ok, @proposal) + end + + private + + # This will be the PaperTrail version that is + # shown in the version control feature (1 of 1) + # + # For an attribute to appear in the new version it has to be reset + # and reassigned, as PaperTrail only keeps track of object CHANGES. + def publish_proposal + title = reset(:title) + body = reset(:body) + + Decidim.traceability.perform_action!( + "publish", + @proposal, + @current_user, + visibility: "public-only" + ) do + @proposal.update title: title, body: body, published_at: Time.current + end + end + + # Reset the attribute to an empty string and return the old value + def reset(attribute) + attribute_value = @proposal[attribute] + PaperTrail.request(enabled: false) do + # rubocop:disable Rails/SkipsModelValidations + @proposal.update_attribute attribute, "" + # rubocop:enable Rails/SkipsModelValidations + end + attribute_value + end + + def send_notification + return if @proposal.coauthorships.empty? + + Decidim::EventsManager.publish( + event: "decidim.events.proposals.proposal_published", + event_class: Decidim::Proposals::PublishProposalEvent, + resource: @proposal, + followers: coauthors_followers + ) + end + + def send_publication_notification + puts "send notification from proposal #{@proposal.id}" + # debug message + Decidim::EventsManager.publish( + event: "decidim.proposals.proposal_published", + event_class: Decidim::Proposals::ProposalPublishedEvent, + resource: @proposal, + affected_users: [@proposal.creator_identity], + extra: { participatory_space: true } + ) + end + + def send_notification_to_participatory_space + Decidim::EventsManager.publish( + event: "decidim.events.proposals.proposal_published", + event_class: Decidim::Proposals::PublishProposalEvent, + resource: @proposal, + followers: @proposal.participatory_space.followers - coauthors_followers, + extra: { + participatory_space: true + } + ) + end + + def coauthors_followers + @coauthors_followers ||= @proposal.authors.flat_map(&:followers) + end + + def increment_scores + @proposal.coauthorships.find_each do |coauthorship| + if coauthorship.user_group + Decidim::Gamification.increment_score(coauthorship.user_group, :proposals) + else + Decidim::Gamification.increment_score(coauthorship.author, :proposals) + end + end + end + end + end +end From a28f39a0678348faa237a343fafa5b56bbe87aa1 Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Wed, 6 Nov 2024 13:46:25 +0100 Subject: [PATCH 05/12] start test rspec --- .../proposal_published_event_spec.rb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 spec/events/decidim/proposals/proposal_published_event_spec.rb diff --git a/spec/events/decidim/proposals/proposal_published_event_spec.rb b/spec/events/decidim/proposals/proposal_published_event_spec.rb new file mode 100644 index 0000000000..4b5ddb61ee --- /dev/null +++ b/spec/events/decidim/proposals/proposal_published_event_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Proposals + describe "ProposalPublishedEvent", type: :job do + include MailerHelpers + + let(:organization) { create(:organization) } + let(:participatory_process) { create(:participatory_process, organization: organization) } + let(:component) { create(:component, manifest_name: :proposals, participatory_space: participatory_process) } + let!(:proposal_state) { create(:proposal_state, component: component) } + let(:proposal) { create(:proposal, component: component, proposal_state: proposal_state, title: { en: "Test proposition notification" }) } + let(:user) { create(:user, :admin, organization: organization, notifications_sending_frequency: "daily") } + + before do + clear_emails + end + + context "when proposal is published" do + it "sends a notification email with the correct content" do + Decidim::EventsManager.publish( + event: "decidim.proposals.proposal_published", + event_class: ProposalPublishedEvent, + resource: proposal, + affected_users: [user], + ) + + perform_enqueued_jobs do + Decidim::EmailNotificationsDigestGeneratorJob.perform_now(user.id, user.notifications_sending_frequency) + end + + expect(last_email).not_to be_nil + expect(last_email_body).not_to include("translation missing") + expect(last_email_body).to include("Test proposition notification") + + end + end + + end +end +# +# shared_context "when sends the notification digest" do +# context "when daily notification mail" do +# let(:user) { create(:user, :admin, organization:, notifications_sending_frequency: "daily") } +# +# it_behaves_like "notification digest mail" +# end +# +# context "when weekly notification mail" do +# let(:user) { create(:user, :admin, organization:, notifications_sending_frequency: "weekly") } +# +# it_behaves_like "notification digest mail" +# end +# end +# +# shared_examples_for "notification digest mail" do +# context "when a notifiable event takes place" do +# let!(:organization) { create(:organization) } +# let!(:participatory_space) { create(:participatory_process, organization:) } +# +# it "sends a notification to the user's email" do +# perform_enqueued_jobs do +# expect(command.call).to broadcast(:ok) +# Decidim::Notification.last.update(created_at: 1.day.ago) +# Decidim::EmailNotificationsDigestGeneratorJob.perform_now(user.id, user.notifications_sending_frequency) +# end +# +# expect(last_email_body.length).to be_positive +# expect(last_email_body).not_to include("translation missing") +# end +# end +# end From 4c9be63015037e9573659e7c230874fb46995cc2 Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Wed, 6 Nov 2024 17:05:02 +0100 Subject: [PATCH 06/12] continuing rspec --- .../proposal_published_event_spec.rb | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/spec/events/decidim/proposals/proposal_published_event_spec.rb b/spec/events/decidim/proposals/proposal_published_event_spec.rb index 4b5ddb61ee..8ec2b28d2c 100644 --- a/spec/events/decidim/proposals/proposal_published_event_spec.rb +++ b/spec/events/decidim/proposals/proposal_published_event_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "spec_helper" +require "nokogiri" module Decidim::Proposals describe "ProposalPublishedEvent", type: :job do @@ -18,25 +19,25 @@ module Decidim::Proposals end context "when proposal is published" do - it "sends a notification email with the correct content" do - Decidim::EventsManager.publish( - event: "decidim.proposals.proposal_published", - event_class: ProposalPublishedEvent, - resource: proposal, - affected_users: [user], - ) - - perform_enqueued_jobs do - Decidim::EmailNotificationsDigestGeneratorJob.perform_now(user.id, user.notifications_sending_frequency) - end + it "sends a notification email with the correct content" do + subject { ProposalPublishedEvent.new(resource: proposal, event_name: "decidim.proposals.proposal_published", user: proposal.creator_identity) } expect(last_email).not_to be_nil expect(last_email_body).not_to include("translation missing") expect(last_email_body).to include("Test proposition notification") + end + + it "sends a notification with the correct content" do + subject { ProposalPublishedEvent.new(resource: proposal, event_name: "decidim.proposals.proposal_published", user: proposal.creator_identity) } + notification = Decidim::Notification.last + # Parse avec Nokogiri + html_body = Nokogiri::HTML(notification.extra["body"]) + text = html_body.text + expect(text).to eq("Your proposal Test proposition notification has been published") + expect(notification).not_to be_nil end end - end end # From eb00e654b56787bcb3729b57aacf229f9ae8f9dc Mon Sep 17 00:00:00 2001 From: barbara oliveira Date: Wed, 6 Nov 2024 17:21:34 +0100 Subject: [PATCH 07/12] potential final test file --- .../proposal_published_event_spec.rb | 43 +------------------ 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/spec/events/decidim/proposals/proposal_published_event_spec.rb b/spec/events/decidim/proposals/proposal_published_event_spec.rb index 8ec2b28d2c..8dfd763e38 100644 --- a/spec/events/decidim/proposals/proposal_published_event_spec.rb +++ b/spec/events/decidim/proposals/proposal_published_event_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "spec_helper" -require "nokogiri" module Decidim::Proposals describe "ProposalPublishedEvent", type: :job do @@ -19,56 +18,18 @@ module Decidim::Proposals end context "when proposal is published" do - + subject { ProposalPublishedEvent.new(resource: proposal, event_name: "decidim.proposals.proposal_published", user: proposal.creator_identity) } it "sends a notification email with the correct content" do - subject { ProposalPublishedEvent.new(resource: proposal, event_name: "decidim.proposals.proposal_published", user: proposal.creator_identity) } + expect(last_email).not_to be_nil expect(last_email_body).not_to include("translation missing") expect(last_email_body).to include("Test proposition notification") end it "sends a notification with the correct content" do - subject { ProposalPublishedEvent.new(resource: proposal, event_name: "decidim.proposals.proposal_published", user: proposal.creator_identity) } notification = Decidim::Notification.last - # Parse avec Nokogiri - html_body = Nokogiri::HTML(notification.extra["body"]) - text = html_body.text - expect(text).to eq("Your proposal Test proposition notification has been published") - expect(notification).not_to be_nil end end end end -# -# shared_context "when sends the notification digest" do -# context "when daily notification mail" do -# let(:user) { create(:user, :admin, organization:, notifications_sending_frequency: "daily") } -# -# it_behaves_like "notification digest mail" -# end -# -# context "when weekly notification mail" do -# let(:user) { create(:user, :admin, organization:, notifications_sending_frequency: "weekly") } -# -# it_behaves_like "notification digest mail" -# end -# end -# -# shared_examples_for "notification digest mail" do -# context "when a notifiable event takes place" do -# let!(:organization) { create(:organization) } -# let!(:participatory_space) { create(:participatory_process, organization:) } -# -# it "sends a notification to the user's email" do -# perform_enqueued_jobs do -# expect(command.call).to broadcast(:ok) -# Decidim::Notification.last.update(created_at: 1.day.ago) -# Decidim::EmailNotificationsDigestGeneratorJob.perform_now(user.id, user.notifications_sending_frequency) -# end -# -# expect(last_email_body.length).to be_positive -# expect(last_email_body).not_to include("translation missing") -# end -# end -# end From df4935d7f6a62f55fd262cefbf74dbea96bfb2f0 Mon Sep 17 00:00:00 2001 From: Quentin Champenois Date: Thu, 7 Nov 2024 16:55:07 +0100 Subject: [PATCH 08/12] fix: Merge proposal command and anonymous proposals --- .../decidim/proposals/publish_proposal.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/commands/decidim/proposals/publish_proposal.rb b/app/commands/decidim/proposals/publish_proposal.rb index 4e6f2860f1..398f2b88fb 100644 --- a/app/commands/decidim/proposals/publish_proposal.rb +++ b/app/commands/decidim/proposals/publish_proposal.rb @@ -4,13 +4,17 @@ module Decidim module Proposals # A command with all the business logic when a user publishes a draft proposal. class PublishProposal < Decidim::Command + include Decidim::AnonymousProposals::AnonymousBehaviorCommandsConcern + # Public: Initializes the command. # # proposal - The proposal to publish. # current_user - The current user. + # override: decidim-module-anonymous_proposals/app/commands/decidim/anonymous_proposals/publish_proposal_command_overrides.rb def initialize(proposal, current_user) @proposal = proposal - @current_user = current_user + @is_anonymous = allow_anonymous_proposals? && (current_user.blank? || proposal.authored_by?(anonymous_group)) + set_current_user(current_user) end # Executes the command. Broadcasts these events: @@ -77,14 +81,13 @@ def send_notification end def send_publication_notification - puts "send notification from proposal #{@proposal.id}" - # debug message Decidim::EventsManager.publish( - event: "decidim.proposals.proposal_published", + event: "decidim.events.proposals.proposal_published", event_class: Decidim::Proposals::ProposalPublishedEvent, resource: @proposal, affected_users: [@proposal.creator_identity], - extra: { participatory_space: true } + extra: { force_email: true }, + force_send: true ) end @@ -113,6 +116,11 @@ def increment_scores end end end + + # override: decidim-module-anonymous_proposals/app/commands/decidim/anonymous_proposals/publish_proposal_command_overrides.rb + def component + @component ||= @proposal.component + end end end end From cb8772af1b552efdbf033f89742153272836922f Mon Sep 17 00:00:00 2001 From: Quentin Champenois Date: Thu, 7 Nov 2024 17:06:28 +0100 Subject: [PATCH 09/12] fix: Change ProposalPublishedEvent to SimpleEvent --- .../proposals/proposal_published_event.rb | 14 +- spec/shared/simple_event.rb | 202 ++++++++++++++++++ 2 files changed, 204 insertions(+), 12 deletions(-) create mode 100644 spec/shared/simple_event.rb diff --git a/app/events/decidim/proposals/proposal_published_event.rb b/app/events/decidim/proposals/proposal_published_event.rb index 2fe8cf8bb7..05456df290 100644 --- a/app/events/decidim/proposals/proposal_published_event.rb +++ b/app/events/decidim/proposals/proposal_published_event.rb @@ -1,13 +1,7 @@ # app/events/decidim/proposals/proposal_published_event.rb module Decidim module Proposals - class ProposalPublishedEvent < Decidim::Events::BaseEvent - include Decidim::Events::NotificationEvent - - def self.model_name - ActiveModel::Name.new(self, nil, I18n.t('decidim.proposals.notifications.proposal_published.subject')) - end - + class ProposalPublishedEvent < Decidim::Events::SimpleEvent def notification_title I18n.t("decidim.proposals.notifications.proposal_published.title", proposal_title: resource_title) end @@ -23,11 +17,7 @@ def resource_title end def resource_path - Decidim::Proposals::Engine.routes.url_helpers.proposal_path( - resource, - component_id: resource.component.id, - initiative_slug: resource.component.participatory_space.slug - ) + "" end end end diff --git a/spec/shared/simple_event.rb b/spec/shared/simple_event.rb new file mode 100644 index 0000000000..d65d354503 --- /dev/null +++ b/spec/shared/simple_event.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +require "spec_helper" + +shared_context "when a simple event" do + include Decidim::SanitizeHelper + subject { event_instance } + + let(:event_instance) do + described_class.new( + resource: resource, + event_name: event_name, + user: user, + user_role: user_role, + extra: extra + ) + end + + let(:organization) do + if resource.respond_to?(:organization) + resource.organization + else + create :organization + end + end + let(:user) { create :user, organization: organization } + let(:user_role) { :follower } + let(:extra) { {} } + let(:resource_path) { resource_locator(resource).path } + let(:resource_url) { resource_locator(resource).url } + let(:resource_title) { decidim_sanitize_translated(resource.title) } + # to be used when resource is a component resource, not a participatory space, in which case should be overriden + let(:participatory_space) { resource.participatory_space } + let(:participatory_space_title) { decidim_sanitize_translated(participatory_space.title) } + let(:participatory_space_path) { Decidim::ResourceLocatorPresenter.new(participatory_space).path } + let(:participatory_space_url) { Decidim::ResourceLocatorPresenter.new(participatory_space).url } + let(:author) do + if resource.respond_to?(:creator_author) + resource.creator_author + else + resource.author + end + end + let(:author_presenter) { Decidim::UserPresenter.new(author) } + let(:author_name) { decidim_html_escape author.name } + let(:author_path) { author_presenter&.profile_path.to_s } + let(:author_nickname) { author_presenter&.nickname.to_s } + let(:i18n_scope) { event_name } +end + +shared_examples_for "a simple event" do |skip_space_checks| + describe "types" do + subject { described_class } + + it "supports notifications" do + expect(subject.types).to include :notification + end + + it "supports emails" do + expect(subject.types).to include :email + end + end + + describe "email_subject" do + it "is generated correctly" do + expect(subject.email_subject).to be_kind_of(String) + expect(subject.email_subject).not_to include("translation missing") + expect(subject.email_subject).not_to include("script") + end + end + + describe "email_intro" do + it "is generated correctly" do + expect(subject.email_intro).to be_kind_of(String) + expect(subject.email_intro).not_to include("translation missing") + end + end + + describe "email_outro" do + it "is generated correctly" do + expect(subject.email_outro).to be_kind_of(String) + expect(subject.email_outro).not_to include("translation missing") + end + end + + describe "email_greeting" do + it "is generated correctly" do + expect(subject.email_greeting).to be_kind_of(String) + expect(subject.email_greeting).not_to include("translation missing") + end + end + + describe "safe_resource_text" do + it "is generated correctly" do + expect(subject.safe_resource_text).to be_kind_of(String) + expect(subject.safe_resource_text).to be_html_safe + end + end + + describe "notification_title" do + it "is generated correctly" do + expect(subject.notification_title).to be_kind_of(String) + expect(subject.notification_title).not_to include("translation missing") + expect(subject.notification_title).not_to include("script") + end + end + + describe "resource_path" do + it "is generated correctly" do + expect(subject.resource_path).to be_kind_of(String) + end + end + + describe "resource_url" do + it "is generated correctly" do + expect(subject.resource_url).to be_kind_of(String) + expect(subject.resource_url).to start_with("http") + end + end + + describe "resource_title" do + it "responds to the method" do + expect(subject).to respond_to(:resource_title) + end + end + + unless skip_space_checks + describe "participatory_space_url" do + it "is generated correctly" do + expect(subject.participatory_space_url).to be_kind_of(String) + expect(subject.participatory_space_url).to start_with("http") + end + end + + describe "participatory_space_title" do + it "is generated correctly" do + expect(translated(participatory_space.title)).to include("script") + end + end + end + + describe "i18n_options" do + subject { super().i18n_options } + + it { is_expected.to include(resource_path: satisfy(&:present?)) } + it { is_expected.to include(resource_title: satisfy(&:present?)) } + it { is_expected.to include(resource_url: start_with("http")) } + + it "includes the i18n scope" do + if event_instance.event_has_roles? + expect(subject).to include(scope: "#{i18n_scope}.#{user_role}") + else + expect(subject).to include(scope: i18n_scope) + end + end + + unless skip_space_checks + it { is_expected.to include(participatory_space_title: satisfy(&:present?)) } + it { is_expected.to include(participatory_space_url: start_with("http")) } + end + end +end + +shared_examples_for "a simple event email" do + describe "email_subject" do + it "is generated correctly" do + expect(subject.email_subject).to eq(email_subject) + end + + # it "is html safe" do + # # pending "Enable after #12547 is merged" + # expect(subject.email_subject).not_to include("script") + # end + end + + describe "email_intro" do + it "is generated correctly" do + expect(subject.email_intro).to eq(email_intro) + end + end + + describe "email_outro" do + it "is generated correctly" do + expect(subject.email_outro).to eq(email_outro) + end + end +end + +shared_examples_for "a simple event notification" do + describe "notification_title" do + it "is generated correctly" do + expect(subject.notification_title) + .to eq(notification_title) + end + # + # it "is html safe" do + # pp subject.notification_title + # pending "Enable after #12547 is merged" + # expect(subject.notification_title).not_to include("script") + # end + end +end \ No newline at end of file From d4beb619b21d89f73e25c35761172d1e01561ddf Mon Sep 17 00:00:00 2001 From: Quentin Champenois Date: Thu, 7 Nov 2024 17:18:01 +0100 Subject: [PATCH 10/12] fix: Proposal Published Event --- app/commands/decidim/proposals/publish_proposal.rb | 2 +- .../decidim/proposals/proposal_published_event.rb | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/app/commands/decidim/proposals/publish_proposal.rb b/app/commands/decidim/proposals/publish_proposal.rb index 398f2b88fb..81f58d3779 100644 --- a/app/commands/decidim/proposals/publish_proposal.rb +++ b/app/commands/decidim/proposals/publish_proposal.rb @@ -82,7 +82,7 @@ def send_notification def send_publication_notification Decidim::EventsManager.publish( - event: "decidim.events.proposals.proposal_published", + event: "decidim.events.proposals.proposal_published_event", event_class: Decidim::Proposals::ProposalPublishedEvent, resource: @proposal, affected_users: [@proposal.creator_identity], diff --git a/app/events/decidim/proposals/proposal_published_event.rb b/app/events/decidim/proposals/proposal_published_event.rb index 05456df290..27b8bba9ab 100644 --- a/app/events/decidim/proposals/proposal_published_event.rb +++ b/app/events/decidim/proposals/proposal_published_event.rb @@ -2,23 +2,9 @@ module Decidim module Proposals class ProposalPublishedEvent < Decidim::Events::SimpleEvent - def notification_title - I18n.t("decidim.proposals.notifications.proposal_published.title", proposal_title: resource_title) - end - - def notification_body - I18n.t("decidim.proposals.notifications.proposal_published.body", proposal_link: resource_path) - end - - private - def resource_title translated_attribute(resource.title) end - - def resource_path - "" - end end end end From 2fbbf92b6d8c96416e7bea692bf1a043cf6eca95 Mon Sep 17 00:00:00 2001 From: Quentin Champenois Date: Thu, 7 Nov 2024 17:18:49 +0100 Subject: [PATCH 11/12] fix: Push FR locales --- config/locales/fr.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 58f4874a10..00052e5887 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -67,6 +67,12 @@ fr: new: sign_in_disabled: Vous pouvez accéder avec un compte externe events: + proposals: + proposal_published_event: + email_intro: 'que vous suivez, a publié une nouvelle proposition appelée "%{resource_title}". Découvrez-le et contribuez:' + email_outro: Vous avez reçu cette notification car vous suivez . Si vous souhaitez vous désabonner des notifications, connectez-vous à la plateforme, puis rendez-vous dans l'onglet “Mon compte” > “Paramètres des notifications”. + email_subject: Nouvelle proposition "%{resource_title}" publiée + notification_title: La proposition %{resource_title} a été publiée. budgets: pending_order: email_intro: Le vote sur le budget "%{resource_title}" n'est pas encore finalisé sur la concertation "%{participatory_space_title}". From 899f21481b58e6ecfa084ab68cfc70c1a58bb07d Mon Sep 17 00:00:00 2001 From: Quentin Champenois Date: Thu, 7 Nov 2024 17:37:41 +0100 Subject: [PATCH 12/12] test: Add specs for proposal_published_event --- .../proposal_published_event_spec.rb | 91 ++++++++---- spec/shared/translated_event_examples.rb | 131 ++++++++++++++++++ 2 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 spec/shared/translated_event_examples.rb diff --git a/spec/events/decidim/proposals/proposal_published_event_spec.rb b/spec/events/decidim/proposals/proposal_published_event_spec.rb index 8dfd763e38..43fd5ea9db 100644 --- a/spec/events/decidim/proposals/proposal_published_event_spec.rb +++ b/spec/events/decidim/proposals/proposal_published_event_spec.rb @@ -2,34 +2,77 @@ require "spec_helper" -module Decidim::Proposals - describe "ProposalPublishedEvent", type: :job do - include MailerHelpers - - let(:organization) { create(:organization) } - let(:participatory_process) { create(:participatory_process, organization: organization) } - let(:component) { create(:component, manifest_name: :proposals, participatory_space: participatory_process) } - let!(:proposal_state) { create(:proposal_state, component: component) } - let(:proposal) { create(:proposal, component: component, proposal_state: proposal_state, title: { en: "Test proposition notification" }) } - let(:user) { create(:user, :admin, organization: organization, notifications_sending_frequency: "daily") } - - before do - clear_emails - end +module Decidim + module Proposals + describe ProposalPublishedEvent do + let(:resource) { create :extended_proposal } + let(:participatory_process) { create :participatory_process, organization: organization } + let(:proposal_component) { create(:extended_proposal_component, participatory_space: participatory_process) } + let(:resource_title) { decidim_sanitize_translated(resource.title) } + let(:event_name) { "decidim.events.proposals.proposal_published" } + + include_context "when a simple event" + + it_behaves_like "a simple event" + + describe "resource_text" do + it "returns the proposal body" do + expect(subject.resource_text).to eq(resource.body) + end + end + + describe "email_subject" do + context "when resource title contains apostrophes" do + it "is generated correctly" do + expect(subject.email_subject).to eq("New proposal \"#{resource_title}\" by @#{author.nickname}") + end + end + + it "is generated correctly" do + expect(subject.email_subject).to eq("New proposal \"#{resource_title}\" by @#{author.nickname}") + end + end - context "when proposal is published" do - subject { ProposalPublishedEvent.new(resource: proposal, event_name: "decidim.proposals.proposal_published", user: proposal.creator_identity) } - it "sends a notification email with the correct content" do + describe "email_intro" do + it "is generated correctly" do + expect(subject.email_intro) + .to eq("#{author.name} @#{author.nickname}, who you are following, has published a new proposal called \"#{resource_title}\". Check it out and contribute:") + end + end - expect(last_email).not_to be_nil - expect(last_email_body).not_to include("translation missing") - expect(last_email_body).to include("Test proposition notification") + describe "email_outro" do + it "is generated correctly" do + expect(subject.email_outro) + .to eq("You have received this notification because you are following @#{author.nickname}. You can stop receiving notifications following the previous link.") + end end - it "sends a notification with the correct content" do - notification = Decidim::Notification.last - expect(notification).not_to be_nil + describe "notification_title" do + it "is generated correctly" do + expect(subject.notification_title) + .to include("The #{resource_title} proposal was published by ") + + expect(subject.notification_title) + .to include("#{author.name} @#{author.nickname}.") + end + end + + describe "translated notifications" do + let(:en_body) { "A nice proposal" } + let(:body) { { en: en_body, machine_translations: { ca: "Une belle idee" } } } + let(:resource) do + create :extended_proposal, + component: proposal_component, + title: { en: "A nice proposal", machine_translations: { ca: "Une belle idee" } }, + body: body + end + + let(:en_version) { subject.resource_text["en"] } + let(:machine_translated) { subject.resource_text["machine_translations"]["ca"] } + let(:translatable) { true } + + it_behaves_like "a translated event" end end end -end +end \ No newline at end of file diff --git a/spec/shared/translated_event_examples.rb b/spec/shared/translated_event_examples.rb new file mode 100644 index 0000000000..47a9a6b6a7 --- /dev/null +++ b/spec/shared/translated_event_examples.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +shared_examples_for "a translated event" do + context "when it is not machine machine translated" do + let(:organization) { create(:organization, enable_machine_translations: false, machine_translation_display_priority: "original") } + + it "does not perform translation" do + expect(subject.perform_translation?).to eq(false) + end + + it "does not have a missing translation" do + expect(subject.translation_missing?).to eq(false) + end + + it "does have content available in multiple languages" do + expect(subject.content_in_same_language?).to eq(false) + end + + it "does return the original language" do + expect(subject.safe_resource_text).to eq(en_version) + end + + it "does not offer an alternate translation" do + expect(subject.safe_resource_translated_text).to eq(en_version) + end + end + + context "when is machine machine translated" do + let(:user) { create :user, organization: organization, locale: "ca" } + + around do |example| + I18n.with_locale(user.locale) { example.run } + end + + context "when priority is original" do + let(:organization) { create(:organization, enable_machine_translations: true, machine_translation_display_priority: "original") } + + it "does perform translation" do + expect(subject.perform_translation?).to eq(translatable) + end + + it "does not have a missing translation" do + expect(subject.translation_missing?).to eq(false) + end + + it "does have content available in multiple languages" do + expect(subject.content_in_same_language?).to eq(false) + end + + it "does return the original language" do + expect(subject.safe_resource_text).to eq(en_version) + end + + it "does not offer an alternate translation" do + expect(subject.safe_resource_translated_text).to eq(machine_translated) + end + + context "when translation is not available" do + let(:body) { { en: en_body } } + + it "does perform translation" do + expect(subject.perform_translation?).to eq(translatable) + end + + it "does have a missing translation" do + expect(subject.translation_missing?).to eq(translatable) + end + + it "does have content available in multiple languages" do + expect(subject.content_in_same_language?).to eq(false) + end + + it "does return the original language" do + expect(subject.safe_resource_text).to eq(en_version) + end + + it "does not offer an alternate translation" do + expect(subject.safe_resource_translated_text).to eq(en_version) + end + end + end + + context "when priority is translation" do + let(:organization) { create(:organization, enable_machine_translations: true, machine_translation_display_priority: "translation") } + + it "does perform translation" do + expect(subject.perform_translation?).to eq(translatable) + end + + it "does not have a missing translation" do + expect(subject.translation_missing?).to eq(false) + end + + it "does have content available in multiple languages" do + expect(subject.content_in_same_language?).to eq(false) + end + + it "does return the original language" do + expect(subject.safe_resource_text).to eq(en_version) + end + + it "does not offer an alternate translation" do + expect(subject.safe_resource_translated_text).to eq(machine_translated) + end + + context "when translation is not available" do + let(:body) { { en: en_body } } + + it "does perform translation" do + expect(subject.perform_translation?).to eq(translatable) + end + + it "does have a missing translation" do + expect(subject.translation_missing?).to eq(translatable) + end + + it "does have content available in multiple languages" do + expect(subject.content_in_same_language?).to eq(false) + end + + it "does return the original language" do + expect(subject.safe_resource_text).to eq(en_version) + end + + it "does not offer an alternate translation" do + expect(subject.safe_resource_translated_text).to eq(en_version) + end + end + end + end +end