feat: Add sidebar unread counts for filters (CW-7262) (#14726)
## Description Extends the conversation unread-count system so the left sidebar can show unread badges for Mentions, Participating, Unattended, and saved conversation folders. Folder badges reuse the existing `custom_filters` conversation filter semantics, store user-scoped Redis sets lazily, and skip unsupported folder filters so invalid saved folders continue to render without a badge. The Unattended badge counts all visible unread open conversations that match the existing unattended conversation scope. Closes - [CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders) ## What changed - Added user-scoped unread-count Redis keys and cache builders for mentions, participating conversations, unattended conversations, and saved folder filters. - Reused `Conversations::FilterService` through a relation-returning path so folder counts match the folder conversation list behavior. - Invalidated user filter caches from mention, participant, custom-filter, and relevant conversation update events. - Extended the unread-count endpoint payload and sidebar Vuex/sidebar rendering for the new badge counts, including the Unattended sidebar item. - Added Ruby, Enterprise, request, listener, and frontend store coverage for the new unread-count dimensions. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Created local validation folders for `john@acme.inc` and confirmed the unread-count payload includes open, resolved, and high-priority folder badges while excluding the invalid unsupported folder. - Added coverage for the Unattended badge rule: all visible unread open conversations matching `Conversation.unattended`. - Ran focused unread-count Ruby specs, including service, listener, request, and Enterprise counter coverage. - Ran frontend unread-count store specs. - Ran RuboCop on the touched Ruby files. - Ran ESLint through the project script; it completed with warnings in existing unrelated files and no errors. <img width="369" height="525" alt="Screenshot 2026-06-13 at 10 51 39 PM" src="https://github.com/user-attachments/assets/36b1d2c4-dac1-4f6f-9c0e-7ef5a6cc2975" /> ## 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] Documentation changes are not required for this internal unread-count behavior - [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] No dependent downstream changes are required --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
co-authored by
Muhsin Keloth
parent
352f120c6a
commit
fe6368b42e
@@ -123,7 +123,7 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
end
|
||||
|
||||
after do
|
||||
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
||||
Conversations::UnreadCounts::Store.clear_all_account!(account.id)
|
||||
end
|
||||
|
||||
context 'when conversation unread counts feature is enabled' do
|
||||
@@ -144,7 +144,42 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
'all_count' => 1,
|
||||
'inboxes' => { visible_inbox.id.to_s => 1 },
|
||||
'labels' => { label.id.to_s => 1 },
|
||||
'teams' => {}
|
||||
'teams' => {},
|
||||
'mentions_count' => 0,
|
||||
'participating_count' => 0,
|
||||
'unattended_count' => 1,
|
||||
'folders' => {}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unread counts for mentions, participating conversations, unattended conversations, and folders' do
|
||||
mentioned_conversation = create_unread_conversation(account: account, inbox: visible_inbox)
|
||||
participating_conversation = create_unread_conversation(account: account, inbox: visible_inbox)
|
||||
resolved_conversation = create_unread_conversation(account: account, inbox: visible_inbox)
|
||||
resolved_conversation.update!(status: :resolved)
|
||||
custom_filter = create(:custom_filter, account: account, user: agent, filter_type: :conversation, query: {
|
||||
payload: [{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['resolved'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}]
|
||||
})
|
||||
|
||||
create(:mention, account: account, conversation: mentioned_conversation, user: agent)
|
||||
create(:conversation_participant, account: account, conversation: participating_conversation, user: agent)
|
||||
|
||||
get "/api/v1/accounts/#{account.id}/conversations/unread_counts",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.parsed_body['payload']).to include(
|
||||
'mentions_count' => 1,
|
||||
'participating_count' => 1,
|
||||
'unattended_count' => 2,
|
||||
'folders' => { custom_filter.id.to_s => 1 }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -862,7 +897,7 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(Conversations::UnreadCounts::Store.counts_for_keys([inbox_key])).to eq(inbox_key => 0)
|
||||
ensure
|
||||
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
||||
Conversations::UnreadCounts::Store.clear_all_account!(account.id)
|
||||
end
|
||||
|
||||
it 'updates both if one timestamp is old even when the other is recent' do
|
||||
@@ -949,7 +984,7 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(Conversations::UnreadCounts::Store.counts_for_keys([inbox_key])).to eq(inbox_key => 1)
|
||||
ensure
|
||||
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
||||
Conversations::UnreadCounts::Store.clear_all_account!(account.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user