Skip to content

Add proposal info to the chat#11

Merged
microstudi merged 19 commits intomainfrom
proposal-info
Feb 11, 2026
Merged

Add proposal info to the chat#11
microstudi merged 19 commits intomainfrom
proposal-info

Conversation

@microstudi
Copy link
Member

No description provided.

Copilot AI review requested due to automatic review settings February 5, 2026 10:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds comprehensive WhatsApp configuration documentation for the Decidim Chatbot module, including setup instructions for Meta's developer platform and a guide for configuring the integration.

Changes:

  • Added a new detailed WhatsApp configuration guide (docs/providers/whatsapp-configuration.md)
  • Updated README.md to reference the new documentation and fixed a typo in ngrok command
  • Simplified WhatsApp setup instructions in README by moving detailed steps to the dedicated guide

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
docs/providers/whatsapp-configuration.md New comprehensive guide covering all steps to configure WhatsApp integration with Meta's platform
README.md Fixed typo (ngrok port 300 → 3000), added reference to new configuration guide, streamlined WhatsApp setup section

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 36 out of 37 changed files in this pull request and generated 15 comments.

Comments suppressed due to low confidence (6)

spec/services/workflows/single_participatory_space_workflow_spec.rb:72

  • The spec expects an end button and always 3 buttons, but SingleParticipatorySpaceWorkflow uses id: "exit" and only adds it when parent_workflow.present?. Update the expectations (and button ids) to match the workflow's actual button set.
    spec/services/workflows/single_participatory_space_workflow_spec.rb:118
  • This spec is still using legacy i18n keys under decidim.chatbot.workflows.single_participatory_space_workflow.*, but the implementation/locales now use decidim.chatbot.workflows.single_participatory_space.*. Update the expectations to the new keys so the tests align with the shipped translations.
    spec/services/workflows/single_participatory_space_workflow_spec.rb:311
  • This spec asserts behavior for enabled: false inside the workflow (not_configured message), but enable/disable is now enforced in WebhooksController#check_enabled (before the workflow runs) and SingleParticipatorySpaceWorkflow no longer checks setting.enabled?. Either move this expectation to controller specs or adjust the workflow spec to reflect the new responsibility split.
    spec/services/workflows/single_participatory_space_workflow_spec.rb:135
  • The exit-path specs still rely on sender.current_workflow_class/sender.parent_workflow_class and button_id == "end", but the implementation migrated to workflow_stack and uses button_id == "exit". Update these examples to assert stack behavior (push/pop/clear) and the correct button id; otherwise they will fail once the DB columns are removed.
    spec/services/workflows/organization_welcome_workflow_spec.rb:103
  • This spec block still expects delegation via actionable buttons and asserts sender.current_workflow_class/sender.parent_workflow_class, but the workflow implementation removed interactive buttons and workflow tracking was migrated to workflow_stack. Update these examples to match the new behavior (plain text welcome + stack-based delegation) or remove the now-invalid actionable-path tests.
    spec/services/workflows/base_workflow_spec.rb:160
  • This spec still asserts sender.current_workflow_class/sender.parent_workflow_class updates in delegate_workflow/reset_workflows, but those columns were removed in favor of workflow_stack. Update the expectations to validate stack mutations (push_to_workflow_stack!, clear_workflow_stack!) instead of the old columns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +35
instructions: Instructions (optional) - displayed before showing the space
information
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

instructions translation is split across two YAML lines (instructions: then an indented information without a key), which makes en.yml invalid YAML and will fail locale loading. Combine into a single line or use YAML folding (>/|).

Suggested change
instructions: Instructions (optional) - displayed before showing the space
information
instructions: Instructions (optional) - displayed before showing the space information

Copilot uses AI. Check for mistakes.
Comment on lines 9 to +26
def current_workflow
current_workflow_class&.safe_constantize || setting.workflow
workflow_stack.last&.dig("class")&.safe_constantize || setting.workflow
end

def parent_workflow
parent_workflow_class&.safe_constantize
workflow_stack[-2]&.dig("class")&.safe_constantize
end

def locale
metadata["locale"].presence || decidim_user&.locale.presence || setting.organization.default_locale
end

def current_workflow_options
workflow_stack.last&.dig("options").presence || {}
end

def parent_workflow_options
workflow_stack[-2]&.dig("options").presence || {}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

current_workflow/parent_workflow (and the options helpers) dig using string keys, but push_to_workflow_stack!/current_workflow_options! append hashes with symbol keys (class:, options:). ActiveRecord JSON casting does not guarantee deep stringification in-memory, so dig("class")/dig("options") can return nil until reload, breaking delegation/exit logic. Store keys as strings consistently (or use indifferent access / dig(:class) and update all writers accordingly).

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +46
deactivated_message = I18n.t(Chatbot.deactivated_message, default: Chatbot.deactivated_message)

action = deactivated_message.present? ? "sending deactivated response" : "ignoring incoming message"
Rails.logger.info("Chatbot is disabled for provider #{provider} (#{action})")
adapter.send_message!(deactivated_message) if deactivated_message.present?

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

Chatbot.deactivated_message is documented as nullable (to disable the response), but this method always calls I18n.t(Chatbot.deactivated_message, ...). If the config is nil, I18n.t(nil, ...) will raise. Guard with return/skip translation when Chatbot.deactivated_message.blank? (and only call I18n.t when a key/message is present).

Copilot uses AI. Check for mistakes.
workflow_name = params.dig(:setting, :start_workflow) || params[:workflow] || current_setting.start_workflow
manifest = Decidim::Chatbot.start_workflows_registry.find(workflow_name)
form(manifest&.form || SettingForm)
form(manifest&.form)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

workflow_form_class now calls form(manifest&.form); if workflow_name is invalid / manifest is missing, this will pass nil to form(...) and likely error. Keep a safe fallback (e.g., manifest&.form || SettingForm) or handle missing manifests explicitly (e.g., show validation error).

Suggested change
form(manifest&.form)
form_class = manifest&.form || SettingForm
form(form_class)

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +15
def process_user_input
send_message!("todo: process user input for comments workflow for proposal #{proposal.id}")
end

private

def proposal
@proposal ||= Decidim::Proposals::Proposal.find_by(id: options[:proposal_id])
end
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

process_user_input interpolates proposal.id, but proposal can be nil (invalid/missing proposal_id), which will raise. Handle the nil case explicitly (send a friendly message and exit_workflow / reset_workflows, or raise a controlled error).

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +33
def process_user_input
return send_ending if current_proposals.empty? || remaining_proposals_count.negative?

mark_as_responding
send_cards
sleep(1) # Sleep for a short time to ensure the user receives the first message before the continuation
remaining_proposals_count <= per_page ? send_ending : send_continuation
sender.current_workflow_merge!(page: current_page + 1, random_seed:)
end

def process_action_input
case received_message.button_id
when "more"
process_user_input
when "exit"
exit_workflow
else
mark_as_responding
if comment_on_id
delegate_workflow(CommentsWorkflow, proposal_id: comment_on_id)
else
send_proposal_details
end
end
end

private
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

ProposalsWorkflow and its WhatsApp payload format are newly introduced, but there are no specs covering pagination (page/random_seed), button actions (more, exit, proposal id, comment-*), and the edge cases (no_proposals, last page). Adding a dedicated workflow spec would help prevent regressions, especially given the provider payload complexity.

Copilot uses AI. Check for mistakes.
Comment on lines 48 to 51
trait :with_parent_workflow do
current_workflow_class { "Decidim::Chatbot::Workflows::ParticipatorySpaceWorkflow" }
current_workflow_class { "Decidim::Chatbot::Workflows::SingleParticipatorySpaceWorkflow" }
parent_workflow_class { "Decidim::Chatbot::Workflows::OrganizationWelcomeWorkflow" }
end
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

This factory still sets current_workflow_class/parent_workflow_class, but the Sender schema was changed to use workflow_stack (and the old columns were removed in the migrations). Update the factory traits to populate workflow_stack instead (and remove the old attributes) so tests can build valid senders.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +15
def process_user_input
return send_ending if current_proposals.empty? || remaining_proposals_count.negative?

mark_as_responding
send_cards
sleep(1) # Sleep for a short time to ensure the user receives the first message before the continuation
remaining_proposals_count <= per_page ? send_ending : send_continuation
sender.current_workflow_merge!(page: current_page + 1, random_seed:)
end
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The early return condition current_proposals.empty? || remaining_proposals_count.negative? is incorrect with the current remaining_proposals_count formula (count - per_page * current_page): on page 1, any count < per_page makes remaining_proposals_count negative and you will never send the first page of proposals. Remove the negative check from the precondition and/or compute remaining using an offset (per_page * (current_page - 1)) so the last page is still shown.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +58
sender.push_to_workflow_stack!(workflow_class, conf)
sender.current_workflow.new(adapter:, message:, **conf).start(true)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

delegate_workflow pushes only the new workflow onto workflow_stack and then instantiates via sender.current_workflow (which depends on the stack). This does not actually preserve the current workflow as the parent (so exit_workflow can't reliably return), and it can instantiate the wrong class if workflow_stack hasn't been reloaded/casted yet. Consider ensuring the current workflow is on the stack before pushing the new one, and instantiate workflow_class directly rather than going back through sender.current_workflow.

Suggested change
sender.push_to_workflow_stack!(workflow_class, conf)
sender.current_workflow.new(adapter:, message:, **conf).start(true)
# Ensure the current workflow is stored as the parent when the stack is empty
if sender.workflow_stack.empty?
sender.push_to_workflow_stack!(self.class, options)
end
# Push the delegated workflow onto the stack
sender.push_to_workflow_stack!(workflow_class, conf)
# Instantiate the delegated workflow class directly and start it
workflow_class.new(adapter:, message:, **conf).start(true)

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +16
class CommentsWorkflow < BaseWorkflow
def process_user_input
send_message!("todo: process user input for comments workflow for proposal #{proposal.id}")
end

private

def proposal
@proposal ||= Decidim::Proposals::Proposal.find_by(id: options[:proposal_id])
end
end
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

CommentsWorkflow is introduced without any automated tests and currently contains placeholder behavior. Add specs for the expected happy path and failure modes (missing proposal, exit behavior, delegation from ProposalsWorkflow) once the workflow is implemented so changes here don't silently break chat flows.

Copilot uses AI. Check for mistakes.
@microstudi microstudi merged commit 04d6d16 into main Feb 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants