Class: ChronoForge::Dashboard::AnalyticsQuery

Inherits:
Object
  • Object
show all
Defined in:
app/queries/chrono_forge/dashboard/analytics_query.rb

Overview

Time-bucketed completion/failure/duration metrics over a window.

The aggregation runs in the database (GROUP BY a per-day bucket), so it returns one row per day regardless of how many workflows match — it never loads workflow rows into Ruby. The bucket and duration expressions are adapter-specific (SQLite / PostgreSQL / MySQL), chosen once per query.

Scale note: completed workflows are bucketed (and windowed) by ‘completed_at`, which is the leading-range column of the existing `[state, completed_at]` index, so the heavy path (millions of completed rows) is an index range scan. Failed workflows have no `completed_at`, so they are bucketed by `updated_at` (when they reached the failed state) —a tiny set in practice. The two terminal axes are merged per day.

Rates here are WORKFLOW-level, not execution-log level: a high count of failed *execution logs* is normal durably_repeat catch-up churn, whereas a failed workflow is a real incident. This query only ever counts workflows.

Defined Under Namespace

Classes: Bucket

Constant Summary collapse

WINDOWS =
{"24h" => 1.day, "7d" => 7.days, "30d" => 30.days}.freeze
DEFAULT_WINDOW =
"7d"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(window: DEFAULT_WINDOW, job_class: nil, now: Time.current) ⇒ AnalyticsQuery

Returns a new instance of AnalyticsQuery.



29
30
31
32
33
34
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 29

def initialize(window: DEFAULT_WINDOW, job_class: nil, now: Time.current)
  @window = WINDOWS.key?(window.presence) ? window : DEFAULT_WINDOW
  @job_class = job_class.presence
  @now = now
  @since = now - WINDOWS.fetch(@window)
end

Instance Attribute Details

#job_classObject (readonly)

Returns the value of attribute job_class.



36
37
38
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 36

def job_class
  @job_class
end

#sinceObject (readonly)

Returns the value of attribute since.



36
37
38
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 36

def since
  @since
end

#windowObject (readonly)

Returns the value of attribute window.



36
37
38
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 36

def window
  @window
end

Instance Method Details

#bucketsObject

Per-day buckets within the window, oldest first.



41
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 41

def buckets = data[:buckets]

#top_errors(limit: 8) ⇒ Object

The most frequent error classes in the window, highest first, as an ordered => count hash. Scoped to the class when set.



49
50
51
52
53
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 49

def top_errors(limit: 8)
  rel = ChronoForge::ErrorLog.where(created_at: @since..@now)
  rel = rel.joins(:workflow).where(ChronoForge::Workflow.table_name => {job_class: @job_class}) if @job_class
  rel.group(:error_class).order(Arel.sql("COUNT(*) DESC")).limit(limit).count
end

#totalsObject

Roll-ups over the whole window: counts, workflow-level rates (nil when no terminal workflows), and average completed duration in seconds.



45
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 45

def totals = data[:totals]

#windowsObject



38
# File 'app/queries/chrono_forge/dashboard/analytics_query.rb', line 38

def windows = WINDOWS.keys