Skip to content

Commit 6439f8b

Browse files
os-warrenclaude
andauthored
fix(service-analytics): compile a measure's field and filter, on both doors (#10411)
* fix(service-analytics): compile a measure's field and filter, on both doors A dataset measure declares `aggregate`, `field` and `filter`; the compiled SQL used only `aggregate`. 1. `{ aggregate: 'count', field: 'x' }` emitted `COUNT(*)`. The wrapper table took the resolved column and discarded it, so a measure asking how many rows carry a value counted every row it was handed. 2. `/api/v1/analytics/query` dropped every per-measure `filter`, and the dataset's definition-level `filter` with it. That door addresses the registered Cube directly; both filters live beside the cube in the dataset registry, and only `DatasetExecutor` — the dashboard's door — ever read them. One cube and one set of measure names answered two different numbers depending on which door the caller came in. `count` now takes its column (`*` still counts rows — it is the compiler's "no field declared" spelling), and a new conditional-aggregate table lowers a measure filter to a portable `CASE WHEN` (not `FILTER (WHERE …)`, which MySQL lacks). The dataset scope reaches the strategy through `getDatasetScope` on the context the analytics package builds for its own strategies — same shape and same registry as the neighbouring `getAllowedRelationships`, and no spec edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx * test(service-analytics): group the two-door agreement on a column the filters do not name Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx * chore(changeset): state the figures that move for an operator Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 6276651 commit 6439f8b

6 files changed

Lines changed: 579 additions & 7 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/service-analytics": patch
3+
---
4+
5+
Analytics measures are now compiled from everything they declare — `aggregate`, `field` **and** `filter` — on both the dashboard path and `POST /api/v1/analytics/query`.
6+
7+
**Reported figures change, and the new ones are the declared ones.** Two corrections, both of which move numbers a dashboard or an API consumer is already reading:
8+
9+
- A measure written `{ aggregate: 'count', field: 'some_column' }` used to compile to `COUNT(*)` and count **rows**. It now compiles to `COUNT("some_column")` and counts **non-null values**. Any such measure will report the same number as before or a **smaller** one, and a rate built on top of it (a numerator over a total) will drop accordingly — a "100%" tile whose column was mostly empty was reading its own denominator.
10+
- `POST /api/v1/analytics/query` used to drop every per-measure `filter`, and the dataset's definition-level `filter` with it, returning unfiltered aggregates under the author's measure names. It now applies both, so the endpoint answers what the dashboard already answered for the same cube. Figures pulled through the API — agent tools, exports, downstream reports — will move to the filtered values; a measure declaring `filter: { stage: 'closed_won' }` stops counting every row.
11+
12+
Measures that declare no `field` still compile to `COUNT(*)`, and a cube that is not a compiled dataset (an inferred or manifest cube) emits byte-for-byte the statement it did before. Measure filters lower to portable `CASE WHEN` conditional aggregates rather than `FILTER (WHERE …)`, which MySQL does not have.
13+
14+
If a saved figure or a screenshot disagrees with what the platform now reports, the new number is the one the metadata declares.

0 commit comments

Comments
 (0)