Conversation
There was a problem hiding this comment.
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.
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
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
endbutton and always 3 buttons, butSingleParticipatorySpaceWorkflowusesid: "exit"and only adds it whenparent_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 usedecidim.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: falseinside the workflow (not_configuredmessage), but enable/disable is now enforced inWebhooksController#check_enabled(before the workflow runs) andSingleParticipatorySpaceWorkflowno longer checkssetting.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_classandbutton_id == "end", but the implementation migrated toworkflow_stackand usesbutton_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 toworkflow_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_classupdates indelegate_workflow/reset_workflows, but those columns were removed in favor ofworkflow_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.
| instructions: Instructions (optional) - displayed before showing the space | ||
| information |
There was a problem hiding this comment.
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 (>/|).
| instructions: Instructions (optional) - displayed before showing the space | |
| information | |
| instructions: Instructions (optional) - displayed before showing the space information |
| 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 || {} |
There was a problem hiding this comment.
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).
| 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? | ||
|
|
There was a problem hiding this comment.
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).
| 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) |
There was a problem hiding this comment.
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).
| form(manifest&.form) | |
| form_class = manifest&.form || SettingForm | |
| form(form_class) |
| 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 |
There was a problem hiding this comment.
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).
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| sender.push_to_workflow_stack!(workflow_class, conf) | ||
| sender.current_workflow.new(adapter:, message:, **conf).start(true) |
There was a problem hiding this comment.
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.
| 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) |
| 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 |
There was a problem hiding this comment.
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.
No description provided.