Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b7240661f | ||
|
|
f2d20905c1 | ||
|
|
9cd08f4075 | ||
|
|
62f77d1091 | ||
|
|
6d38b917c9 | ||
|
|
5f6cd48a53 | ||
|
|
af511b39c4 | ||
|
|
d4992ad4c6 | ||
|
|
d752c379b0 | ||
|
|
d0525660ab | ||
|
|
efc58eef9a | ||
|
|
96f1aa7205 | ||
|
|
ace5c90007 | ||
|
|
6e85dcbbce | ||
|
|
6a053e975e |
@@ -12,6 +12,7 @@ gem 'bootsnap', require: false
|
||||
gem 'acts-as-taggable-on'
|
||||
gem 'attr_extras'
|
||||
gem 'browser'
|
||||
gem 'counter_culture'
|
||||
gem 'hashie'
|
||||
gem 'jbuilder'
|
||||
gem 'kaminari'
|
||||
|
||||
@@ -192,6 +192,9 @@ GEM
|
||||
commonmarker (0.23.10)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
counter_culture (3.12.1)
|
||||
activerecord (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
crack (1.0.0)
|
||||
bigdecimal
|
||||
rexml
|
||||
@@ -1041,6 +1044,7 @@ DEPENDENCIES
|
||||
cld3 (~> 3.7)
|
||||
climate_control
|
||||
commonmarker
|
||||
counter_culture
|
||||
csv-safe
|
||||
database_cleaner
|
||||
datadog (~> 2.0)
|
||||
|
||||
@@ -2,6 +2,9 @@ class ConversationFinder
|
||||
attr_reader :current_user, :current_account, :params
|
||||
|
||||
DEFAULT_STATUS = 'open'.freeze
|
||||
# Filter params that affect conversation count
|
||||
# NOTE: When adding a new filter, add it here to prevent incorrect cache usage
|
||||
FILTER_PARAMS = %i[inbox_id team_id labels q source_id conversation_type].freeze
|
||||
SORT_OPTIONS = {
|
||||
'last_activity_at_asc' => %w[sort_on_last_activity_at asc],
|
||||
'last_activity_at_desc' => %w[sort_on_last_activity_at desc],
|
||||
@@ -184,10 +187,23 @@ class ConversationFinder
|
||||
end
|
||||
|
||||
def set_count_for_all_conversations
|
||||
# Use cache only for admins with no filters other than status
|
||||
use_cache = current_account.feature_enabled?(:counter_cache_optimization) &&
|
||||
@is_admin &&
|
||||
FILTER_PARAMS.none? { |key| params[key] } &&
|
||||
params[:status] != 'all'
|
||||
|
||||
all_count = if use_cache
|
||||
status = params[:status].presence_in(Conversation.statuses.keys) || DEFAULT_STATUS
|
||||
current_account.public_send("#{status}_conversations_count")
|
||||
else
|
||||
@conversations.count
|
||||
end
|
||||
|
||||
[
|
||||
@conversations.assigned_to(current_user).count,
|
||||
@conversations.unassigned.count,
|
||||
@conversations.count
|
||||
all_count
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Internal::BackfillConversationCountersJob < ApplicationJob
|
||||
queue_as :low
|
||||
|
||||
def perform(account)
|
||||
Internal::BackfillConversationCountersService.new(account: account).perform
|
||||
end
|
||||
end
|
||||
@@ -144,6 +144,7 @@ class Account < ApplicationRecord
|
||||
before_validation :validate_limit_keys
|
||||
after_create_commit :notify_creation
|
||||
after_destroy :remove_account_sequences
|
||||
after_save :backfill_conversation_counters_if_needed
|
||||
|
||||
def agents
|
||||
users.where(account_users: { role: :agent })
|
||||
@@ -208,6 +209,13 @@ class Account < ApplicationRecord
|
||||
"execute format('create sequence IF NOT EXISTS camp_dpid_seq_%s', NEW.id);"
|
||||
end
|
||||
|
||||
def backfill_conversation_counters_if_needed
|
||||
return unless saved_change_to_feature_flags?
|
||||
return unless feature_counter_cache_optimization?
|
||||
|
||||
Internal::BackfillConversationCountersJob.perform_later(self)
|
||||
end
|
||||
|
||||
def validate_limit_keys
|
||||
# method overridden in enterprise module
|
||||
end
|
||||
|
||||
@@ -98,6 +98,8 @@ class Conversation < ApplicationRecord
|
||||
}
|
||||
|
||||
belongs_to :account
|
||||
counter_culture :account,
|
||||
column_name: proc { |model| "#{model.status}_conversations_count" }
|
||||
belongs_to :inbox
|
||||
belongs_to :assignee, class_name: 'User', optional: true, inverse_of: :assigned_conversations
|
||||
belongs_to :assignee_agent_bot, class_name: 'AgentBot', optional: true
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
class Internal::BackfillConversationCountersService
|
||||
# Backfills the conversation status counter cache columns on accounts
|
||||
# (open_conversations_count, resolved_conversations_count, pending_conversations_count, snoozed_conversations_count).
|
||||
#
|
||||
# Safe to re-run to correct any drift.
|
||||
#
|
||||
# Usage:
|
||||
# Internal::BackfillConversationCountersService.new.perform
|
||||
# Internal::BackfillConversationCountersService.new(account: Account.find(id)).perform
|
||||
#
|
||||
def initialize(account: nil)
|
||||
@account = account
|
||||
end
|
||||
|
||||
def perform
|
||||
scope = @account ? Account.where(id: @account.id) : Account.all
|
||||
scope.find_each do |account|
|
||||
account.update(
|
||||
open_conversations_count: account.conversations.open.count,
|
||||
resolved_conversations_count: account.conversations.resolved.count,
|
||||
pending_conversations_count: account.conversations.pending.count,
|
||||
snoozed_conversations_count: account.conversations.snoozed.count
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
+4
-4
@@ -17,10 +17,10 @@
|
||||
display_name: Facebook Channel
|
||||
enabled: true
|
||||
help_url: https://chwt.app/hc/fb
|
||||
- name: channel_twitter
|
||||
display_name: Twitter Channel
|
||||
enabled: true
|
||||
deprecated: true
|
||||
- name: counter_cache_optimization
|
||||
display_name: Counter Cache Optimization
|
||||
enabled: false
|
||||
chatwoot_internal: true
|
||||
- name: ip_lookup
|
||||
display_name: IP Lookup
|
||||
enabled: false
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class AddStatusConversationCountersToAccounts < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :accounts, :open_conversations_count, :integer, default: 0, null: false
|
||||
add_column :accounts, :resolved_conversations_count, :integer, default: 0, null: false
|
||||
add_column :accounts, :pending_conversations_count, :integer, default: 0, null: false
|
||||
add_column :accounts, :snoozed_conversations_count, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
# counter_cache_optimization reclaims the deprecated channel_twitter bit (position 4).
|
||||
# channel_twitter was enabled: true, so all existing accounts have bit 4 set to 1.
|
||||
# counter_cache_optimization is enabled: false, so we must clear bit 4 for all accounts.
|
||||
class DisableCounterCacheOptimizationForExistingAccounts < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
Account.feature_counter_cache_optimization.find_each(batch_size: 100) do |account|
|
||||
account.disable_features(:counter_cache_optimization)
|
||||
account.save!(validate: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
+5
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_02_26_084618) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_02_26_125101) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -73,6 +73,10 @@ ActiveRecord::Schema[7.1].define(version: 2026_02_26_084618) do
|
||||
t.integer "status", default: 0
|
||||
t.jsonb "internal_attributes", default: {}, null: false
|
||||
t.jsonb "settings", default: {}
|
||||
t.integer "open_conversations_count", default: 0, null: false
|
||||
t.integer "resolved_conversations_count", default: 0, null: false
|
||||
t.integer "pending_conversations_count", default: 0, null: false
|
||||
t.integer "snoozed_conversations_count", default: 0, null: false
|
||||
t.index ["status"], name: "index_accounts_on_status"
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user