iachat/app/javascript/dashboard/api
Gabriel Jablonski e032fc7774
feat(whatsapp): convert inbox between WhatsApp providers (#268)
* feat(whatsapp): allow converting inbox between WhatsApp providers

Adds a Convert flow to switch a WhatsApp inbox between the four
supported providers (default/360dialog, whatsapp_cloud, baileys, zapi)
without losing conversations, agents, or history.

- Channel::Whatsapp#convert_provider! runs inside a transaction:
  disconnects the old provider, clears provider_connection and
  message_templates, assigns the new provider/config, and triggers
  webhook setup plus template resync on the new service.
- New POST /api/v1/accounts/:id/inboxes/:id/convert_provider endpoint
  guarded by InboxPolicy#convert_provider? (admin only).
- UI adds a Convert button on the inbox Settings page with a
  type-to-confirm ConvertInboxModal that lists the effects before
  redirecting to a dedicated route reusing the WhatsApp provider
  wizard in convert mode (phone number locked, current provider
  hidden from the picker).

* chore(whatsapp): polish convert UI colors and expand specs

- Settings: use slate for the Convert trigger and ruby for the modal
  confirm to mirror the delete gate instead of the less conventional
  amber variant.
- Drop the redundant "current provider is hidden from the list"
  sentence from the convert wizard description.
- Add specs for the post-conversion webhook setup path (triggered and
  skipped branches) and the sync_templates error-rescue behaviour.

* fix: address CodeRabbit review on convert-provider flow

- Whitelist provider_config keys in the convert endpoint via permit
  rather than permit!, and default to an empty hash when omitted so
  the request no longer crashes.
- Pre-validate the new provider config before disconnecting the old
  session so a bad target config no longer terminates the existing
  provider; also keep the disconnect bound to the old provider_url.
- Guard ConvertInboxModal's submit handler so pressing Enter cannot
  bypass the type-to-confirm gate, and migrate it to <script setup>.
- Reject invalid ?provider= query values in convert mode so hidden
  providers (Twilio, the current provider) cannot be reached via URL.
- Await the inbox fetch in InboxConvert before running the route guard
  so directly opening the route for a non-WhatsApp inbox redirects.
- Remove the unreachable second CloudWhatsapp branch in Whatsapp.vue.

* fix: address second CodeRabbit round on convert-provider flow

- Unify provider picker validation so create mode also rejects
  unknown ?provider= values, with a single helper that accepts
  available providers plus the whatsapp_manual fallback.
- Simplify the pre-validation rollback in convert_provider!: the
  errors snapshot/merge dance was redundant because assign_attributes
  does not clear errors.
- Follow the repo convention of asserting on error.class.name so the
  rollback spec stays stable under reloading/parallel environments.
- Strengthen the controller success spec with provider_connection and
  message_templates cleanup invariants, and set Content-Type on the
  templates stub so HTTParty parses the empty data array correctly.

* fix: address third CodeRabbit round on convert-provider flow

- Add 360Dialog entry to the Whatsapp provider catalog, keep it hidden
  from the create picker (preserving the existing fork behavior) but
  expose it in the convert picker where it is a valid target. Restore
  URL reachability for ?provider=360dialog in create mode.
- Scope the WHATSAPP_MANUAL allowance to create mode only: the manual
  fallback flow is not reachable in convert mode.
- Redirect to the inboxes list in InboxConvert when the inbox is still
  absent after the store fetch, so the page no longer stays blank.
- Use an explicit allowlist of WhatsApp providers to gate the Convert
  button instead of negating Twilio, so adding a new WhatsApp channel
  type will not silently expose the flow.
- Bind the disabled provider display field with :value instead of
  v-model, since the underlying computed is getter-only.
- Add Content-Type: application/json to the templates stub in the
  model spec so HTTParty parses the empty data array.

* fix: address fourth CodeRabbit round on convert-provider flow

- Reject no-op conversions that target the same provider as the one
  already configured, so the endpoint no longer wipes provider
  connection and message templates on a request that changes nothing.
- Call the provider service's disconnect directly so failures abort
  the conversion instead of being silently swallowed; otherwise the
  old external session could remain live while the inbox flips to
  the new provider.
- Cover both behaviors with specs.

* fix: address fifth CodeRabbit round on convert-provider flow

- Reset the Vuelidate state when closing ConvertInboxModal so reopening
  the gate does not surface stale validation errors.
- Call teardown_webhooks before converting away from whatsapp_cloud so
  the Meta webhook subscription is removed for embedded_signup channels,
  mirroring the destroy-time cleanup (manual-setup channels keep the
  existing no-op behavior). Swallow teardown failures so a flaky Meta
  call does not abort the swap.
- Switch the rollback specs to compare message_templates counts instead
  of the boolean be_present matcher so they remain meaningful if the
  fixture happens to have an empty templates list.

* fix: address sixth CodeRabbit round on convert-provider flow

- Derive the convert header's current-provider label from the shared
  PROVIDER_CATALOG so the picker and header stay in sync.
- Assert the full Cloud provider_config payload and the absence of the
  Baileys-only provider_url key on both the controller success spec
  and the model atomic-swap spec.
- In the sync-error spec, reload and assert that the record was
  actually flipped to the new provider before the sync rescue fires,
  so the test can't pass on a pre-save failure.

* test: pin 422 error payload on convert_provider negative paths

The unsupported-conversion and invalid-config specs only checked the
status code, so they would have stayed green if the 422 started coming
from a different branch. Pin the response body so each example actually
covers the failure case it names.

* fix(baileys): save custom host as provider_url, not url

The Baileys form was writing the custom endpoint to
provider_config['url'] while the backend reads
provider_config['provider_url']. That silently broke the custom-host
feature for newly created or converted Baileys inboxes: they always
fell back to BAILEYS_PROVIDER_DEFAULT_URL. Align the key on both ends.

* fix(whatsapp): skip second validation pass in convert_provider!

The transaction's save! was re-running validate_provider_config after
the old provider's session had already been disconnected, so a transient
Graph API failure on the second check could roll back the swap while
leaving the external session terminated — the exact inconsistency the
pre-flight valid? was meant to rule out.

Capture the validated provider_config snapshot after valid? (so fields
populated by before_validation callbacks like webhook_verify_token are
preserved) and switch the final persist to save!(validate: false) so the
earlier check stays authoritative.

* fix: normalize provider-conversion failures and pass accountId

- The convert_provider action only rescued ActiveRecord::RecordInvalid,
  so disconnect/teardown failures bubbled up as 500 with no stable
  payload. Catch StandardError, log the class + message, and return a
  422 with a generic user-facing message so the dashboard can surface
  the error consistently.
- Nested settings routes live under /accounts/:accountId, so the
  router push from Settings.vue must include accountId alongside
  inboxId. Mirrors how sibling pages navigate to settings_inbox_show.

* fix: report missing :provider as 400 and sync modal v-model

- The generic rescue StandardError on convert_provider was masking
  ActionController::ParameterMissing behind a misleading
  provider-conversion error message. Catch it explicitly before the
  generic rescue and return 400 with the parameter-missing message.
- ConvertInboxModal's closeModal now drives localShow to false so
  parents using v-model:show stay in sync on every close path,
  not only when the explicit onClose listener flips the flag.

* fix(whatsapp): serialize concurrent convert_provider calls with_lock

Without a per-record lock, two admin requests against the same inbox
could both pass the pre-flight validation, race the disconnect/save,
and then run setup_webhooks/sync_templates in arbitrary order, leaving
the persisted provider out of sync with the external configuration.

Wrap the whole convert flow in with_lock so the loser blocks until the
winner commits; the subsequent no-op guard then rejects a second
conversion request targeting the provider the first one just set.

* test: harden convert_provider policy + controller failure specs

- Pass accountId explicitly in InboxConvert redirects so the route
  navigation mirrors how Settings.vue reaches settings_inbox_convert.
- Add a spec that assigns the agent to the inbox and still expects 401,
  so a future regression in InboxPolicy#convert_provider? can no longer
  slip past on the show policy alone.
- Add a spec that stubs convert_provider! to raise StandardError and
  asserts the controller's generic-failure 422 payload, pinning the
  dashboard contract for provider-side failures.

* test: pin convert_provider success response payload

Parse the rendered body and assert provider + provider_config so the
spec catches regressions where the DB is updated correctly but the
serialized response drifts (dashboard store commits response.data).

* fix(whatsapp): reset teardown guard after pre-conversion webhook cleanup

teardown_webhooks memoizes @webhook_teardown_initiated = true to prevent
double execution during destroy. Calling it from convert_provider!
leaves that flag set, so a subsequent destroy! or follow-up conversion
on the same instance would skip webhook removal silently. Reset the
flag in an ensure block so the destroy-time guard stays scoped to
destroy only.

* fix: include accountId in post-conversion redirect params

* test: pin same-provider convert returns 422

* fix(whatsapp): reset template columns when post-conversion sync fails

* fix(convert): enforce provider allowlist in InboxConvert route guard

* test: broaden Cloud templates stub to match account-scoped path

* test(whatsapp): cover cloud to baileys conversion branch
2026-04-18 20:57:27 -03:00
..
captain feat: captain custom tools v1 (#13890) 2026-04-02 12:40:11 +05:30
channel feat: Voice Channel (#11602) 2025-12-19 12:41:33 -08:00
enterprise perf: update the logic to purchase credits (#12998) 2025-12-08 10:52:17 +05:30
helpCenter feat(help-center): enable drag-and-drop category reordering (#13706) 2026-03-05 12:53:38 +05:30
inbox feat(whatsapp): show contact typing and recording indicators via baileys presence (#264) 2026-04-13 11:38:11 -03:00
integrations feat: new Captain Editor (#13235) 2026-01-21 13:39:07 +05:30
specs Merge branch 'chatwoot/develop' into chore/merge-upstream-4.12.0 2026-03-20 00:27:45 -03:00
account.js feat: Reconnect logic (#9453) 2024-06-03 15:54:19 +05:30
accountActions.js feat: Create modal to merge two contacts (#2457) 2021-10-13 18:35:13 +05:30
agentBots.js feat: sign webhooks for API channel and agentbots (#13892) 2026-04-06 15:28:25 +05:30
agentCapacityPolicies.js feat: Agent capacity policy index page with CRUD actions (#12409) 2025-09-12 16:22:42 +05:30
agents.js feat: Adds bulk_invite api for onboarding view (#8931) 2024-02-16 17:01:27 +05:30
ApiClient.js feat: update tool-chain to latest (#7975) 2023-09-27 14:02:34 +05:30
assignableAgents.js chore: Add Assignable Agents API (#4722) 2022-05-23 19:24:07 +05:30
assignmentPolicies.js feat: Agent assignment policy index page with CRUD actions (#12373) 2025-09-10 12:07:21 +05:30
attributes.js feat: Render contact custom attributes in contact/conversation sidebar (#3310) 2021-11-11 15:23:33 +05:30
auditLogs.js feat: audit logs UI (#6803) 2023-04-17 19:11:05 +05:30
auth.js fix: Prevent display_name reset when updating password (#10374) 2025-06-11 19:05:30 -04:00
automation.js feat: add a common upload endpoint (#7806) 2023-08-31 10:36:02 +07:00
bulkActions.js feat: Add Bulk actions to conversations (#4647) 2022-06-03 11:12:22 +05:30
CacheEnabledApiClient.js fix: idb is not available in firefox private mode [CW-2217] (#7524) 2023-07-14 13:35:30 +05:30
campaigns.js feat: Add campaign (#2177) 2021-05-04 15:08:41 +05:30
cannedResponse.js Chore: Scope URLs with account_id (#601) 2020-03-09 23:27:10 +05:30
changelog.js Chore/merge upstream 4.8.0 (#150) 2025-11-19 16:25:58 -03:00
channels.js Chore: Add Facebook app set up documentation (#647) 2020-03-28 11:43:02 +05:30
companies.js Chore/merge upstream 4.8.0 (#150) 2025-11-19 16:25:58 -03:00
contactNotes.js feat: Add notes for Contacts (#3273) 2021-10-25 18:35:58 +05:30
contacts.js feat: compose form improvements (#13668) 2026-03-02 18:27:51 +05:30
conversations.js Chore: Scope URLs with account_id (#601) 2020-03-09 23:27:10 +05:30
csatReports.js fix: CSAT filter metrics rendering & conversation reports not working [CW-1840, CW-1818] (#7170) 2023-05-23 16:47:04 +05:30
customRole.js chore: Custom Roles to manage permissions [ UI ] (#9865) 2024-09-17 11:40:11 -07:00
customViews.js feat: Adds the ability to delete a segment (#3836) 2022-01-24 17:37:43 +05:30
dashboardApps.js feat: Allow users to create dashboard apps to give agents more context (#4761) 2022-06-01 11:13:10 +05:30
endPoints.js fix: Prevent display_name reset when updating password (#10374) 2025-06-11 19:05:30 -04:00
groupMembers.js feat: group conversations (#228) 2026-03-19 21:56:58 -03:00
inboxes.js feat(whatsapp): convert inbox between WhatsApp providers (#268) 2026-04-18 20:57:27 -03:00
inboxHealth.js feat(whatsapp): add webhook registration and status endpoints (#13551) 2026-03-16 12:48:16 +05:30
inboxMembers.js Chore: Inbox Members API improvements (#3008) 2021-09-14 11:55:02 +05:30
inboxSignatures.js feat: add per-inbox signature management (#226) 2026-02-26 19:53:03 -03:00
integrations.js feat(apps): Shopify Integration (#11101) 2025-03-19 15:37:55 -07:00
internalChatChannels.js feat(internal-chat): implement internal chat system for agents (#247) 2026-04-11 13:50:15 -03:00
internalChatDrafts.js feat(internal-chat): implement internal chat system for agents (#247) 2026-04-11 13:50:15 -03:00
internalChatMessages.js feat(internal-chat): implement internal chat system for agents (#247) 2026-04-11 13:50:15 -03:00
internalChatPolls.js feat(internal-chat): implement internal chat system for agents (#247) 2026-04-11 13:50:15 -03:00
labels.js feat: IndexedDB based caching for labels, inboxes and teams [CW-50] (#6710) 2023-03-27 12:16:25 +05:30
liveReports.js feat: Add live report for teams (#10849) 2025-03-12 16:03:09 -07:00
macros.js feat: Add API module and Vuex store for Macros (#5603) 2022-10-11 22:54:17 -07:00
mfa.js feat: Add the frontend support for MFA (#12372) 2025-09-18 21:16:06 +05:30
notifications.js fix: Inbox view Read/Snoozed display filters (#8907) 2024-02-17 13:59:25 +05:30
notificationSubscription.js Feature: Add web push notification permission in frontend (#766) 2020-05-06 00:10:56 +05:30
notion_auth.js feat: notion OAuth setup (#11765) 2025-06-26 19:16:06 +05:30
recurringScheduledMessages.js feat: schedule messages recurrence (#240) 2026-03-19 22:51:14 -03:00
reports.js feat: Add conversations summary CSV export (#13110) 2026-01-13 12:30:26 +04:00
samlSettings.js feat: SAML UI [CW-2958] (#12345) 2025-09-15 19:33:54 +05:30
scheduledMessages.js fix(schedule): enhance attachment handling in scheduled message modal (#239) 2026-03-19 22:45:21 -03:00
search.js feat: Advanced Search Backend (#12917) 2026-01-07 15:30:49 +05:30
sla.js feat(ee): Add SLA management UI (#8777) 2024-02-20 23:03:22 -08:00
slaReports.js fix: Add more filters for SLA download reports (#9231) 2024-04-16 09:00:52 +05:30
summaryReports.js feat: label reports overview (#11194) 2025-06-11 14:35:46 +05:30
teams.js feat: IndexedDB based caching for labels, inboxes and teams [CW-50] (#6710) 2023-03-27 12:16:25 +05:30
userNotificationSettings.js Chore: Scope URLs with account_id (#601) 2020-03-09 23:27:10 +05:30
webhooks.js Chore: Scope URLs with account_id (#601) 2020-03-09 23:27:10 +05:30
yearInReview.js feat(ce): Add Year in review feature (#13078) 2025-12-15 17:24:45 -08:00