db9b451eeb
236 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
72c9821270
|
feat(whatsapp): add emoji reactions UI (#276)
* feat(whatsapp): add emoji reactions UI
Adds end-to-end agent UI for emoji reactions on WhatsApp inboxes
(Cloud API, Baileys, Z-API). Reactions arrive as Messages with
is_reaction=true; this PR exposes them in the bubble UI and lets
agents react with toggle/replace/remove semantics.
- Add POST /reactions endpoint with toggle/replace logic that handles
multi-device echoes from the same connected number
- Add Channel::Whatsapp#supports_reactions? capability
- Add Message.hide_removed_reactions scope and use it in conversation
card preview / last_non_activity_message
- Enrich last_non_activity_message with in_reply_to_snippet for
reaction previews in chat list
- Frontend: hover EmojiReactionPicker (8 quick + full picker) with
alignment-aware positioning, single ReactionDisplay chip aggregating
emojis with total count, conversation card preview shows "Você
reagiu" for own/multi-device echoes
* fix: address CodeRabbit review feedback
- MessagePreview: render "Você" for outgoing reaction echoes that have no
sender (multi-device echoes from the connected number)
- MessagesView#findCurrentUserReaction: prefer active reactions over
deleted rows so a stale deleted echo cannot hijack the toggle target
- conversationHelper: drop removed reactions up-front so the activity
fallback never returns null when older non-removed messages exist
- imap_import rake: wrap IMAP work in begin/ensure so the session is
closed even when uid_search/scan_new_email_uids raises
- ReactionDisplay: include reaction.id in the user row so v-for keys
stay stable across re-renders
* fix: address CodeRabbit round 2 feedback
- enterprise Message override of mark_pending_conversation_as_open_for_human_response
now early-returns on reaction? so reactions can no longer auto-open Captain-pending
conversations (matches the OSS guard)
- whatsapp incoming reaction-removal handlers (Cloud/Baileys/Z-API) look up the
reaction Message globally by sender instead of through the inbound conversation
scope, then operate on existing.conversation; otherwise an old/resolved thread
could be silently no-op'd while the inbound flow created a stray empty thread
- EmojiReactionPicker: localize quick-emoji tooltip labels via i18n keys
- Message.vue: track pendingTimeouts and clear them on unmount so the cooldown
setTimeout no longer touches state after teardown
- toggleMessageReaction action returns the API promise so callers can reconcile
if the cable echo is delayed
* fix: address CodeRabbit round 3 feedback
- MessageFinder#page_window: pluck the 20-row window IDs before taking .min
so the latest page honors PAGE_LIMIT (ActiveRecord's .minimum(:id) ignores
.limit and aggregates over the whole relation)
- ReactionsController#current_user_reaction: rank active reactions ahead of
deleted rows (same invariant as the frontend lookup) so a stale deleted
echo can no longer hijack the toggle target and resurrect itself
- Whatsapp incoming handlers (Cloud, Baileys individual & group, Z-API) now
branch on reaction_removal? BEFORE set_conversation / find_or_create_group_
conversation, so a blank reaction-removal webhook can never open or create
a stray thread just to no-op
- Message#reaction?: strict-boolean cast (via ActiveModel::Type::Boolean)
so a stored string "false" no longer leaks through .present? as truthy
* fix: address CodeRabbit round 4 feedback
- MessageList: anchor unread divider on the filtered visibleMessages
(firstUnreadId can land on a reaction that's filtered out, otherwise
the separator silently disappears)
- ReactionDisplay: render the removable user row as a real <button> when
it's the current user's reaction so keyboard users can focus/activate it
- MessagesView#findCurrentUserReaction: read sender_type from m.sender_type
OR m.sender?.type so REST-loaded messages match the same row instead of
spawning a duplicate optimistic reaction
- Whatsapp incoming reaction-removal lookup (Cloud, Baileys, Z-API): pick
the newest active row first and only fall back to the newest deleted row
when no active reaction exists, mirroring the controller invariant
- CardMessagePreview: use MESSAGE_TYPE.OUTGOING constant in place of the
literal 1 for the multi-device reaction echo check
* fix: address CodeRabbit round 5 feedback
- ReactionsController#ensure_target_is_reactable: reject activity,
template, failed, is_unsupported and missing-source_id targets so the
API mirrors the client toolbar gate and refuses reactions that could
never land on WhatsApp
- MessageList reaction aggregator: treat "agent reacted via Chatwoot"
and "agent reacted via the connected phone" as the same self bucket
so the chip no longer double-counts the current user when both shapes
coexist for one target
- internalChat ReactionDisplay: render the removable user row as a real
<button> so keyboard users can focus and trigger removal (mirrors the
fix already applied to components-next/message/ReactionDisplay)
- EventDataPresenter#push_last_non_activity_message: reorder
created_at: :desc before .first so the cable snapshot publishes the
latest preview instead of the oldest row
- Z-API mark_existing_reaction_as_removed: drop the blanket
`return unless incoming_message?` and route the lookup by direction
(contact sender for incoming removals, senderless outgoing row for
multi-device removals from the connected phone). Chatwoot-originated
echoes stay idempotent because the active-first guard finds nothing
once the controller has flipped deleted=true locally
- spec: assert reaction removal does not change messages.count on the
in-place Cloud path
* fix: address CodeRabbit round 6 feedback
- ReactionsController: validate the emoji payload is a single grapheme
cluster containing a Unicode Emoji codepoint (not just <=32 bytes), so
arbitrary short strings like "ok" or "123" can no longer be persisted
as a reaction or enqueued as a WhatsApp reaction send
- target_unreactable_error: add the content_attributes['deleted'] guard
to mirror the frontend picker gate on deleted messages
- IncomingMessageBaseService: move contact_processable? AFTER the
reaction_removal? early-return so a blocked contact's removal webhook
can still reconcile an existing reaction row instead of leaving a
stale chip/preview
- imap_import rake: add safe_close_imap(imap) that falls back to
disconnect when logout raises Net::IMAP::Error, mirroring
terminate_imap_connection in BaseFetchEmailService, and replace the
three ensure-block imap&.logout sites with it
* fix: address CodeRabbit round 7 feedback
- CardMessagePreview: resolve `lastNonActivityMessage` against the live
`messages` array by id before rendering, so the chat-card preview
picks up the freshest copy instead of the (possibly stale) snapshot
that was mutated in place by a reaction toggle / multi-device echo
- Message + ReactionDisplay: thread an `inboxSupportsReactions` →
`read-only` prop into the chip so non-supported channels (eg.
360Dialog) render historical reactions without a clickable
toggle/remove path that would only hit a 422
- conversations/index.js: replace the truthiness `&&` guard around the
out-of-order MESSAGE_UPDATED check with `Number.isFinite` parsing so
a malformed/missing `updated_at` is treated as stale instead of
silently overwriting a fresher local row
- Baileys mark_existing_reaction_as_removed: drop the blanket
`return unless incoming?` and split the lookup by direction
(sender for incoming, sender-less outgoing for multi-device removals)
to mirror the Z-API/Cloud handlers
- Whatsapp reaction-removal lookup (Cloud, Baileys, Z-API): drop the
fallback to the newest deleted row so a Chatwoot-originated removal
echo no-ops cleanly instead of bumping `updated_at` and dispatching
another `conversation.updated`
- conversation jbuilder: explicit `reorder(created_at: :desc)` on
`last_non_activity_message` so REST and cable both serialize the
same most-recent preview
* fix: address CodeRabbit round 8 feedback
- ReactionsController#current_user_reaction: also match on
content_attributes.in_reply_to_external_id = @target_message.source_id
(via OR with the existing in_reply_to check), so WhatsApp-echoed
reactions persisted by the incoming handlers — where in_reply_to could
be blanked if the target wasn't resolvable at save time — are found and
toggled instead of stacking a duplicate self-reaction
- Mirror the same defensive OR check in the frontend
MessagesView#findCurrentUserReaction, and thread the target's
source_id through the toggleReaction event from Message.vue so the
lookup sees it
* fix: address CodeRabbit round 9 feedback
- emoji_payload_valid?: tighten the final property check from \p{Emoji}
to \p{Extended_Pictographic} so plain "1", "#", "*" (which Unicode
tags as Emoji because they're valid keycap bases) are rejected as
reaction payloads
- EmojiReactionPicker: mirror the translated `title` into `aria-label`
on the icon-only smile-plus / plus buttons so screen readers announce
a meaningful action name
- internalChat ReactionDisplay: close the popover when the post-removal
state would leave ≤1 reactions, so a singleton-user popover never
lingers after removing one of a pair
- EventDataPresenter + conversation jbuilder: strip HTML before
truncating `in_reply_to_snippet` so reactions to email/HTML bubbles
don't surface literal "<p>..." markup in the chat-list preview
* fix: address CodeRabbit round 10 feedback
- MessageList#reactionsByMessageId: break createdAt ties with `<=` so a
later iteration wins on second-resolution tie; two toggles in the same
second no longer leave the chip pointing at a stale row
- MessagePreview: require a non-empty `message.attachments` array (via
`?.length`) before taking the attachment preview branch, so a removed
reaction with `[]` no longer renders the attachment placeholder
- MessagesView#findCurrentUserReaction: replace the sort-based pick with
a reduce that deterministically takes the last element on tie, so a
fast toggle can't hit a stale/deleted row with the same created_at
- Baileys group handler: guard against `@sender_contact.blank?` before
dispatching mark_existing_reaction_as_removed, otherwise a nil sender
would fall into the senderless-outgoing branch and match the wrong row
- WhatsApp reaction-removal lookups (Cloud, Baileys, Z-API): scope the
base query to `inbox_id: inbox.id` so a colliding WhatsApp message id
across inboxes can never mutate a reaction row from another inbox
* fix: address CodeRabbit round 11 feedback
- ReactionsController#emoji_payload_valid?: broaden the final property
check to accept flag and keycap emoji. `\p{Extended_Pictographic}` by
itself is per-codepoint, so 🇧🇷 (two Regional Indicators) and 1️⃣
(digit + VS16 + U+20E3) failed validation. Allow any grapheme cluster
that contains at least one pictographic codepoint, a Regional
Indicator, or the combining keycap, while still rejecting plain
ASCII like "ok", "1", "#"
- Message.vue#canShowReactionToolbar: hide the picker when the target
has no provider source_id, mirroring the server guard in
ReactionsController#target_unreactable_error instead of letting the
click fall through to a 422
- MessageList#reactionsByMessageId: fall back to a sourceId → id
lookup when a reaction only carries `inReplyToExternalId` (WhatsApp
echo / phone-originated), so its chip still renders against the
target bubble after reload
- getLastMessage: merge the fresher store fields onto the API
snapshot instead of replacing it, so jbuilder-only enrichments like
`in_reply_to_snippet` survive the store refresh
* fix(reactions): preserve API fields on card preview and expose a11y state on quick picker
* fix(reactions): consistent originalId resolution, natural PT-BR snippet phrasing, accurate outgoing-echo spec
* fix(reactions): reject requests missing emoji param and align zapi outgoing-echo spec fixture
* fix(reactions): activity preview fallback, camelCase event listener, EN snippet quoting, fromMe group removals, REST chat-only preview
* fix(reactions): reject non-string emoji, scope page reactions to window, exempt reactions from human_response, add cloud multi-device removal
* test(message): isolate hide_removed_reactions deleted-branch from blank-content branch
* fix(reactions): coerce in_reply_to_snippet to plain String
strip_tags returns an ActiveSupport::SafeBuffer; truncate preserves the
class. When this snippet flowed into ActionCableBroadcastJob via the
CONVERSATION_UPDATED dispatch, Sidekiq's strict-args check rejected the
non-native JSON type, raising synchronously through the dispatcher and
turning the reactions controller response into a 500 even though the row
had already persisted. The UI then surfaced the generic 'failed to update
reaction' toast despite the chip rendering correctly.
Wrap with String.new so the broadcast payload contains plain Strings.
* fix(reactions): don't auto-scroll to bottom on reaction add
ADD_MESSAGE emits SCROLL_TO_MESSAGE for every new push, which makes
sense for regular outgoing messages (the user just hit send and wants
to see it). Reactions render as chips on the parent bubble, so the
auto-scroll yanked the agent away from whichever older message they
were reacting to. Skip the emit when the incoming message is flagged
as a reaction.
* fix(reactions): skip scroll on conversation-only updates triggered by reactions
The reactions controller dispatches CONVERSATION_UPDATED so the chat list
preview can refresh in place. UPDATE_CONVERSATION mutation always emitted
SCROLL_TO_MESSAGE for the open conversation, so every toggle yanked the
viewport back to the bottom even after the previous fix in ADD_MESSAGE.
When the refreshed preview row is itself a reaction the update is
preview-only and the scroll is unwanted; for a regular incoming message
the latest non-activity row is the message itself, which still triggers
the scroll as before.
* fix(reactions): anchor compact picker to button side instead of centering
The compact picker was centered on the smile button, so half its width
always extended toward the bubble side and overflowed past the chat edge
on short messages. Anchor it to the button's outer side and nudge 4px
toward the bubble so it lines up with the trigger.
* test(reactions): regression coverage for safebuffer + scroll skip
The previous CodeRabbit rounds shipped three bugs the existing specs
didn't catch: a SafeBuffer return from `strip_tags` that 500'd the
reactions controller via Sidekiq strict-args, and two SCROLL_TO_MESSAGE
emits (one per mutation) that yanked the open conversation to the
bottom on every emoji toggle. Lock all three behaviors.
Also tighten the spec policy in AGENTS.md so new features default to
having specs instead of skipping them.
* test(baileys): align send_message_body helper with id:updated_at format
The reactions PR switched chatwootMessageId to "<id>:<updated_at>" so
toggle/replace cycles get a fresh idempotency key against baileys-api,
but the shared spec helper still merged the bare integer id. 18 baileys
provider specs were silently broken on CI as a result.
* fix(reactions): skip set_contact for unknown reaction-removal webhooks
Reaction-removal cloud webhooks were unconditionally creating a contact
even when the sender was unknown and there was nothing to remove,
because set_contact ran before the reaction_removal? short-circuit
(needed earlier so blocked-contact reconciliation works). Add a
sender-agnostic existence check on the inbox/in_reply_to scope and bail
out before set_contact when no candidate row exists.
Also realign two specs that were not updated when the chatwootMessageId
format gained an `:updated_at` suffix and when zapi reaction-removal
short-circuited instead of creating a Message.
* test(conversation): include last_non_activity_message in push_data fixture
Reactions PR added last_non_activity_message to the push_data payload
but conversation_spec's exact-match expectation wasn't updated, so the
sharded CI shard that landed on this file flipped red.
|
||
|
|
112385fd9e |
Merge branch 'main' into chore/merge-4.13.0
Resolves 26 conflicts via manual review. Key decisions: - signature: kept fork's send-time architecture (PR #79), discarded upstream's editor-manipulation functions - WhatsApp incoming: combined fork's two-layer locking (source_id + contact phone) with upstream's blocked-contact drop. Fixed pre-existing regression where echoes were silently dropped - InstallationConfig: upstream's simplified coder (validated against legacy YAML-in-jsonb data) - schema.rb: regenerated, stripped kanban tables from other branches, restored f_unaccent SQL function |
||
|
|
b96bf41234
|
chore: Enable Participating tab for conversations (#11714)
## Summary This PR enables the **Participating** conversation view in the main sidebar and keeps the behavior aligned with existing conversation views. ## What changed - Added **Participating** under Conversations in the new sidebar. - Added a guard in conversation realtime `addConversation` flow so generic `conversation.created` events are not injected while the user is on Participating view. - Added participating route mapping in conversation-list redirect helper so list redirects resolve correctly to `/participating/conversations`. ## Scope notes - Kept changes minimal and consistent with current `develop` behavior. - No additional update-event filtering was added beyond what existing views already do. --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
8fcef79847 | Merge branch 'chatwoot/develop' into chore/merge-upstream-4.12.0 | ||
|
|
939471cb3b
|
fix: Prevent duplicate conversations in conversation list (#13713)
Agents using API channel inboxes (e.g., WhatsApp Automate) reported seeing the same conversation appear twice in their conversation list — one showing the last message preview and the other showing "No Messages". Backend investigation confirmed no duplicate conversations exist in the database, making this purely a frontend issue. The root cause is a race condition in WebSocket event delivery. When a conversation is created via the API with auto-assignment, the backend enqueues multiple ActionCable broadcast jobs (`conversation.created`, `assignee.changed`, `team.changed`) within milliseconds of each other. In production with multi-threaded Sidekiq workers, these events can arrive at the frontend out of order. If `assignee.changed` arrives before `conversation.created`, the `UPDATE_CONVERSATION` mutation pushes the conversation into the store (since it doesn't exist yet), and then `ADD_CONVERSATION` blindly pushes it again — resulting in a duplicate entry. The fix adds a uniqueness check in the `ADD_CONVERSATION` mutation to skip the push if a conversation with the same ID already exists in the store, matching the dedup pattern already used by `SET_ALL_CONVERSATION`. |
||
|
|
9a4c5058f3 | Merge branch 'main' into chore/merge-upstream-4.11.0 | ||
|
|
e65ea24360
|
fix: Wrong assignee displayed after switching conversations (#13501) | ||
|
|
b252656984
|
fix: Prevent race condition in conversation dataFetched flag (#13492)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
0e30e3c00a
|
fix: add loading and silent retry to summary reports (#13455)
For large accounts, summary report queries can take several seconds to complete, often times hitting the 15-second production request timeout. The existing implementation silently swallows these failures and provides no feedback during loading. Users see stale data with no indication that a fetch is in progress, and if they interact with filters while a request is in flight, they trigger race conditions that can result in mismatched data being displayed. This is a UX-level fix for what is fundamentally a performance problem. While the underlying query performance is addressed separately, users need proper feedback either way ## Approach The PR adds three things: 1. A loading overlay on the table, to provide feedback on loading state 2. Disabled filter inputs during loading so that the user does not request new information that can cause race conditions in updating the store 3. Silent retry before showing an error. The retry exists because these queries often succeed on the second attempt—likely due to database query caching. Rather than immediately showing an error and forcing the user to manually retry, we do it automatically. If the second attempt also fails, we show a toast so the user knows something went wrong. The store previously caught and discarded errors entirely. It now rethrows them after resetting the loading flag, allowing components to handle failures as they see fit. ### Previews #### Double Retry and Error https://github.com/user-attachments/assets/c189b173-8017-44b7-9493-417d65582c95 #### Loading State https://github.com/user-attachments/assets/9f899c20-fbad-469b-93cc-f0d05d0853b0 --------- Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
f9d1146cb0
|
feat: mensagens agendadas (#198)
* feat: Adds model for scheduling messages * feat: Implement scheduled message handling and processing jobs * feat: Add ScheduledMessagesController and associated specs for managing scheduled messages * refactor: Simplify scheduled message job specs and improve metadata handling * feat: Add ScheduledMessagePolicy for managing access to scheduled messages * feat: Add routes for managing scheduled messages * feat: Add scheduled message event handling and broadcasting * feat: Add JSON views for scheduled messages creation, destruction, updating, and indexing * feat: Update scheduled message status and dispatch update event after message creation * feat: Ensure scheduled message updates trigger dispatch event * feat: Add mutation types for managing scheduled messages * feat: Add additionalAttributes prop to Message component and provider * feat: Implement scheduled message handling in ActionCable and Vuex store * feat: Add unit tests for scheduled messages actions and mutations * feat: implement scheduled messages functionality - Added support for scheduling messages in the conversation dashboard. - Introduced new components: ScheduledMessageModal and ScheduledMessages for managing scheduled messages. - Enhanced ReplyBottomPanel to include scheduling options. - Updated Base.vue to handle scheduled message styling. - Integrated Vuex store module for managing scheduled messages state. - Added necessary translations for scheduled messages in English and Portuguese. * feat: add pagination to scheduled messages index and update tests accordingly * chore: update scheduled messages specs for future time validation and response status * chore: enhance scheduled messages API with pagination and add skeleton loader component * feat: add create_scheduled_message action to automation rule attributes * feat: implement create_scheduled_message action and enhance attachment handling * feat: add scheduled message functionality with UI components and localization * test: enhance scheduledMessages mutations tests with meta handling and structure * chore: update label to display file name upon successful upload in AutomationFileInput component * feat: add initialAttachment prop to ScheduledMessageModal and update ReplyBox to pass attachment * chore: prepend_mod_with to ScheduledMessagesController for better module handling * fix: attachment visibility in ScheduledMessageItem component * chore: enhance ScheduledMessage model with validations and reduce controller load * refactor: simplify ScheduledMessagesAPI methods by removing unnecessary instance variable * chore: update event emission for scheduled message creation in ReplyBox and ScheduledMessageModal * refactor: update status configuration to use label keys * chore: update date formatting in ScheduledMessageItem component * refactor: collapse logic to checkOverflow and update related functionality * chore: add author indication for current user in scheduled messages * chore: enhance scheduled message metadata with author information and localization * fix: send message shortcut * chore: handle errors in scheduled message submission * chore: update scheduled message modal to use combined date and time input * chore: refactor scheduled messages handling to remove pagination and update related tests * fix: ensure scheduled messages update status and dispatch on failure * fix: update scheduled message due date logic and simplify sending checks * refactor: rename build_message method for send_message * fix: update scheduled message creation time and improve test reliability * chore: ignore unnecessary check * chore: add scheduled message metadata handling in message builder, add scheduled message factorie and update specs * refactor: use scheduled message factorie creation in specs * chore: streamline error handling in scheduled message job and remove dispatch logic * fix: change scheduled_messages association to destroy dependent records * refactor: remove unused attributes from scheduled message payload builder * chore: update scheduled message retrieval to use conversation association * chore: correct cron format for scheduled messages job * chore: remove migration for author_type in scheduled_messages * feat: enhance scheduled messages management with delete confirmation and error handling * chore: set cron poll interval to 10 seconds for improved scheduling precision * feat: include additional_attributes in message JSON response * feat: enhance scheduled message validation and localization support * chore: update scheduled message display * Merge branch 'main' into Cayo-Oliveira/CU-86aenh268/Mensagens-agendadas * feat: add scheduled message indicators and validation for message length * fix: remove unnecessary condition from line-clamp class binding * feat: update scheduled messages localization and enhance content validation * feat: update scheduled messages order, enhance scheduledAt computation, and add message association * fix: reorder condition for Facebook channel message length computation * fix: change detection for attachments in scheduled messages * fix: remove unnecessary colon from close-on-backdrop-click prop in ScheduledMessageModal * chore: add error handling for scheduled message deletion and update localization for delete failure * fix: enforce minimum delay of 1 minute for scheduled messages and update validation * fix: remove unused private property and improve locale formatting for scheduled messages * fix: adjust positioning of DropdownBody in ReplyBottomPanel and clean up schema foreign keys * docs: add scheduled messages management APIs and payload definitions --------- Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com> |
||
|
|
04b2901e1f
|
feat: Conversation workflows(EE) (#13040)
We are expanding Chatwoot’s automation capabilities by introducing **Conversation Workflows**, a dedicated section in settings where teams can configure rules that govern how conversations are closed and what information agents must fill before resolving. This feature helps teams enforce data consistency, collect structured resolution information, and ensure downstream reporting is accurate. Instead of having auto‑resolution buried inside Account Settings, we introduced a new sidebar item: - Auto‑resolve conversations (existing behaviour) - Required attributes on resolution (new) This groups all conversation‑closing logic into a single place. #### Required Attributes on Resolve Admins can now pick which custom conversation attributes must be filled before an agent can resolve a conversation. **How it works** - Admin selects one or more attributes from the list of existing conversation level custom attributes. - These selected attributes become mandatory during resolution. - List all the attributes configured via Required Attributes (Text, Number, Link, Date, List, Checkbox) - When an agent clicks Resolve Conversation: If attributes already have values → the conversation resolves normally. If attributes are missing → a modal appears prompting the agent to fill them. <img width="1554" height="1282" alt="CleanShot 2025-12-10 at 11 42 23@2x" src="https://github.com/user-attachments/assets/4cd5d6e1-abe8-4999-accd-d4a08913b373" /> #### Custom Attributes Integration On the Custom Attributes page, we will surfaced indicators showing how each attribute is being used. Each attribute will show badges such as: - Resolution → used in the required‑on‑resolve workflow - Pre‑chat form → already existing <img width="2390" height="1822" alt="CleanShot 2025-12-10 at 11 43 42@2x" src="https://github.com/user-attachments/assets/b92a6eb7-7f6c-40e6-bf23-6a5310f2d9c5" /> #### Admin Flow - Navigate to Settings → Conversation Workflows. - Under Required attributes on resolve, click Add Required Attribute. - Pick from the dropdown list of conversation attributes. - Save changes. Agents will now be prompted automatically whenever they resolve. <img width="2434" height="872" alt="CleanShot 2025-12-10 at 11 44 42@2x" src="https://github.com/user-attachments/assets/632fc0e5-767c-4a1c-8cf4-ffe3d058d319" /> #### NOTES - The Required Attributes on Resolve modal should only appear when values are missing. - Required attributes must block the resolution action until satisfied. - Bulk‑resolve actions should follow the same rules — any conversation missing attributes cannot be bulk‑resolved, rest will be resolved, show a notification that the resolution cannot be done. - API resolution does not respect the attributes. --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Pranav <pranav@chatwoot.com> |
||
|
|
6ab1898992 | Merge branch 'main' into chore/merge-upstream-4.10 | ||
|
|
a8b302d4cd
|
feat(ee): Review Notes for CSAT Reports (#13289)
CSAT scores are helpful, but on their own they rarely tell the full story. A drop in rating can come from delayed timelines, unclear expectations, or simple misunderstandings, even when the issue itself was handled correctly. Review Notes for CSAT let admins/report manager roles add internal-only context next to each CSAT response. This makes it easier to interpret scores properly and focus on patterns and root causes, not just numbers. <img width="2170" height="1680" alt="image" src="https://github.com/user-attachments/assets/56df7fab-d0a7-4a94-95b9-e4c459ad33d5" /> ### Why this matters * Capture the real context behind individual CSAT ratings * Clarify whether a low score points to a genuine service issue or a process gap * Spot recurring themes across conversations and teams * Make CSAT reviews more useful for leadership reviews and retrospectives ### How Review Notes work **View CSAT responses** Open the CSAT report to see overall metrics, rating distribution, and individual responses. **Add a Review Note** For any CSAT entry, managers can add a Review Note directly below the customer’s feedback. **Document internal insights** Use Review Notes to capture things like: * Why a score was lower or higher than expected * Patterns you are seeing across similar cases * Observations around communication, timelines, or customer expectations Review Notes are visible only to administrators and people with report access only. We may expand visibility to agents in the future based on feedback. However, customers never see them. Each note clearly shows who added it and when, making it easy to review context and changes over time. |
||
|
|
821a5b85c2
|
feat: Add conversations summary CSV export (#13110)
# Pull Request Template ## Description This PR adds support for exporting conversation summary reports as CSV. Previously, the Conversations report incorrectly showed an option to download agent reports; this has now been fixed to export conversation-level data instead. Fixes https://linear.app/chatwoot/issue/CW-6176/conversation-reports-export-button-exports-agent-reports-instead ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screenshot <img width="1859" height="1154" alt="image" src="https://github.com/user-attachments/assets/419d26f4-fda9-4782-aea6-55ffad0c37ab" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
59cbf57e20
|
feat: Advanced Search Backend (#12917)
## Description
Implements comprehensive search functionality with advanced filtering
capabilities for Chatwoot (Linear: CW-5956).
This PR adds:
1. **Time-based filtering** for contacts and conversations (SQL-based
search)
2. **Advanced message search** with multiple filters
(OpenSearch/Elasticsearch-based)
- **`from` filter**: Filter messages by sender (format: `contact:42` or
`agent:5`)
- **`inbox_id` filter**: Filter messages by specific inbox
- **Time range filters**: Filter messages using `since` and `until`
parameters (Unix timestamps in seconds)
- **90-day limit enforcement**: Automatically limits searches to the
last 90 days to prevent performance issues
The implementation extends the existing `Enterprise::SearchService`
module for advanced features and adds time filtering to the base
`SearchService` for SQL-based searches.
## API Documentation
### Base URL
All search endpoints follow this pattern:
```
GET /api/v1/accounts/{account_id}/search/{resource}
```
### Authentication
All requests require authentication headers:
```
api_access_token: YOUR_ACCESS_TOKEN
```
---
## 1. Search All Resources
**Endpoint:** `GET /api/v1/accounts/{account_id}/search`
Returns results from all searchable resources (contacts, conversations,
messages, articles).
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `since` | integer | Unix timestamp (contacts/conversations only) | No
|
| `until` | integer | Unix timestamp (contacts/conversations only) | No
|
### Example Request
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search?q=customer" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"contacts": [...],
"conversations": [...],
"messages": [...],
"articles": [...]
}
}
```
---
## 2. Search Contacts
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/contacts`
Search contacts by name, email, phone number, or identifier with
optional time filtering.
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `since` | integer | Unix timestamp - filter by last_activity_at | No |
| `until` | integer | Unix timestamp - filter by last_activity_at | No |
### Example Requests
**Basic search:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/contacts?q=john" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search contacts active in the last 7 days:**
```bash
SINCE=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/contacts?q=john&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search contacts active between 30 and 7 days ago:**
```bash
SINCE=$(date -v-30d +%s)
UNTIL=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/contacts?q=john&since=${SINCE}&until=${UNTIL}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"contacts": [
{
"id": 42,
"email": "john@example.com",
"name": "John Doe",
"phone_number": "+1234567890",
"identifier": "user_123",
"additional_attributes": {},
"created_at": 1701234567
}
]
}
}
```
---
## 3. Search Conversations
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/conversations`
Search conversations by display ID, contact name, email, phone number,
or identifier with optional time filtering.
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `since` | integer | Unix timestamp - filter by last_activity_at | No |
| `until` | integer | Unix timestamp - filter by last_activity_at | No |
### Example Requests
**Basic search:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/conversations?q=billing" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search conversations active in the last 24 hours:**
```bash
SINCE=$(date -v-1d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/conversations?q=billing&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search conversations from last month:**
```bash
SINCE=$(date -v-30d +%s)
UNTIL=$(date +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/conversations?q=billing&since=${SINCE}&until=${UNTIL}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"conversations": [
{
"id": 123,
"display_id": 45,
"inbox_id": 1,
"status": "open",
"messages": [...],
"meta": {...}
}
]
}
}
```
---
## 4. Search Messages (Advanced)
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/messages`
Advanced message search with multiple filters powered by
OpenSearch/Elasticsearch.
### Prerequisites
- OpenSearch/Elasticsearch must be running (`OPENSEARCH_URL` env var
configured)
- Account must have `advanced_search` feature flag enabled
- Messages must be indexed in OpenSearch
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `from` | string | Filter by sender: `contact:{id}` or `agent:{id}` |
No |
| `inbox_id` | integer | Filter by specific inbox ID | No |
| `since` | integer | Unix timestamp - searches from this time (max 90
days ago) | No |
| `until` | integer | Unix timestamp - searches until this time | No |
### Important Notes
- **90-Day Limit**: If `since` is not provided, searches default to the
last 90 days
- If `since` exceeds 90 days, returns `422` error: "Search is limited to
the last 90 days"
- All time filters use message `created_at` timestamp
### Example Requests
**Basic message search:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages from a specific contact:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&from=contact:42" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages from a specific agent:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&from=agent:5" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages in a specific inbox:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&inbox_id=3" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages from the last 7 days:**
```bash
SINCE=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages between specific dates:**
```bash
SINCE=$(date -v-30d +%s)
UNTIL=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&since=${SINCE}&until=${UNTIL}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Combine all filters:**
```bash
SINCE=$(date -v-14d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&from=contact:42&inbox_id=3&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Attempt to search beyond 90 days (returns error):**
```bash
SINCE=$(date -v-120d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response (Success)
```json
{
"payload": {
"messages": [
{
"id": 789,
"content": "I need a refund for my purchase",
"message_type": "incoming",
"created_at": 1701234567,
"conversation_id": 123,
"inbox_id": 3,
"sender": {
"id": 42,
"type": "contact"
}
}
]
}
}
```
### Example Response (90-day limit exceeded)
```json
{
"error": "Search is limited to the last 90 days"
}
```
**Status Code:** `422 Unprocessable Entity`
---
## 5. Search Articles
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/articles`
Search help center articles by title or content.
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
### Example Request
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/articles?q=installation" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"articles": [
{
"id": 456,
"title": "Installation Guide",
"slug": "installation-guide",
"portal_slug": "help",
"account_id": 1,
"category_name": "Getting Started",
"status": "published",
"updated_at": 1701234567
}
]
}
}
```
---
## Technical Implementation
### SQL-Based Search (Contacts, Conversations, Articles)
- Uses PostgreSQL `ILIKE` queries by default
- Optional GIN index support via `search_with_gin` feature flag for
better performance
- Time filtering uses `last_activity_at` for contacts/conversations
- Returns paginated results (15 per page)
### Advanced Search (Messages)
- Powered by OpenSearch/Elasticsearch via Searchkick gem
- Requires `OPENSEARCH_URL` environment variable
- Requires `advanced_search` account feature flag
- Enforces 90-day lookback limit via
`Limits::MESSAGE_SEARCH_TIME_RANGE_LIMIT_DAYS`
- Validates inbox access permissions before filtering
- Returns paginated results (15 per page)
---
## Type of change
- [x] New feature (non-breaking change which adds functionality)
- [x] Enhancement (improves existing functionality)
---
## How Has This Been Tested?
### Unit Tests
- **Contact Search Tests**: 3 new test cases for time filtering
(`since`, `until`, combined)
- **Conversation Search Tests**: 3 new test cases for time filtering
- **Message Search Tests**: 10+ test cases covering:
- Individual filters (`from`, `inbox_id`, time range)
- Combined filters
- Permission validation for inbox access
- Feature flag checks
- 90-day limit enforcement
- Error handling for exceeded time limits
### Test Commands
```bash
# Run all search controller tests
bundle exec rspec spec/controllers/api/v1/accounts/search_controller_spec.rb
# Run search service tests (includes enterprise specs)
bundle exec rspec spec/services/search_service_spec.rb
```
### Manual Testing Setup
A rake task is provided to create 50,000 test messages across multiple
inboxes:
```bash
# 1. Create test data
bundle exec rake search:setup_test_data
# 2. Start OpenSearch
mise elasticsearch-start
# 3. Reindex messages
rails runner "Message.search_index.import Message.all"
# 4. Enable feature flag
rails runner "Account.first.enable_features('advanced_search')"
# 5. Test via API or Rails console
```
---
## Checklist
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation (this PR
description)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---
## Additional Notes
### Requirements
- **OpenSearch/Elasticsearch**: Required for advanced message search
- Set `OPENSEARCH_URL` environment variable
- Example: `export OPENSEARCH_URL=http://localhost:9200`
- **Feature Flags**:
- `advanced_search`: Account-level flag for message advanced search
- `search_with_gin` (optional): Account-level flag for GIN-based SQL
search
### Performance Considerations
- 90-day limit prevents expensive long-range queries on large datasets
- GIN indexes recommended for high-volume search on SQL-based resources
- OpenSearch/Elasticsearch provides faster full-text search for messages
### Breaking Changes
- None. All new parameters are optional and backward compatible.
### Frontend Integration
- Frontend PR tracking advanced search UI will consume these endpoints
- Time range pickers should convert JavaScript `Date` to Unix timestamps
(seconds)
- Date conversion: `Math.floor(date.getTime() / 1000)`
### Error Handling
- Invalid `from` parameter format is silently ignored (filter not
applied)
- Time range exceeding 90 days returns `422` with error message
- Missing `q` parameter returns `422` (existing behavior)
- Unauthorized inbox access is filtered out (no error, just excluded
from results)
---------
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
|
||
|
|
549214e96d | Merge branch main into chore/merge-upstream | ||
|
|
ca5e112a8c
|
feat: TikTok channel (#12741)
fixes: #11834 This pull request introduces TikTok channel integration, enabling users to connect and manage TikTok business accounts similarly to other supported social channels. The changes span backend API endpoints, authentication helpers, webhook handling, configuration, and frontend components to support TikTok as a first-class channel. **Key Notes** * This integration is only compatible with TikTok Business Accounts * Special permissions are required to access the TikTok [Business Messaging API](https://business-api.tiktok.com/portal/docs?id=1832183871604753). * The Business Messaging API is region-restricted and is currently unavailable to users in the EU. * Only TEXT, IMAGE, and POST_SHARE messages are currently supported due to limitations in the TikTok Business Messaging API * A message will be successfully sent only if it contains text alone or one image attachment. Messages with multiple attachments or those combining text and attachments will fail and receive a descriptive error status. * Messages sent directly from the TikTok App will be synced into the system * Initiating a new conversation from the system is not permitted due to limitations from the TikTok Business Messaging API. **Backend: TikTok Channel Integration** * Added `Api::V1::Accounts::Tiktok::AuthorizationsController` to handle TikTok OAuth authorization initiation, returning the TikTok authorization URL. * Implemented `Tiktok::CallbacksController` to handle TikTok OAuth callback, process authorization results, create or update channel/inbox, and handle errors or denied scopes. * Added `Webhooks::TiktokController` to receive and verify TikTok webhook events, including signature verification and event dispatching. * Created `Tiktok::IntegrationHelper` module for JWT-based token generation and verification for secure TikTok OAuth state management. **Configuration and Feature Flags** * Added TikTok app credentials (`TIKTOK_APP_ID`, `TIKTOK_APP_SECRET`) to allowed configs and app config, and registered TikTok as a feature in the super admin features YAML. [[1]](diffhunk://#diff-5e46e1d248631a1147521477d84a54f8ba6846ea21c61eca5f70042d960467f4R43) [[2]](diffhunk://#diff-8bf37a019cab1dedea458c437bd93e34af1d6e22b1672b1d43ef6eaa4dcb7732R69) [[3]](diffhunk://#diff-123164bea29f3c096b0d018702b090d5ae670760c729141bd4169a36f5f5c1caR74-R79) **Frontend: TikTok Channel UI and Messaging Support** * Added `TiktokChannel` API client for frontend TikTok authorization requests. * Updated channel icon mappings and tests to include TikTok (`Channel::Tiktok`). [[1]](diffhunk://#diff-b852739ed45def61218d581d0de1ba73f213f55570aa5eec52aaa08f380d0e16R16) [[2]](diffhunk://#diff-3cd3ae32e94ef85f1f2c4435abf0775cc0614fb37ee25d97945cd51573ef199eR64-R69) * Enabled TikTok as a supported channel in contact forms, channel widgets, and feature toggles. [[1]](diffhunk://#diff-ec59c85e1403aaed1a7de35971fe16b7033d5cd763be590903ebf8f1ca25a010R47) [[2]](diffhunk://#diff-ec59c85e1403aaed1a7de35971fe16b7033d5cd763be590903ebf8f1ca25a010R69) [[3]](diffhunk://#diff-725b90ca7e3a6837ec8291e9f57094f6a46b3ee00e598d16564f77f32cf354b0R26-R29) [[4]](diffhunk://#diff-725b90ca7e3a6837ec8291e9f57094f6a46b3ee00e598d16564f77f32cf354b0R51-R54) [[5]](diffhunk://#diff-725b90ca7e3a6837ec8291e9f57094f6a46b3ee00e598d16564f77f32cf354b0R68) * Updated message meta logic to support TikTok-specific message statuses (sent, delivered, read). [[1]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696R23) [[2]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696L63-R65) [[3]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696L81-R84) [[4]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696L103-R107) * Added support for embedded message attachments (e.g., TikTok embeds) with a new `EmbedBubble` component and updated message rendering logic. [[1]](diffhunk://#diff-c3d701caf27d9c31e200c6143c11a11b9d8826f78aa2ce5aa107470e6fdb9d7fR31) [[2]](diffhunk://#diff-047859f9368a46d6d20177df7d6d623768488ecc38a5b1e284f958fad49add68R1-R19) [[3]](diffhunk://#diff-c3d701caf27d9c31e200c6143c11a11b9d8826f78aa2ce5aa107470e6fdb9d7fR316) [[4]](diffhunk://#diff-cbc85e7c4c8d56f2a847d0b01cd48ef36e5f87b43023bff0520fdfc707283085R52) * Adjusted reply policy and UI messaging for TikTok's 48-hour reply window. [[1]](diffhunk://#diff-0d691f6a983bd89502f91253ecf22e871314545d1e3d3b106fbfc76bf6d8e1c7R208-R210) [[2]](diffhunk://#diff-0d691f6a983bd89502f91253ecf22e871314545d1e3d3b106fbfc76bf6d8e1c7R224-R226) These changes collectively enable end-to-end TikTok channel support, from configuration and OAuth flow to webhook processing and frontend message handling. ------------ # TikTok App Setup & Configuration 1. Grant access to the Business Messaging API ([Documentation](https://business-api.tiktok.com/portal/docs?id=1832184145137922)) 2. Set the app authorization redirect URL to `https://FRONTEND_URL/tiktok/callback` 3. Update the installation config with TikTok App ID and Secret 4. Create a Business Messaging Webhook configuration and set the callback url to `https://FRONTEND_URL/webhooks/tiktok` ([Documentation](https://business-api.tiktok.com/portal/docs?id=1832190670631937)) . You can do this by calling `Tiktok::AuthClient.update_webhook_callback` from rails console once you finish Tiktok channel configuration in super admin ( will be automated in future ) 5. Enable TikTok channel feature in an account --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
1df5fd513a
|
fix: Reduce unnecessary label suggestion API calls (#12978) | ||
|
|
48627da0f9
|
feat: outbound voice call essentials (#12782)
- Enables outbound voice calls in voice channel . We are only caring about wiring the logic to trigger outgoing calls to the call button introduced in previous PRs. We will connect it to call component in subsequent PRs ref: #11602 ## Screens <img width="2304" height="1202" alt="image" src="https://github.com/user-attachments/assets/b91543a8-8d4e-4229-bd80-9727b42c7b0f" /> <img width="2304" height="1200" alt="image" src="https://github.com/user-attachments/assets/1a1dad2a-8cb2-4aa2-9702-c062416556a7" /> --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com> |
||
|
|
b03dfdb751
|
Chore/merge upstream 4.8.0 (#150)
* chore: Hide "Learn More" button in feature spotlight for self-hosted (#12675) * feat: single query for reporting event stats (#12664) This PR collapses multiple queries fetching stats from a single table to a single query ```sql SELECT user_id as user_id, COUNT(CASE WHEN name = 'conversation_resolved' THEN 1 END) as resolved_count, AVG(CASE WHEN name = 'conversation_resolved' THEN value END) as avg_resolution_time, AVG(CASE WHEN name = 'first_response' THEN value END) as avg_first_response_time, AVG(CASE WHEN name = 'reply_time' THEN value END) as avg_reply_time FROM "reporting_events" WHERE "reporting_events"."account_id" = <account_id> AND "reporting_events"."created_at" >= '2025-09-14 18:30:00' AND "reporting_events"."created_at" < '2025-10-14 18:29:59' GROUP BY "reporting_events"."user_id"; ``` ### Why this works? Here's why this optimization is faster based on PostgreSQL internals: - Single Table Scan vs Multiple Scans: Earlier we did 4 sequential scans (or 4 index scans) of the same data, with the same where clause, now in a single scan all 4 `CASE` expressions are evaluated in a single pass. - Shared Buffer Cache Efficiency: PostgreSQL's shared buffer cache stores recently accessed pages, with this, pages are loaded once and re-used for all aggregation, earlier with separate queries we were forced to re-read all from the disk each time - Reduced planning and network overhead (4 vs 1 query) ### How is it tested 1. The specs all pass without making any changes 2. Verified the reports side by side after generating from report seeder #### How to test Generate seed data using the following command ```bash ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data ``` Once done download the reports, checkout to this branch and download the reports again and compare them * chore: Update translations (#12625) * chore: Migrate mailers from the worker to jobs (#12331) Previously, email replies were handled inside workers. There was no execution logs. This meant if emails silently failed (as reported by a customer), we had no way to trace where the issue happened, the only assumption was “no error = mail sent.” By moving email handling into jobs, we now have proper execution logs for each attempt. This makes it easier to debug delivery issues and would have better visibility when investigating customer reports. Fixes https://linear.app/chatwoot/issue/CW-5538/emails-are-not-sentdelivered-to-the-contact --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> * chore(deps-dev): bump vite from 5.4.20 to 5.4.21 (#12700) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.20 to 5.4.21. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/releases">vite's releases</a>.</em></p> <blockquote> <h2>v5.4.21</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md">vite's changelog</a>.</em></p> <blockquote> <h2><!-- raw HTML omitted -->5.4.21 (2025-10-20)<!-- raw HTML omitted --></h2> <ul> <li>fix(dev): trim trailing slash before <code>server.fs.deny</code> check (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/20968">#20968</a>) (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/20970">#20970</a>) (<a href=" |
||
|
|
7f0748460e
|
feat: dashboard apps on sidebar (#146)
* feat: dashboard apps on sidebar * fix: handle dashboard app not found * chore: minor refactoring |
||
|
|
344e8d5016
|
fix: Exclude authentication templates from WhatsApp template selection (#12753)
This PR add the changes for excluding the authentication templates from the WhatsApp template selection in the frontend, as these templates are not supported at the moment. Reference: https://www.chatwoot.com/hc/user-guide/articles/1754940076-whatsapp-templates#what-is-not-supported |
||
|
|
ca579bd62a
|
feat: Agent capacity policy Create/Edit pages (#12424)
# Pull Request Template ## Description Fixes https://linear.app/chatwoot/issue/CW-5573/feat-createedit-agent-capacity-policy-page ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/8de9e3c5d8824cd998d242636540dd18?sid=1314536f-c8d6-41fd-8139-cae9bf94f942 ### Screenshots **Light mode** <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/7e6d83a4-ce02-47a7-91f6-87745f8f5549" /> <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/7dd1f840-2e25-4365-aa1d-ed9dac13385a" /> **Dark mode** <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/0c787095-7146-4fb3-a61a-e2232973bcba" /> <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/481c21fd-03b5-4c1f-b59e-7f8c8017f9ce" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
59ba91473a
|
feat: Agent capacity policy index page with CRUD actions (#12409) | ||
|
|
257df30589
|
feat: Agent assignment policy Create/Edit pages (#12400) | ||
|
|
55633ab063
|
feat: Agent assignment policy index page with CRUD actions (#12373)
# Pull Request Template ## Description This PR incudes new Agent assignment policy index page with CRUD actions. Fixes https://linear.app/chatwoot/issue/CW-5570/feat-assignment-policy-index-page-with-actions ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Loom https://www.loom.com/share/17ab5ceca4854f179628a3b53f347e5a?sid=cb64e881-57fd-4ae1-921b-7648653cca33 ### Screenshots **Light mode** <img width="1428" height="888" alt="image" src="https://github.com/user-attachments/assets/fdbb83e9-1f4f-4432-9e8a-4a8f1b810d31" /> **Dark mode** <img width="1428" height="888" alt="image" src="https://github.com/user-attachments/assets/f1fb38b9-1150-482c-ba62-3fe63ee1c7d4" /> <img width="726" height="495" alt="image" src="https://github.com/user-attachments/assets/90a6ad55-9ab6-4adb-93a7-2327f5f09c79" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
b989ca6397
|
feat: Agent language settings (#11222)
# Pull Request Template ## Description This Pull Request will provide a language selector in the Profile Settings for each user, and allows them to change the UI language per agent, defaulting back to the account locale. Fixes # #678 This does PR addresses the Dashboard view but does not change the language of the agents emails ## Type of change Please delete options that are not relevant. - [X ] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? 1. Go to an Agents Profile settings page 2. Select a language from the Language drop down 3. the UI will update to the new i18n locale 4. navigate through the UI to make sure the appropriate language is being used 5. Refresh the page to test that the locale persists 270 - [X] My code follows the style guidelines of this project - [X] I have performed a self-review of my code - [X] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [X] My changes generate no new warnings - [X] I have added tests that prove my fix is effective or that my feature works - [X] New and existing unit tests pass locally with my changes - [X] Any dependent changes have been merged and published in downstream modules Checklist:.724.2708 --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
5c560c7628
|
feat: WhatsApp enhanced templates front end changes (#12117)
Part of the https://github.com/chatwoot/chatwoot/pull/11997 Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
ab1ba1c4c7
|
feat: Add manual WhatsApp templates sync with UI (#12007)
Fixes https://github.com/chatwoot/chatwoot/issues/9600 **Summary** - Added manual WhatsApp templates sync functionality accessible via UI - Added refresh button in both inbox settings and template picker modal - Enhanced template picker to always show for WhatsApp Cloud channels (even when empty) **Preview** <img width="1456" height="798" alt="CleanShot 2025-07-22 at 14 15 28@2x" src="https://github.com/user-attachments/assets/8a04ff26-61fa-42ee-a1b8-5e06433ae6e0" /> <img width="2376" height="1574" alt="CleanShot 2025-07-22 at 14 19 24@2x" src="https://github.com/user-attachments/assets/1c772034-04ff-484d-88dd-ca8123e93065" /> --------- Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
0ea616a6ea
|
feat: WhatsApp campaigns (#11910)
# Pull Request Template ## Description This PR adds support for WhatsApp campaigns to Chatwoot, allowing businesses to reach their customers through WhatsApp. The implementation includes backend support for WhatsApp template messages, frontend UI components, and integration with the existing campaign system. Fixes #8465 Fixes https://linear.app/chatwoot/issue/CW-3390/whatsapp-campaigns ## Type of change - [x] New feature (non-breaking change which adds functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - Tested WhatsApp campaign creation UI flow - Verified backend API endpoints for campaign creation - Tested campaign service integration with WhatsApp templates - Validated proper filtering of WhatsApp campaigns in the store ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules ## What we have changed: We have added support for WhatsApp campaigns as requested in the discussion. Ref: https://github.com/orgs/chatwoot/discussions/8465 **Note:** This implementation doesn't exactly match the maintainer's specification and variable support is missing. This is an initial implementation that provides the core WhatsApp campaign functionality. ### Changes included: **Backend:** - Added `template_params` column to campaigns table (migration + schema) - Created `Whatsapp::OneoffCampaignService` for WhatsApp campaign execution - Updated campaign model to support WhatsApp inbox types - Added template_params support to campaign controller and API **Frontend:** - Added WhatsApp campaign page, dialog, and form components - Updated campaign store to filter WhatsApp campaigns separately - Added WhatsApp-specific routes and empty state - Updated i18n translations for WhatsApp campaigns - Modified sidebar to include WhatsApp campaigns navigation This provides a foundation for WhatsApp campaigns that can be extended with variable support and other enhancements in future iterations. --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
b7f2c151bf
|
feat: Voice channel creation Flow (#11775)
This PR introduces a new channel type for voice conversations. ref: #11481 ## Changes - Add database migration for channel_voice table with phone_number and provider_config - Create Channel::Voice model with E.164 phone number validation and Twilio config validation - Add voice channel association to Account model - Extend inbox helpers and types to support voice channels - Add voice channel setup UI with Twilio configuration form - Include voice channel in channel factory and list components - Add API routes and store actions for voice channel creation - Add comprehensive translations for voice channel management --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
35f06f30e7
|
feat: label reports overview (#11194)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
273c277d47
|
feat: Add conversation delete feature (#11677)
<img width="1240" alt="Screenshot 2025-06-05 at 12 39 04 AM" src="https://github.com/user-attachments/assets/0071cd23-38c3-4638-946e-f1fbd11ec845" /> ## Changes Give the admins an option to delete conversation via the context menu - enable conversation deletion in routes and controller - expose delete API on conversations - add delete option in conversation context menu and integrate with card and list - implement store action and mutation for delete - update i18n with new strings fixes: https://github.com/chatwoot/chatwoot/issues/947 --------- Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Pranav <pranavrajs@gmail.com> |
||
|
|
513d954027
|
feat: Show active Contacts (#8243) | ||
|
|
07a39f4b42
|
feat: Enforce role permissions on filtered page (#11638)
Fixes: https://github.com/chatwoot/chatwoot/issues/11610 Demo: https://www.loom.com/share/c9181b42619044379ba01e0ac913801d?sid=e306fe30-ce80-47ac-83e5-92132a99f464 |
||
|
|
873cfa08d8
|
feat: Ability to reset api_access_token (#11565)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
b1120ae7fb
|
feat: allow searching articles in omnisearch (#11558)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> |
||
|
|
b533980880
|
feat: Add support for minutes in auto resolve feature (#11269)
### Summary - Converts conversation auto-resolution duration from days to minutes for more granular control - Updates validation to allow values from 10 minutes (minimum) to 999 days (maximum) - Implements smart messaging to show appropriate time units in activity messages ### Changes - Created migration to convert existing durations from days to minutes (x1440) - Updated conversation resolver to use minutes instead of days - Added dynamic translation key selection based on duration value - Updated related specs and documentation - Added support for displaying durations in days, hours, or minutes based on value ### Test plan - Verify account validation accepts new minute-based ranges - Confirm existing account settings are correctly migrated - Test auto-resolution works properly with minute values - Ensure proper time unit display in activity messages --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> |
||
|
|
630826baed
|
feat: Implement UI for Agent Bots in settings and remove CSML support (#11276)
- Add agent bots management UI in settings with avatar upload - Enable agent bot configuration for all inbox types - Implement proper CRUD operations with webhook URL support - Fix agent bots menu item visibility in settings sidebar - Remove all CSML-related code and features - Add migration to convert existing CSML bots to webhook bots - Simplify agent bot model and services to focus on webhook bots - Improve UI to differentiate between system bots and account bots ## Video https://github.com/user-attachments/assets/3f4edbb7-b758-468c-8dd6-a9537b983f7d --------- Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Pranav <pranav@chatwoot.com> |
||
|
|
c6d4bc5632
|
fix: Old Instagram inbox warnings (#11318)
We have added warnings for existing Instagram messenger inboxes via https://github.com/chatwoot/chatwoot/pull/11303. There is an issue with finding the correct Instagram/messenger inbox. This PR will fixes that issue. |
||
|
|
ad17887898
|
feat: Add warnings for existing instagram messenger channels (#11303) | ||
|
|
1140ca7a78
|
fix: stale report value shown if summary fetch breaks (#11270)
This pull request includes several changes to better show the report metrics fetching status on the dashboard. This also prevents showing stale data in case fetching summary fails due to timeout or other issue The most important changes include adding a new fetching status state to the store called `summaryFetchingStatus`, updating the `useReportMetrics` composable, and modifying the `ChartStats` component to handle different fetching statuses. #### Loading  #### Finished  #### Failed <img width="1512" alt="image" src="https://github.com/user-attachments/assets/d521a785-9299-4e59-94dc-561a7a84377e" /> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
0dc2af3c78
|
feat: Ability to delete account for administrators (#1874)
## Description Add account delete option in the user account settings. Fixes #1555 ## Type of change - [ ] New feature (non-breaking change which adds functionality)   ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Sojan Jose <sojan.official@gmail.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
9761214860
|
feat: Add debounce for meta query (#11195)
This PR combines the approaches in https://github.com/chatwoot/chatwoot/pull/11190 and https://github.com/chatwoot/chatwoot/pull/11187 to debounce the meta request with a max wait time of 2.5 seconds With 500 concurrent users, the theoretical limit with this is 720K requests per minute, if all of them continuously receive websocket events. The max wait of 2.5 seconds is still very generous, and we can easily make it 2 seconds for smaller accounts and 5 seconds for larger accounts. ```js const debouncedFetchMetaData = debounce(fetchMetaData, 500, false, 200); const longDebouncedFetchMetaData = debounce(fetchMetaData, 500, false, 5000); export const actions = { get: async ({ commit, state: $state }, params) => { if ($state.allCount > 100) { longDebouncedFetchMetaData(commit, params); } else { debouncedFetchMetaData(commit, params); } }, set({ commit }, meta) { commit(types.SET_CONV_TAB_META, meta); }, }; ``` Related Utils PR: https://github.com/chatwoot/utils/pull/49 Here's the debounce in action <img width="934" alt="image" src="https://github.com/user-attachments/assets/5265a108-9c64-4488-9b4c-2e0d06aadc50" /> --------- Co-authored-by: Pranav <pranavrajs@gmail.com> |
||
|
|
4b4d9f8f7c
|
fix: Update throttle for /meta endpoints, will call every 2s for small account, 10s for large accounts (#11190)
This update improves the throttling mechanism for conversation meta requests to optimize server load and enhance performance. The changes implement differentiated thresholds based on account size - a 2-second throttle for small accounts (≤100 conversations) and a 10-second throttle for large accounts (>100 conversations). Fixes #11178 |
||
|
|
f67b20b203
|
feat: remove sentry log and allow same timestamp (#11100) | ||
|
|
00556bd55a
|
feat: ignore out of sync messages (#11058)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> |
||
|
|
616bbced9c
|
feat: allow copilot use without connecting an inbox (#10992)
This PR allows Copilot to be used without connecting the Captain assistant to an inbox. Currently, if Captain is enabled on an account, it takes over conversations and responds directly to users. This PR enables the use of Captain as a Copilot without allowing it to respond to users. Additionally, it allows using a different assistant for Copilot instead of the default Captain assistant. The selection logic for the Copilot assistant follows this order of preference: - If the user has selected a specific assistant, it takes first preference for Copilot. - If the above is not available, the assistant connected to the inbox takes preference. - If neither of the above is available, the first assistant in the account takes preference. |
||
|
|
bd94e5062d
|
chore: Search improvements (#10801)
- Adds pagination support for search. - Use composition API on all search related component. - Minor UI improvements. - Adds missing specs Loom video https://www.loom.com/share/5b01afa5c9204e7d97ff81b215621dde?sid=82ca6d22-ca8c-4d5e-8740-ba06ca4051ba |
||
|
|
c3aab44b5f
|
feat: Add unread badge to sidebar for inbox view (#10803)
# Pull Request Template ## Description This PR adds a badge to the icon in the sidebar group header and an unread highlight for the inbox view. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Loom video** https://www.loom.com/share/1a880aa16de249a1836b8d014325c067?sid=87cc7b64-667b-4066-8993-d33abccf7b67 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |