diff --git a/app/controllers/super_admin/dashboard_controller.rb b/app/controllers/super_admin/dashboard_controller.rb index 56d19d563..8bc22cc59 100644 --- a/app/controllers/super_admin/dashboard_controller.rb +++ b/app/controllers/super_admin/dashboard_controller.rb @@ -2,10 +2,33 @@ class SuperAdmin::DashboardController < SuperAdmin::ApplicationController include ActionView::Helpers::NumberHelper def index - @data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a - @accounts_count = number_with_delimiter(Account.count) - @users_count = number_with_delimiter(User.count) - @inboxes_count = number_with_delimiter(Inbox.count) - @conversations_count = number_with_delimiter(Conversation.count) + respond_to do |format| + format.html + format.json { render json: dashboard_stats } + end + end + + private + + def dashboard_stats + Rails.cache.fetch('super_admin:dashboard_stats', expires_in: 30.minutes) do + { + chartData: Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a, + accountsCount: number_with_delimiter(Account.count), + usersCount: number_with_delimiter(User.count), + inboxesCount: number_with_delimiter(Inbox.count), + conversationsCount: number_with_delimiter(conversations_count_estimate) + } + end + end + + # Exact COUNT(*) scans the whole table; the planner estimate is instant + # and close enough for dashboard display. + def conversations_count_estimate + estimate = ActiveRecord::Base.connection.select_value( + "SELECT reltuples::bigint FROM pg_class WHERE relname = 'conversations'" + ).to_i + # reltuples is -1 until the table is first vacuumed/analyzed + estimate.negative? ? Conversation.count : estimate end end diff --git a/app/javascript/superadmin_pages/views/dashboard/Index.vue b/app/javascript/superadmin_pages/views/dashboard/Index.vue index f85ebea7f..944373673 100644 --- a/app/javascript/superadmin_pages/views/dashboard/Index.vue +++ b/app/javascript/superadmin_pages/views/dashboard/Index.vue @@ -1,13 +1,31 @@ @@ -47,26 +62,25 @@ const { accountsCount, usersCount, inboxesCount, conversationsCount } = - - {{ accountsCount }} - {{ 'Accounts' }} - - - {{ usersCount }} - {{ 'Users' }} - - - {{ inboxesCount }} - {{ 'Inboxes' }} - - - {{ conversationsCount }} - {{ 'Conversations' }} + + + + {{ item.value || 'N/A' }} + + {{ item.label }} + -<%= render_vue_component('DashboardIndex', { - accountsCount: @accounts_count, - usersCount: @users_count, - inboxesCount: @inboxes_count, - conversationsCount: @conversations_count, - chartData: @data -}) %> +<%= render_vue_component('DashboardIndex', {}) %> diff --git a/db/migrate/20260724000100_add_index_to_conversations_created_at.rb b/db/migrate/20260724000100_add_index_to_conversations_created_at.rb new file mode 100644 index 000000000..a89752e80 --- /dev/null +++ b/db/migrate/20260724000100_add_index_to_conversations_created_at.rb @@ -0,0 +1,7 @@ +class AddIndexToConversationsCreatedAt < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_index :conversations, :created_at, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index c7c5f6a77..acf83e12a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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_07_18_000000) do +ActiveRecord::Schema[7.1].define(version: 2026_07_24_000100) do # These extensions should be enabled to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -800,6 +800,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_07_18_000000) do t.index ["campaign_id"], name: "index_conversations_on_campaign_id" t.index ["contact_id"], name: "index_conversations_on_contact_id" t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id" + t.index ["created_at"], name: "index_conversations_on_created_at" t.index ["first_reply_created_at"], name: "index_conversations_on_first_reply_created_at" t.index ["identifier", "account_id"], name: "index_conversations_on_identifier_and_account_id" t.index ["inbox_id"], name: "index_conversations_on_inbox_id" diff --git a/tailwind.config.js b/tailwind.config.js index cc2aebb63..65388ad3a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -23,6 +23,7 @@ const tailwindConfig = { darkMode: 'class', content: [ './enterprise/app/views/**/*.erb', + './app/javascript/superadmin_pages/**/*.vue', './app/javascript/widget/**/*.vue', './app/javascript/v3/**/*.vue', './app/javascript/dashboard/**/*.vue',