mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:39:43 +08:00
Adds basic usage metrics for **workflows**, **automations**, and **data explorer** so we can measure adoption across all sites, following the pattern established for Kanban (dev topic `t/185911`). Each plugin registers stats via the freeform `stat_type` API, so the values flow automatically into our site statistics pipeline. No infrastructure changes are needed. ## Metrics For each feature: `total` (running count), `created`, `edited`, `executed` (distinct objects run), and `executions` (total run count) — each windowed over `last_day / 7_days / 30_days / previous_30_days`, with a lifetime `count` on `total` and `executions`. Resulting columns: | | Workflows | Automations | Data Explorer | |---|---|---|---| | total | `workflows_total_count` | `automations_total_count` | `de_queries_total_count` | | created | `workflows_created_*` | `automations_created_*` | `de_queries_created_*` | | edited | `workflows_edited_*` | `automations_edited_*` | `de_queries_edited_*` | | executed | `workflows_executed_*` | `automations_executed_*` | `de_queries_executed_*` | | executions | `workflows_executions_*` | `automations_executions_*` | `de_executions_*` | ## How executions are counted Automations already have a daily rollup (`discourse_automation_stats`), so they reuse it. Workflows and Data Explorer get a small daily rollup table each (`discourse_workflows_execution_stats`, `data_explorer_query_stats`) recording `total_runs` per object per day. This deliberately avoids two traps: - **Workflow executions are purged** after `workflow_executions_retention_days` (default 30), and the purged rows carry heavy payloads (full node-graph snapshot + step I/O). Counting from a lightweight rollup keeps `previous_30_days`/lifetime metrics valid without retaining those payloads, and decouples the metrics from a per-site retention setting. - **Data Explorer has no run log** — only a single `last_run_at`. The rollup gives us real execution counts and a durable `previous_30_days` window. Workflow runs are recorded via an `after_create` on `Execution` (one row per execution, rate-limited executions excluded). Data Explorer runs go through a new `Query#record_run!` used by the three run sites. ## Data Explorer `updated_at` fix Query runs previously did `query.update!(last_run_at:)`, which bumped `updated_at` on every run and made "edited" indistinguishable from "executed". `record_run!` now uses `update_columns`, so `updated_at` reflects genuine edits again. Default (unpersisted) queries are still persisted on first run as before, and the `DeleteHiddenQueries` job is unaffected (it also gates on `last_run_at`). ## Core change `register_stat` deduplicated by name only, so the three plugins couldn't all register generic names like `total`/`executions` — whichever activated first won and the rest were silently dropped. The identity check now includes `stat_type`, so a name can be reused across stat types (columns stay unique because they are prefixed with the stat type). Kanban avoided this only because its names happened to be globally unique. ## Notes for review - **Single `de` stat_type** (yielding `de_queries_*` and `de_executions_*`) rather than splitting into `de_queries`/`de_executions` — happy to change if you'd rather query by two separate `stat_type_freeform` values. cc measurement folks. - `structure.sql` and model annotations were hand-updated (local box is on PG17, which can't run the temp-DB dump/annotate tasks); CI will verify. ## Tests - New specs for all three `Statistics` modules and both rollup models (`.log`, the `after_create`/`record_run!` hooks, the `updated_at` behavior). - New core spec covering same-name/different-`stat_type` registration.
16 lines
583 B
Ruby
Vendored
16 lines
583 B
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module PeriodCountHelper
|
|
def period_counts(scope, column, count: true, &aggregate)
|
|
aggregate ||= ->(relation) { relation.count }
|
|
col = scope.arel_table[column]
|
|
result = {
|
|
last_day: aggregate.call(scope.where(col.gt(1.day.ago))),
|
|
"7_days": aggregate.call(scope.where(col.gt(7.days.ago))),
|
|
"30_days": aggregate.call(scope.where(col.gt(30.days.ago))),
|
|
previous_30_days: aggregate.call(scope.where(col.between(60.days.ago..30.days.ago))),
|
|
}
|
|
result[:count] = aggregate.call(scope) if count
|
|
result
|
|
end
|
|
end
|