mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-25 16:42:28 +08:00
Behind the `dashboard_improvements` setting, the Highlights row is hardcoded This commit updates `/admin/dashboard.json` to swap out to a `sections.highlights`. The endpoint takes optional `start_date` / `end_date` so the redesigned page passes its picker range. Legacy dashboard / `AdminDashboard.fetch()` callers get the same response without the new stuff. Core ships three KPIs (`new_signups`, `dau_mau`, `new_contributors`). Plugins register more via a new `register_admin_dashboard_highlight_kpi(type:, report:, enabled:)` (discourse-solved for now). Each plugin owns their own KPI stuff. Caching uses the existing per-report layer (`Report.find_cached`) rather than wrapping the service in its own cache. Toggling a plugin's `enabled:` takes effect immediately, the cache is shared with the Reports tab when an admin drills into a tile, and the cache key isn't fragmented per admin since the data is admin-agnostic. The server returns `report_type` + `report_query` instead of a full URL so routes are stable. Without solved: https://github.com/user-attachments/assets/28612829-b425-4664-bfb9-02e8783f5b93 With solved: https://github.com/user-attachments/assets/3c6b6d6f-44b7-4c61-9b23-0aaaa3f21ddc
108 lines
2.8 KiB
Ruby
Vendored
108 lines
2.8 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class AdminDashboardHighlights
|
|
DEFAULT_RANGE_DAYS = 30
|
|
|
|
KPI_REPORTS = {
|
|
new_signups: "signups",
|
|
dau_mau: "dau_by_mau",
|
|
new_contributors: "new_contributors",
|
|
}.freeze
|
|
|
|
def self.build(start_date:, end_date:)
|
|
new(start_date: start_date, end_date: end_date).build
|
|
end
|
|
|
|
def initialize(start_date:, end_date:)
|
|
@start_date = parse_date(start_date) || DEFAULT_RANGE_DAYS.days.ago.beginning_of_day
|
|
@end_date = parse_date(end_date)&.end_of_day || Time.zone.now.end_of_day
|
|
end
|
|
|
|
def build
|
|
{ kpis: build_kpis }
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :start_date, :end_date
|
|
|
|
def parse_date(value)
|
|
return nil if value.blank?
|
|
Time.zone.parse(value.to_s)&.beginning_of_day
|
|
rescue ArgumentError, TypeError
|
|
nil
|
|
end
|
|
|
|
def build_kpis
|
|
core_kpis = KPI_REPORTS.map { |type, report| { type: type, report: report } }
|
|
all_kpis = core_kpis + DiscoursePluginRegistry.admin_dashboard_highlight_kpis
|
|
|
|
all_kpis.filter_map do |kpi|
|
|
next if kpi[:enabled].respond_to?(:call) && !kpi[:enabled].call
|
|
build_kpi(kpi[:type], kpi[:report])
|
|
end
|
|
end
|
|
|
|
def build_kpi(type, report_name)
|
|
args = { start_date: start_date, end_date: end_date, facets: %i[prev_period] }
|
|
|
|
report = Report.find_cached(report_name, args)
|
|
if report.nil?
|
|
report = Report.find(report_name, args)
|
|
Report.cache(report) if report && report.error.blank?
|
|
end
|
|
|
|
return nil if report.nil? || report_error?(report) || report_data(report).nil?
|
|
|
|
current = period_value(type, report_data(report))
|
|
previous = report_prev_period(report)
|
|
|
|
{
|
|
type: type,
|
|
value: current,
|
|
previous_value: previous,
|
|
percent_change: compute_percent_change(current, previous),
|
|
report_type: report_name,
|
|
report_query: {
|
|
start_date: start_date.to_date.iso8601,
|
|
end_date: end_date.to_date.iso8601,
|
|
},
|
|
}
|
|
end
|
|
|
|
# Report.find returns a Report object
|
|
# Report.find_cached returns the as_json hash (symbol-keyed).
|
|
# The accessors below cope with either
|
|
def report_error?(report_or_hash)
|
|
report_or_hash.is_a?(Hash) ? report_or_hash[:error].present? : report_or_hash.error.present?
|
|
end
|
|
|
|
def report_data(report_or_hash)
|
|
report_or_hash.is_a?(Hash) ? report_or_hash[:data] : report_or_hash.data
|
|
end
|
|
|
|
def report_prev_period(report_or_hash)
|
|
if report_or_hash.is_a?(Hash)
|
|
report_or_hash[:prev_period]
|
|
else
|
|
report_or_hash.prev_period
|
|
end
|
|
end
|
|
|
|
def period_value(type, data)
|
|
return nil if data.empty?
|
|
|
|
ys = data.map { |point| point[:y] }
|
|
|
|
if type == :dau_mau
|
|
(ys.sum(&:to_f) / ys.size).round(1)
|
|
else
|
|
ys.sum(&:to_i)
|
|
end
|
|
end
|
|
|
|
def compute_percent_change(current, previous)
|
|
return nil if previous.blank? || previous.zero? || current.blank?
|
|
((current.to_f - previous) / previous * 100).round(2)
|
|
end
|
|
end
|