Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions app/services/decidim/chatbot/workflows/proposals_workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ def process_action_input
else
mark_as_responding
if commentable_id
delegate_workflow(
Decidim::Chatbot::Workflows::CommentsWorkflow,
resource_gid: commentable_gid.to_s,
back_button: { id: "more", title: I18n.t("decidim.chatbot.workflows.proposals.buttons.more") }
)
if component_commentable?
delegate_workflow(
Decidim::Chatbot::Workflows::CommentsWorkflow,
resource_gid: commentable_gid.to_s,
back_button: { id: "more", title: I18n.t("decidim.chatbot.workflows.proposals.buttons.more") }
)
else
send_message!(
body: I18n.t("decidim.chatbot.workflows.proposals.comments_disabled")
)
end
else
send_proposal_details
end
Expand Down Expand Up @@ -71,18 +77,22 @@ def send_proposal_details
# Use video thumbnail as header image if available, otherwise use proposal photo with fallback
header_image = video.thumbnail_url.presence || resource_url(proposal.photo, fallback_image: true)

send_message!(
type: :interactive_buttons,
body_text: body,
header_image:,
footer_text: sanitize_text(proposal.creator_author&.presenter&.name, 60),
buttons: [
{
id: "comment-#{proposal.id}",
title: I18n.t("decidim.chatbot.workflows.proposals.buttons.comment")
}
]
)
if component_commentable?
send_message!(
type: :interactive_buttons,
body_text: body,
header_image:,
footer_text: sanitize_text(proposal.creator_author&.presenter&.name, 60),
buttons: [
{
id: "comment-#{proposal.id}",
title: I18n.t("decidim.chatbot.workflows.proposals.buttons.comment")
}
]
)
else
send_message!(body:, preview_url: true)
end
end

# Calculate maximum body length dynamically to stay within 1024 char limit
Expand Down Expand Up @@ -143,6 +153,10 @@ def component
@component ||= Decidim::Component.find_by(id: config[:component_id])
end

def component_commentable?
@component_commentable ||= component&.settings&.comments_enabled?
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

component_commentable? uses ||= memoization, which won’t cache a false result (so when comments are disabled it will recompute on every call). Use an instance_variable_defined?/defined? guard (or remove memoization entirely) so false/nil values are cached too.

Suggested change
@component_commentable ||= component&.settings&.comments_enabled?
return @component_commentable if instance_variable_defined?(:@component_commentable)
@component_commentable = component&.settings&.comments_enabled?

Copilot uses AI. Check for mistakes.
end

def proposals
@proposals ||= begin
proposal_search = Decidim::Proposals::Proposal.where(component:).published.only_amendables
Expand Down
31 changes: 21 additions & 10 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ en:
success: Chatbot settings saved successfully.
messages:
deactivated: The chatbot is currently deactivated. Please try again later.
generic_error: Sorry, something went wrong while processing your request.
Please try again later.
reset_workflows: Thanks for participating! You can start again by sending
any message.
stale_cleared: Your previous session was cleared due to inactivity. Starting
fresh!
generic_error: Sorry, something went wrong while processing your request. Please try again later.
providers:
whatsapp:
description: Enable to receive and respond to messages via WhatsApp Business
Expand All @@ -61,25 +62,35 @@ en:
exit: Exit & go back
reset: End conversation
unprocessable_input: Sorry, I couldn't process that. Please try again with
a different message or use "Exit & go back" or "End conversation" to start over.
a different message or use "Exit & go back" or "End conversation" to start
over.
comments:
comment_received: Here is your comment, if you want to change it just send a new message. If you're happy with it, click "Submit comment".
resource_not_found: Unfortunately, the requested resource could not be found.
Please choose another one or exit and restart.
instructions: Thanks for your interest in commenting "%{title}"! Please write your comment below and send it to me. You will have the chance to review and edit your comment before submitting it.
comment_created: "Your comment has been published successfully! You can view it here:"
comment_creation_failed: "Unfortunately, your comment could not be published. Please try again or contact support."
signature: "\n\nSent via %{provider}"
buttons:
submit: Publish comment
exit: Cancel & go back
submit: Publish comment
comment_created: 'Your comment has been published successfully! You can
view it here:'
comment_creation_failed: Unfortunately, your comment could not be published.
Please try again or contact support.
comment_received: Here is your comment, if you want to change it just send
a new message. If you're happy with it, click "Publish comment".
instructions: Thanks for your interest in commenting "%{title}"! Please
write your comment below and send it to me. You will have the chance to
review and edit your comment before submitting it.
resource_not_found: Unfortunately, the requested resource could not be found.
Please choose another one or exit and restart.
signature: |2-


Sent via %{provider}
organization_welcome:
title: Organization Welcome
proposals:
buttons:
comment: Comment proposal
more: View more
view_proposal: View proposal
comments_disabled: Commenting is disabled for this proposal.
no_more_proposals: There are no more proposals to show. Please choose one
of the proposals above.
no_proposals: There are no proposals published yet. Please check back later.
Expand Down
1 change: 1 addition & 0 deletions config/locales/pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pt-BR:
comment: Comentar proposta
more: Ver mais
view_proposal: Ver proposta
comments_disabled: Os comentários estão desativados para esta proposta.
no_more_proposals: Não há mais propostas para mostrar. Por favor, escolha uma das propostas acima.
no_proposals: Não há propostas publicadas ainda. Por favor, verifique novamente mais tarde.
remaining_proposals: Há %{count} mais propostas. Clique em "Ver mais" para vê-las.
Expand Down
59 changes: 52 additions & 7 deletions spec/services/workflows/proposals_workflow_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,39 @@ module Workflows
subject.start
end

it "sends proposal details with comment button" do
expect(adapter).to receive(:send_message!).with(
hash_including(
type: :interactive_buttons,
buttons: [hash_including(id: "comment-#{clicked_proposal.id}")]
context "when comments are enabled in the component" do
before do
proposals_component.update!(settings: { comments_enabled: true })
end

it "sends proposal details with comment button" do
expect(adapter).to receive(:send_message!).with(
hash_including(
type: :interactive_buttons,
buttons: [hash_including(id: "comment-#{clicked_proposal.id}")]
)
)
)
subject.start
subject.start
end
end

context "when comments are disabled in the component" do
before do
proposals_component.update!(settings: { comments_enabled: false })
end

it "sends proposal details as text message without buttons" do
expect(adapter).to receive(:send_message!).with(
hash_including(
body: anything,
preview_url: true
)
) do |args|
# Make sure it's not an interactive_buttons message
expect(args).not_to include(:type)
end
subject.start
end
end

context "when proposal body contains a YouTube iframe" do
Expand All @@ -322,6 +347,7 @@ module Workflows

before do
clicked_proposal.update!(body: body_with_youtube)
proposals_component.update!(settings: { comments_enabled: true })
end

it "sends interactive_buttons message with video URL in body text" do
Expand Down Expand Up @@ -368,6 +394,7 @@ module Workflows

before do
clicked_proposal.update!(body: body_with_vimeo)
proposals_component.update!(settings: { comments_enabled: true })
end

it "sends interactive_buttons message with video URL in body text" do
Expand Down Expand Up @@ -407,6 +434,7 @@ module Workflows

before do
clicked_proposal.update!(body: body_without_video)
proposals_component.update!(settings: { comments_enabled: true })
end

it "sends interactive_buttons message without video URL" do
Expand Down Expand Up @@ -447,6 +475,7 @@ module Workflows
allow(received_message).to receive(:button_id).and_return("comment-#{clicked_proposal.id}")
allow(CommentsWorkflow).to receive(:new).and_return(comments_workflow_instance)
allow(comments_workflow_instance).to receive(:start)
proposals_component.update!(settings: { comments_enabled: true })
end

it "marks as responding" do
Expand All @@ -466,6 +495,22 @@ module Workflows
expect(sender.workflow_stack.last["options"]["resource_gid"]).to eq(clicked_proposal.to_global_id.to_s)
expect(sender.workflow_stack.last["options"]["back_button"]).to be_present
end

context "when comments are disabled in the component" do
before do
proposals_component.update!(settings: { comments_enabled: false })
end

it "sends a non-interactive message and does not delegate" do
expect(adapter).to receive(:send_message!).with(
hash_including(
body: I18n.t("decidim.chatbot.workflows.proposals.comments_disabled")
)
)
expect(comments_workflow_instance).not_to receive(:start)
subject.start
end
end
end
end
end
Expand Down