From 2441487a761ca8e6c3313aa5de108375fe2f02af Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Fri, 20 Feb 2026 21:20:19 +0530 Subject: [PATCH] perf: skip conversation loading in /meta endpoint (#13564) # Pull Request Template ## Summary - Adds `perform_meta_only` method to `ConversationFinder` that runs setup and counts without loading the paginated conversation list - Updates `/api/v1/conversations/meta` to use `perform_meta_only` instead of `perform` ## Problem The `/meta` endpoint calls `ConversationFinder#perform` which: 1. Runs all filters and setup (`set_up`) 2. Computes 3 COUNT queries (`set_count_for_all_conversations`) 3. Filters by assignee type 4. **Builds the full paginated conversation list** with `.includes(:taggings, :inbox, {assignee: {avatar_attachment: [:blob]}}, {contact: {avatar_attachment: [:blob]}}, :team, :contact_inbox)` + sorting + pagination The controller then **discards the conversations** and only uses the counts: ```ruby def meta result = conversation_finder.perform @conversations_count = result[:count] # conversations thrown away end ``` ## Type of change - [x] Performance fix ## How Has This Been Tested? - [ ] Verify /meta returns correct mine/unassigned/assigned/all counts - [ ] Verify counts update when switching inbox, team, or status filters - [ ] Verify conversation list still loads correctly (uses perform, not affected) - [ ] Monitor response time reduction for /meta in NewRelic after deploy ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] 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 - [ ] 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: Pranav --- .../v1/accounts/conversations_controller.rb | 2 +- app/finders/conversation_finder.rb | 16 ++++++++++++ spec/finders/conversation_finder_spec.rb | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 7c21844f5..ab1cae17d 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -15,7 +15,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def meta - result = conversation_finder.perform + result = conversation_finder.perform_meta_only @conversations_count = result[:count] end diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index d43ed31e7..7821bee49 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -55,6 +55,22 @@ class ConversationFinder } end + def perform_meta_only + set_up + + mine_count, unassigned_count, all_count, = set_count_for_all_conversations + assigned_count = all_count - unassigned_count + + { + count: { + mine_count: mine_count, + assigned_count: assigned_count, + unassigned_count: unassigned_count, + all_count: all_count + } + } + end + private def set_up diff --git a/spec/finders/conversation_finder_spec.rb b/spec/finders/conversation_finder_spec.rb index 0174ccfb0..9427e8157 100644 --- a/spec/finders/conversation_finder_spec.rb +++ b/spec/finders/conversation_finder_spec.rb @@ -190,6 +190,32 @@ describe ConversationFinder do end end + context 'with perform_meta_only' do + let(:params) { { assignee_type: 'assigned' } } + + it 'returns only count without conversations' do + result = conversation_finder.perform_meta_only + expect(result).to have_key(:count) + expect(result).not_to have_key(:conversations) + end + + it 'returns the correct counts' do + result = conversation_finder.perform_meta_only + expect(result[:count]).to eq({ + mine_count: 2, + assigned_count: 3, + unassigned_count: 1, + all_count: 4 + }) + end + + it 'returns same counts as perform' do + meta_result = conversation_finder.perform_meta_only + full_result = conversation_finder.perform + expect(meta_result[:count]).to eq(full_result[:count]) + end + end + context 'with unattended' do let(:params) { { status: 'open', assignee_type: 'me', conversation_type: 'unattended' } }