Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c0b3c3418 | ||
|
|
34249d0a3a | ||
|
|
c4a66346c9 | ||
|
|
37ceac4cd3 |
@@ -32,13 +32,15 @@ class ConversationFinder
|
||||
def initialize(current_user, params)
|
||||
@current_user = current_user
|
||||
@current_account = current_user.account
|
||||
@is_admin = current_account.account_users.find_by(user_id: current_user.id)&.administrator?
|
||||
@params = params
|
||||
end
|
||||
|
||||
def perform
|
||||
set_up
|
||||
|
||||
mine_count, unassigned_count, all_count, = set_count_for_all_conversations
|
||||
mine_count = compute_mine_count
|
||||
unassigned_count, all_count = compute_unassigned_and_all_count
|
||||
assigned_count = all_count - unassigned_count
|
||||
|
||||
filter_by_assignee_type
|
||||
@@ -86,7 +88,8 @@ class ConversationFinder
|
||||
end
|
||||
|
||||
def find_all_conversations
|
||||
@conversations = current_account.conversations.where(inbox_id: @inbox_ids)
|
||||
@conversations = current_account.conversations
|
||||
@conversations = @conversations.where(inbox_id: @inbox_ids) unless @is_admin
|
||||
filter_by_conversation_type if params[:conversation_type]
|
||||
@conversations
|
||||
end
|
||||
@@ -151,12 +154,54 @@ class ConversationFinder
|
||||
@conversations = @conversations.where(contact_inboxes: { source_id: params[:source_id] })
|
||||
end
|
||||
|
||||
def set_count_for_all_conversations
|
||||
[
|
||||
@conversations.assigned_to(current_user).count,
|
||||
@conversations.unassigned.count,
|
||||
@conversations.count
|
||||
]
|
||||
def compute_mine_count
|
||||
# Mine count is always computed fresh, never cached
|
||||
@conversations.assigned_to(current_user).count
|
||||
end
|
||||
|
||||
def compute_unassigned_and_all_count
|
||||
return compute_unassigned_and_all_count_without_cache if additional_filters? || !should_cache?
|
||||
|
||||
cached_counts = ::Redis::Alfred.get(cache_key)
|
||||
if cached_counts.present?
|
||||
begin
|
||||
return JSON.parse(cached_counts)
|
||||
rescue JSON::ParserError
|
||||
compute_unassigned_and_all_count_without_cache
|
||||
end
|
||||
end
|
||||
|
||||
counts = compute_unassigned_and_all_count_without_cache
|
||||
::Redis::Alfred.setex(cache_key, counts, cache_expiry(counts[0]))
|
||||
|
||||
counts
|
||||
end
|
||||
|
||||
def compute_unassigned_and_all_count_without_cache
|
||||
unassigned_count = @conversations.unassigned.count
|
||||
all_count = @conversations.count
|
||||
[unassigned_count, all_count]
|
||||
end
|
||||
|
||||
def should_cache?
|
||||
current_account.feature_enabled?('cache_meta_counts')
|
||||
end
|
||||
|
||||
def cache_key
|
||||
@cache_key ||= format(::Redis::Alfred::CONVERSATION_COUNTS, inbox_ids: @inbox_ids.sort.join('-'), status: params[:status] || DEFAULT_STATUS)
|
||||
end
|
||||
|
||||
def cache_expiry(unassigned_count)
|
||||
# these numbers are intentionally chosen to balance between cache hit rate and freshness
|
||||
return 5.seconds if unassigned_count < 100
|
||||
return 10.seconds if unassigned_count < 200
|
||||
return 15.seconds if unassigned_count < 500
|
||||
|
||||
20.seconds
|
||||
end
|
||||
|
||||
def additional_filters?
|
||||
[:q, :conversation_type, :team_id, :labels, :source_id, :inbox_id].any? { |key| params[key].present? }
|
||||
end
|
||||
|
||||
def current_page
|
||||
|
||||
@@ -161,3 +161,6 @@
|
||||
- name: search_with_gin
|
||||
display_name: Search messages with GIN
|
||||
enabled: false
|
||||
- name: cache_meta_counts
|
||||
display_name: Cache Meta Counts
|
||||
enabled: false
|
||||
|
||||
@@ -41,4 +41,7 @@ module Redis::RedisKeys
|
||||
IG_MESSAGE_MUTEX = 'IG_MESSAGE_CREATE_LOCK::%<sender_id>s::%<ig_account_id>s'.freeze
|
||||
SLACK_MESSAGE_MUTEX = 'SLACK_MESSAGE_LOCK::%<conversation_id>s::%<reference_id>s'.freeze
|
||||
EMAIL_MESSAGE_MUTEX = 'EMAIL_CHANNEL_LOCK::%<inbox_id>s'.freeze
|
||||
|
||||
# Cache for storing conversation meta counts
|
||||
CONVERSATION_COUNTS = 'CONVERSATION::COUNTS::%<inbox_ids>s::%<status>s'.freeze
|
||||
end
|
||||
|
||||
@@ -20,6 +20,7 @@ describe ConversationFinder do
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_2, contact_inbox: contact_inbox)
|
||||
# unassigned conversation
|
||||
create(:conversation, account: account, inbox: inbox)
|
||||
account.disable_features!('cache_meta_counts')
|
||||
Current.account = account
|
||||
end
|
||||
|
||||
@@ -193,5 +194,81 @@ describe ConversationFinder do
|
||||
expect(result[:conversations].length).to be 2
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching behavior' do
|
||||
let(:params) { { status: 'open' } }
|
||||
let(:cache_key) { format(Redis::Alfred::CONVERSATION_COUNTS, inbox_ids: inbox.id, status: 'open') }
|
||||
|
||||
before do
|
||||
account.enable_features!('cache_meta_counts')
|
||||
end
|
||||
|
||||
context 'when should_cache? returns true' do
|
||||
before do
|
||||
Redis::Alfred.delete(cache_key) if cache_key
|
||||
end
|
||||
|
||||
it 'caches the unassigned and all counts' do
|
||||
expect(Redis::Alfred).to receive(:setex).with(
|
||||
cache_key,
|
||||
[1, 4], # unassigned_count and all_count
|
||||
instance_of(Integer)
|
||||
)
|
||||
|
||||
conversation_finder.perform
|
||||
end
|
||||
|
||||
it 'uses cached counts on subsequent calls' do
|
||||
# First call should cache the counts
|
||||
first_result = conversation_finder.perform
|
||||
|
||||
# Create a new conversation that shouldn't be counted due to caching
|
||||
create(:conversation, account: account, inbox: inbox)
|
||||
|
||||
# Second call should use cached counts
|
||||
second_result = conversation_finder.perform
|
||||
|
||||
expect(second_result[:count][:unassigned_count]).to eq(first_result[:count][:unassigned_count])
|
||||
expect(second_result[:count][:all_count]).to eq(first_result[:count][:all_count])
|
||||
end
|
||||
|
||||
it 'always computes mine_count fresh' do
|
||||
# First call
|
||||
first_result = conversation_finder.perform
|
||||
|
||||
# Create a new conversation assigned to user_1
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1)
|
||||
|
||||
# Second call should have updated mine_count
|
||||
second_result = conversation_finder.perform
|
||||
|
||||
expect(second_result[:count][:mine_count]).to eq(first_result[:count][:mine_count] + 1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when has_additional_filters? returns true' do
|
||||
let(:params) { { status: 'open', labels: ['test'] } }
|
||||
|
||||
it 'does not use cache' do
|
||||
expect(Redis::Alfred).not_to receive(:get)
|
||||
expect(Redis::Alfred).not_to receive(:setex)
|
||||
|
||||
conversation_finder.perform
|
||||
end
|
||||
end
|
||||
|
||||
context 'when should_cache? returns false' do
|
||||
before do
|
||||
account.disable_features!('cache_meta_counts')
|
||||
end
|
||||
|
||||
it 'does not use cache' do
|
||||
expect(Redis::Alfred).not_to receive(:get)
|
||||
expect(Redis::Alfred).not_to receive(:setex)
|
||||
|
||||
conversation_finder.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user