Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/27074-dataset-drill-window.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@objectstack/service-analytics': patch
'@objectstack/spec': patch
---

fix(service-analytics): a windowed dataset answer reports its window in `drillRanges`, so a drill list stays inside the picked period

Clause-②: yes (widening)
Metadata-protocol: none

A dataset query whose selection carries `timeDimensions[].dateRange` (a report's picked period) counts only that period's records, but its answer's `drillRanges` covered only the date dimensions it groups by. So a report run on "this quarter" and grouped by stage drilled to a list of every period's deals, and a month bucket cut by `last_30_days` drilled the whole month.

- **The window rides `drillRanges`.** A windowed dimension that is not grouped gets an entry on every row: the `[gte, lt)` the query filtered by. A windowed field that a bucket already bounds narrows that bucket's entry to the part inside the window, and adds no entry of its own, so an edge bucket drills only its in-window records. Drill hosts already filter by every `drillRanges` entry, by its `field`, so they need no change.
- **One window, not a second resolver.** The bounds come from the query's own steps: the preset resolved in the reference time zone, an explicit `[a, b]` as written, the shared whole-day lowering (a bare-day end runs to the start of the next day), and the engine's temporal storage form: ISO instants for a `datetime` field, `YYYY-MM-DD` days for a `date` field.
- **No entry when there is no exact spelling.** An explicit window whose end is an instant (compared with `<=`), or whose end is the last supported day (no upper bound), gets no entry, and the drill stays as wide as before. So does a field whose type is unknown under a non-UTC time zone, as for buckets.
- **Spec.** The `drillRanges` docblock on `AnalyticsResult` and its `.describe` text on the dataset answer schema say what the key now carries. No key is added.
2 changes: 1 addition & 1 deletion content/docs/references/api/analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ const result = AnalyticsEndpoint.parse(data);
| **dimensionFields** | `Record<string, string>` | optional | Drill-through sidecar: each equality-drillable dimension the answer is grouped by, dimension name to that dataset dimension's own `field` (a base-object field or a `relationship.field` path). Set only on a dataset answer (`POST /analytics/dataset/query`) that has rows and groups by at least one dimension with a `field` that is not `type: 'date'`; a date dimension is never listed (see `drillRanges`). Absent on a cube query answer. |
| **drillRawRows** | `Record<string, any>[]` | optional | Drill-through sidecar, aligned to `rows` by index: each `dimensionFields` dimension name to the stored value that row was grouped by (a select option's value, a lookup's record id), captured before label resolution rewrites the row to its display label. An exact-match drill filter is built from these, not from the labels in `rows`. Set exactly when `dimensionFields` is. |
| **drillRawTotals** | `Record<string, any>[][]` | optional | Drill-through sidecar for `totals`: entry `[i][j]` aligns to `totals[i].rows[j]` and holds the stored value of the `dimensionFields` dimensions that grouping groups by, so the grand-total grouping yields an empty map per row. Set only when `dimensionFields` is and the answer carries at least one `totals` grouping. |
| **drillRanges** | `Record<string, { field: string; gte: string; lt: string }>[]` | optional | Drill-through sidecar for time buckets, aligned to `rows` by index: date dimension name to the half-open range `[gte, lt)` that row's bucket covers. Bounds are `YYYY-MM-DD` calendar days, or ISO-8601 instants at midnight in the reference timezone when the source field is `datetime`; a row whose bucket value is null gets no entry for that dimension. Set only on a dataset answer that has rows and groups by at least one `type: 'date'` dimension with a `field` and a granularity; independent of `dimensionFields`. Absent on a cube query answer. |
| **drillRanges** | `Record<string, { field: string; gte: string; lt: string }>[]` | optional | Drill-through sidecar for date ranges, aligned to `rows` by index: date dimension name to the half-open range `[gte, lt)` that row's records fall in on that dimension's field. A time bucket gets the span it covers; a `timeDimensions[].dateRange` window gets the window the query filtered by on every row, or narrows the bucket entry on the same field to the part inside the window. Bounds are `YYYY-MM-DD` calendar days, or ISO-8601 instants when the source field is `datetime` (a bucket's at midnight in the reference timezone); a row whose bucket value is null gets no bucket entry, and a window whose end is an instant or the last supported day gets none. Set only on a dataset answer that has rows and groups by at least one `type: 'date'` dimension with a `field` and a granularity, or carries a `dateRange` on a dimension with a `field`; independent of `dimensionFields`. Absent on a cube query answer. |


---
Expand Down
Loading
Loading