feat(analytics): add useMetricView client experience - #488
Conversation
|
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 855 KB (-294 B) | 298 KB (-235 B) |
| Type declarations | 313 KB | 108 KB (-9 B) |
| Source maps | 1.7 MB (-5.6 KB) | 557 KB (-1.5 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 2.8 MB (-5.9 KB) | 967 KB (-1.7 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
87 KB | 2.5 KB | 90 KB | external | 285 KB |
./beta |
48 KB | 459 B | 48 KB | external | 140 KB |
./type-generator |
20 KB | 0 B | 20 KB | external | 57 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 83 KB |
. |
utils.js |
initial | 4.0 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 32 KB |
./beta |
stream-manager.js |
initial | 5.8 KB |
./beta |
wide-event-emitter.js |
initial | 3.2 KB |
./beta |
databricks.js |
initial | 3.0 KB |
./beta |
configuration.js |
initial | 2.1 KB |
./beta |
service-context.js |
initial | 1.3 KB |
./beta |
client.js |
initial | 431 B |
./beta |
client-options.js |
initial | 219 B |
./beta |
supervisor-api.js |
lazy | 194 B |
./beta |
databricks.js |
lazy | 142 B |
./beta |
index.js |
lazy | 123 B |
./type-generator |
index.js |
initial | 20 KB |
@databricks/appkit-ui
npm tarball (packed): 339 KB (+2.0 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 387 KB (+2.2 KB) | 130 KB (+603 B) |
| Type declarations | 226 KB | 82 KB |
| Source maps | 744 KB (+4.8 KB) | 245 KB (+1.3 KB) |
| CSS | 16 KB | 3.3 KB |
| Total | 1.3 MB (+7.0 KB) | 461 KB (+1.9 KB) |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
5.0 KB | 49 KB | 54 KB | 208 KB | 14 KB |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
431 KB (+405 B) | 49 KB | 480 KB (+405 B) | 1.3 MB | 174 KB (+1.1 KB) |
./react/beta |
1.0 KB | 0 B | 1.0 KB | 0 B | 1.9 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 4.9 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 20 B |
./react |
index.js |
initial | 429 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 1.0 KB |
useMetricViews
7456890 to
613d5cb
Compare
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 31110646227 -R databricks/appkit -n appkit-template-0.55.0-pr.693f431-mv-hook-488 -D appkit-pr-488 \
&& unzip -o "appkit-pr-488/appkit-template-0.55.0-pr.693f431-mv-hook-488.zip" -d "appkit-pr-488" \
&& databricks apps init --template "appkit-pr-488"The template pins |
Freeze the three shared seams the metric-view hook/server/generator phases compile against: - S1: MetricColumnMeta + MetricViewsMetadata value types in packages/shared - S2: optional per-column metadata on the SSE result message + makeResultMessage - S3: base MetricRegistry, MetricKey, Infer* helpers, MetricFilter mirrors, and UseMetricViewOptions/UseMetricViewResult in appkit-ui hook types Types only (plus a makeResultMessage passthrough); existing /query callers are unchanged since metadata is optional. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
… metadata (PR5 1-4)
Implement the four parallel PR5 slices against the frozen phase-0 contracts:
- Generator (type-generator): emit metric-views.ts (was .d.ts) carrying both
the erasable declare-module MetricRegistry augmentation and a runtime
`export const metricViewsMetadata = {...} as const`. Header is a type-only
import (no runtime side-effect import on the Node server). Rename propagated
through METRIC_TYPES_FILE, mvOutFile, vite-plugin, CLI announce, and tests;
generated .ts added to Biome ignore.
- Server (analytics plugin): accept an injected `metricViewsMetadata` config
and stamp the responding metric's per-column slice (scoped to the requested
measures/dimensions) into the SSE result payload. Metadata is response
decoration — it never enters composeMetricCacheKey and never alters SQL.
- Hook (appkit-ui): `useMetricView(key, opts)` mirroring useAnalyticsQuery
(SSE, abort-on-arg-change, autoStart), returning
{ data, loading, error, errorCode, metadata }.
- Formatters (appkit-ui js): pure, React-free, tree-shakeable formatValue /
formatLabel / toD3Format taking the format spec / column metadata as args.
Also fix a pre-existing latent port collision: analytics.integration.test.ts
and server.integration.test.ts both hardcoded port 9879; under the added
metric-test weight they could bind concurrently in the shared vitest worker
pool, so an analytics request hit the server-plugin app and 404'd. Switch the
analytics integration test to an OS-assigned ephemeral port (port: 0), matching
the files plugin integration test.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…se 5)
Integrate the metric-view runtime in dev-playground and document it:
- Regenerate the generated artifact as shared/appkit-types/metric-views.ts
(delete the legacy .d.ts). Verified byte-for-byte identical to a live
`generate-types` DESCRIBE against a real UC Metric View (warehouse
dd43ee29fedd958d, dogfood): display_name/format/description genuinely flow
from the UC YAML through typegen into the runtime metricViewsMetadata const.
- Inject the const server-side: analytics({ metricViewsMetadata }).
- Add a /metric-views demo route calling useMetricView("revenue", …) with
timeGrain/timeDimension, rendering a chart + table whose labels and value
formats come from the payload metadata (never hand-typed), degrading
gracefully when metadata is absent.
- Docs: extend plugins/analytics.md with the useMetricView + format-utility +
metricViewsMetadata injection story (Plotly + ECharts examples), and fix the
stale metric-views.d.ts references in development/type-generation.md.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
… builder to /js
Move the twelve-operator MetricFilter grammar out of react/hooks/types.ts into a
canonical, framework-agnostic js/metric-filter/ module and add a toMetricFilter
builder that compiles a { dimension -> value(s) } shorthand into a MetricFilter
(scalar -> equals, array -> in, omit undefined/empty). react/hooks/types.ts now
re-exports the types so the /react public surface and UseMetricViewOptions.filter
are unchanged. Wire the dev-playground metric-views route's buildFilter onto
toMetricFilter, keeping only the app-specific cross-filter facet-exclusion local.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Adds two public chart props, inherited by every chart type via the factory:
- onDataClick?(datum: ChartClickDatum): fire-and-forget click callback. base.tsx
builds a memoized internal echarts onEvents={{ click }} only when the handler is
set (no idle listener), mapping raw params via the pure mapToDatum. Pointer-only
(canvas) — documented to require a keyboard-accessible equivalent.
- selected?: string | string[]: controlled, name-based visual emphasis. base.tsx
runs the pure applySelectionEmphasis transform over the built option so matching
bar/pie-donut categories stay prominent and the rest dim; no-op when unset.
ChartClickDatum is the only new public (barrel) symbol; mapToDatum,
applySelectionEmphasis and SelectionEmphasisOptions are internal. echarts types
stay out of the public API (datum.raw is unknown). Phases 1 and 2 are committed
together so the producer helpers have their consumer (satisfies knip).
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…egment charts
Wire the new appkit-ui chart props into the metric-views demo: the region BarChart
and segment DonutChart get onDataClick={(d) => setDimension(dim, d.name)} — reusing
the same setDimension the table row-click uses — and selected={selection[dim]} so the
clicked category is emphasized. LineChart, Table, and the existing (keyboard-accessible)
table row-click are unchanged.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
… review cleanup Address adversarial-review findings on the useMetricView / metric-route branch: - Type soundness: infer rows from the selected measure/dimension tuples (PickMetricRow) and correlate timeDimension/timeGrain to temporal dims only. - Formatting: preserve every currency symbol the generator emits end-to-end and keep bigint precision (no Number() rounding). - Cache correctness: stamp fresh per-column metadata AFTER the cached execute() so a cache hit never serves stale labels/formats after a redeploy. - Charts: guard selected="" as a no-op, split [x,y] click tuples into x/y, and memoize onEvents on handler presence (no listener thrash per SSE tick). - Typegen: sweep a stale sibling metric-views.d.ts on upgrade and reject a .d.ts mvOutFile. - Drop the unused public notify export and the fake "Write back" demo; remove the dead autoStart option; align AnalyticsStreamMessage; tighten the biome ignore; add tests + comment cleanup. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Drop comments that restated adjacent JSDoc/functions: - base.tsx: the inline-handler re-subscribe rationale lived in both the `interactive` and `onEvents` comments; keep it once at `onEvents` (where the subscription happens) and point the `interactive` note at it. - use-metric-view.ts: result-branch comment re-explained metadata narrowing already documented on `asMetricMetadata`; defer to that doc. - js/format/index.ts: call-site comment restated `currencyPrefix`'s own JSDoc. - render-types.ts: convert `generateMetricTypeDeclarations`'s // block to /** */ so the exported function's rationale surfaces on IDE hover. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
useMetricView never implemented autoStart — the row was copied from useAnalyticsQuery's options table. The hook's effect calls start() unconditionally and UseMetricViewOptions has no such field. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
- Centralize analytics SSE parsing across React hooks - Share metric filter types and runtime operator vocabulary - Reuse metadata and label formatters; refresh size baseline Signed-off-by: Atila Fassina <atila@fassina.eu>
- analytics.md: make the Plotly and ECharts chart-library references links to plotly.com/javascript and echarts.apache.org; minor wording tidy (hardcode). - charts/types.ts: trim the ChartClickDatum doc comment. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Phase 1 of typegen-ci-resilient-describe. In blocking (`--wait`) mode the type generator now suppresses the `.d.ts` write on ANY degraded result (query `result: unknown` or degraded metric), leaving the committed types untouched as the CI fallback of record, then throws as before. The prior path wrote degraded (`unknown`) declarations first and threw after, which clobbered good committed types on a fresh CI checkout — including via the auth/timeout/bad-id/DELETED fatal-degrade path. Non-blocking mode is unchanged (still writes degraded types for the detached worker to refresh). Tests inverted to assert no-write-on-degrade while preserving throw + behavioral assertions; adds coverage for the query-side fatal-degrade clobber-prevention case. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Phase 2 of typegen-ci-resilient-describe. Adds a pure classifyBlockingFailure(error) → "deterministic" | "environmental" to type-generator/errors.ts, building on the existing getErrorStatus and isConnectivityError helpers. Deterministic (build must crash regardless of committed types): HTTP 404 (bad warehouse id) and 400 (malformed request), checked first and walked through cause/AggregateError chains. Environmental (has-types gate applies later): 401/403 auth, connectivity, DELETED/DELETING, wait-timeout, and any unrecognized failure (the default). The auth status set is a one-line change point for the auth-owning team. No behavior change to isConnectivityError. Adds tests/errors.test.ts. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…wait Phase 3 of typegen-ci-resilient-describe (join point). Wires the classifyBlockingFailure taxonomy into the reordered blocking write path so `--wait` degrades gracefully on environmental failures instead of always crashing: - Deterministic failures (SQL syntax, HTTP 404/400) still crash the build. - Environmental failures (401/403 auth, connectivity, DELETED/DELETING, wait-timeout, unrecognized) now flow through a has-types gate: if committed analytics/metric-views .d.ts exist, skip the (already-suppressed) write, emit one loud greppable stderr warning naming the coarse cause (auth blocked / warehouse unreachable / warehouse unavailable) + warehouse id, and exit 0 using the committed types as the fallback of record. If no committed types exist, crash with a generic 'run generate-types --wait locally' remedy. Serving types are excluded from the gate (gitignored, degrade independently). Non-blocking mode is unchanged. Threads deterministic-vs-environmental and a coarse cause label out of the query + metric preflights. Adds gate-matrix coverage: environmental+present (per cause) → warning+exit0, environmental+ absent → crash, deterministic (404/400/syntax) → crash regardless of types, partial presence, serving-exclusion, and CI-safe (ANSI-free) warning output. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Phase 4 of typegen-ci-resilient-describe. Updates the type-generation docs to describe the committed-types fallback and two-bucket failure taxonomy for blocking (`--wait`) builds: committed .d.ts as the fallback of record, --wait never overwriting good types with degraded ones, deterministic failures (SQL syntax / 404 / 400) crashing vs. environmental failures (auth / connectivity / deleted / timeout) gating on committed-type presence, the loud stderr warning, and the run-locally remedy for a first build with no committed types. Notes the metric-views-only edge case (empty analytics.d.ts satisfies the gate). Refreshes the metric-view section to reference the same taxonomy instead of the old always-fail framing. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
…anges
Wash of the typegen-ci-resilient-describe branch: strips loop-process
"Phase N" labels from comments, test names, and describe titles (keeping
the semantic text), removes an unnecessary comment / empty else-block /
useless default parameter, rewrites two stale+duplicated write-suppression
comments to match the actual behavior, and converts errors.test.ts's
`(error as any)` casts to the sibling `Object.assign(new Error(...), { ... })`
idiom. Comments, names, and test-setup style only — no logic or assertion
changes (537 tests still pass).
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Brings in main's revert of PR #502 (#509), which relocated the typegen cache back to node_modules/.databricks. This branch was cut from the #502 merge commit and inherited that surface without ever depending on it, so the sync is a clean subtraction: cache-paths.ts, the committed apps/dev-playground/.appkit/ fixtures, the cache-serialization tests, and the warehouse-less-deploy docs all drop out, while the metric-view runtime (useMetricView, formatters, metric-views.ts codegen) is untouched. bundle-size-baseline.json conflicted because both sides regenerated it; resolved by taking main's, to be regenerated against the merged tree. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
The merge took main's baseline to resolve the conflict; this remeasures against the post-merge build so the numbers reflect the actual tree. `size:compare` now reports no change. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu>
useMetricViewsSigned-off-by: Atila Fassina <atila@fassina.eu>
Condense explanatory comments on the metric view cross-filter demo and the metadata-stamping invariant, dropping development-phase framing in favour of stating the invariant directly. Comments only, no behaviour change. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu> # Conflicts: # docs/docs/development/type-generation.md # packages/appkit-ui/src/react/hooks/index.ts # packages/appkit-ui/src/react/hooks/types.ts # packages/appkit/src/plugins/analytics/analytics.ts # packages/appkit/src/plugins/analytics/mv/metadata.ts # packages/appkit/src/plugins/analytics/tests/metric.test.ts # packages/appkit/src/plugins/analytics/types.ts # packages/appkit/src/type-generator/index.ts # packages/appkit/src/type-generator/mv-registry/render-types.ts # packages/appkit/src/type-generator/tests/index.test.ts # packages/appkit/src/type-generator/tests/mv-registry.test.ts # packages/appkit/src/type-generator/tests/sync-metric-views-types.test.ts # packages/appkit/src/type-generator/vite-plugin.ts # packages/shared/src/index.ts # packages/shared/src/metric-metadata.ts # packages/shared/src/sse/analytics.ts
Define the no-metadata case by its concrete equivalence (envelope-identical to /query) rather than by reference to a prior state of the route. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Delegating to formatLabel dropped the [^a-zA-Z0-9_-] strip the wrapper previously applied. data-table passes a raw column.id and defaultFilterColumn that never pass through SAFE_KEY_REGEX, so the label echoed arbitrary input; the accompanying test had encoded that as expected. Restore the strip and correct the expectation. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
The `interactive` flag already carries this explanation where it is computed, and the ChartOptions prop owns the mechanism. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu>
The useMetricView result table and the format-utilities section both referred to `MetricColumnMeta`, which is not exported anywhere. The hook returns `Record<string, MetricViewColumnDisplay>`, so a reader importing the documented name got a compile error. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
mapToDatum treated every array-valued datum as an [x, y] tuple. Heatmap data items are [xIndex, yIndex, value] INDEX triples, so onDataClick received the y *index* as `value` and lost the actual cell value; radar items hold one value per indicator and were reduced to their second component. Branch on `params.seriesType` instead of the datum's shape: - heatmap reports the cell value, and resolves the two indices back to their axis category labels (the raw index when labels are absent), so a caller gets the labels the user sees rather than positions. BaseChart passes the normalized axis data for this, held in a ref so the click subscription still does not re-attach when the data changes. - radar reports `value: null` rather than an arbitrary component; the full vector stays reachable through `raw`. Line/scatter tuples and scalar bar/pie data are unchanged. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
The JSON_ARRAY wire path delivers every numeric cell as a string (the SQL
connector copies data_array cells verbatim), so a BIGINT or large DECIMAL
measure reaches formatValue as an integer-shaped string. Coercing that
through Number() rounds it: formatValue("9007199254740993", "#,##0")
rendered 9,007,199,254,740,992.
Format oversized integer strings exactly via BigInt instead. This is also
what the existing bigint branch was written for — JSON.parse cannot yield
a bigint, so that branch had no reachable caller and the precision it
protects was lost on the path that actually runs. Both now share one
formatBigInt helper.
Only plain optionally-signed digit strings qualify; fractions and
exponents stay on the Number path, where float semantics are correct.
Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Selecting a NULL group key built a predicate that matched nothing. The value was stringified on the way into the filter, so a NULL region compiled to `region equals 'null'` — a literal string comparison against a column that is NULL. toMetricFilter now accepts `null` and compiles it to the grammar's `notSet`, which the server already renders as IS NULL. Note the asymmetry: `undefined` means no filter on that dimension, `null` means filter to the rows where it IS NULL. A guard in toMetricFilter alone is not enough, since the value was already a string by the time it arrived, so the playground's producers pass the real `null` through: table rows, the dropdown domains (NULL sorted last, labelled "(none)" behind a sentinel because Radix rejects empty item values), and the filter chips. Chart clicks needed it too — normalizeChartData maps a NULL category to "", which would otherwise have compiled to `equals ''`. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Tighten the doc comments across the metric-view client surface: drop restated rationale, redundant per-field descriptions, and prose that repeated what the signature already says. Comments only — no behavior, type, or formatting changes. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu> # Conflicts: # bundle-size-baseline.json
There was a problem hiding this comment.
Pull request overview
Adds the Metric Views client experience to AppKit UI and shared packages: a new useMetricView React hook (SSE-driven, typed rows + metadata), shared SSE lifecycle handling with useAnalyticsQuery, plus framework-agnostic format/filter utilities and chart interaction APIs. Also wires generated Metric View metadata into the dev-playground server and provides an end-to-end playground page + docs + focused tests.
Changes:
- Introduce shared Metric Filter AST + a
toMetricFilterhelper, and re-export these types consistently acrossshared,appkit, andappkit-ui. - Add
useMetricViewand consolidate analytics SSE parsing/handling for both metric-view and analytics-query hooks. - Add chart click + selection emphasis support and a dev-playground Metric Views page demonstrating cross-filtering with metadata-driven formatting/labels.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/metric-filter.ts | New shared Metric Filter operator vocabulary + filter AST types. |
| packages/shared/src/index.ts | Re-export metric-filter from shared barrel. |
| packages/appkit/src/plugins/analytics/types.ts | Switch metric-filter types to shared source-of-truth; expand analytics SSE error typing. |
| packages/appkit/src/plugins/analytics/tests/types.test.ts | Type-level test ensuring analytics re-exports shared metric-filter types. |
| packages/appkit/src/plugins/analytics/tests/analytics.integration.test.ts | Use ephemeral port + wait-for-listening helper to reduce integration-test flakiness. |
| packages/appkit/src/plugins/analytics/mv/constants.ts | Re-export filter-operator vocab/subsets from shared module. |
| packages/appkit-ui/src/react/lib/format.ts | Delegate label humanization to JS formatter with input sanitization. |
| packages/appkit-ui/src/react/lib/format.test.ts | Add coverage for label formatting + sanitization behavior. |
| packages/appkit-ui/src/react/hooks/use-metric-view.ts | New useMetricView hook implementation using shared analytics SSE lifecycle. |
| packages/appkit-ui/src/react/hooks/use-analytics-query.ts | Refactor to use shared SSE handlers (analytics-sse.ts). |
| packages/appkit-ui/src/react/hooks/types.ts | Add Metric View registry-driven typing utilities + UseMetricView* types. |
| packages/appkit-ui/src/react/hooks/index.ts | Export new Metric View-related types and useMetricView. |
| packages/appkit-ui/src/react/hooks/analytics-sse.ts | New shared SSE parsing/handling helpers for analytics hooks. |
| packages/appkit-ui/src/react/hooks/tests/use-metric-view.types.test.ts | Compile-time probe test for useMetricView type inference/correlation. |
| packages/appkit-ui/src/react/hooks/tests/use-metric-view.test.ts | Behavioral tests for useMetricView (payload, results, errors, abort, warehouse status publishing). |
| packages/appkit-ui/src/react/hooks/tests/analytics-sse.test.ts | Unit tests for shared analytics SSE parsing/handling helpers. |
| packages/appkit-ui/src/react/charts/utils.ts | Re-export shared formatLabel; add click datum mapping helper for ECharts. |
| packages/appkit-ui/src/react/charts/types.ts | Add chart click datum type + onDataClick + controlled selected props. |
| packages/appkit-ui/src/react/charts/options.ts | Add interactive line clickability + selection emphasis transform for categorical series. |
| packages/appkit-ui/src/react/charts/index.ts | Export new chart interaction/selection APIs. |
| packages/appkit-ui/src/react/charts/base.tsx | Wire click handling + selection emphasis into base chart; stabilize event subscription. |
| packages/appkit-ui/src/react/charts/tests/utils.test.ts | Add tests for click datum normalization. |
| packages/appkit-ui/src/react/charts/tests/options.test.ts | Add tests for interactive line options + applySelectionEmphasis behavior. |
| packages/appkit-ui/src/js/metric-filter/index.ts | New JS helper toMetricFilter + re-export shared metric-filter types. |
| packages/appkit-ui/src/js/metric-filter/index.test.ts | Tests for toMetricFilter behavior and type re-exports. |
| packages/appkit-ui/src/js/index.ts | Export new JS format + metric-filter modules. |
| packages/appkit-ui/src/js/format/index.ts | New pure formatting utilities: formatValue, formatLabel, toD3Format (incl bigint-safe paths). |
| packages/appkit-ui/src/js/format/index.test.ts | Tests for format utilities, including bigint precision and currency-prefix coverage. |
| docs/docs/plugins/analytics.md | Document useMetricView, metadata injection, and formatting utilities with examples. |
| bundle-size-baseline.json | Update bundle size baseline after adding new APIs/utilities. |
| biome.json | Exclude generated metric-views artifact from Biome formatting/linting. |
| apps/dev-playground/shared/appkit-types/metric-views.ts | Generated registry augmentation + new exported metricViewsMetadata constant. |
| apps/dev-playground/server/index.ts | Wire generated metricViewsMetadata into analytics({ metricViewsMetadata }). |
| apps/dev-playground/client/src/routeTree.gen.ts | Add route entry for Metric Views playground page. |
| apps/dev-playground/client/src/routes/metric-views.route.tsx | New end-to-end Metric Views playground page demonstrating cross-filtering + metadata-driven formatting. |
| apps/dev-playground/client/src/lib/nav.ts | Add navigation entry for Metric Views page. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Atila Fassina <atila@fassina.eu>
…r UI Radix `Select` throws when an item value is the empty string, and `toDimensionOptions` keeps `""` as a real value, so a dimension carrying an empty string crashed the filter dropdown on render. Add an `EMPTY` sentinel alongside `ALL`/`NONE` and decode it back to `""` so it still compiles to an `equals ''` filter, distinct from the NULL group's `IS NULL`. `FilterBadge` also interpolated the raw selection, rendering "Region: null" for a NULL group key while the active-filter chip on the same page showed "(none)" for it. Route the badge through `toDisplayLabel` so both read the same. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
The note said the memo "enforces referencial equality" — misspelled, and backwards: serializing to a string is what lets `start`'s dependency check compare the request by value instead of by object identity. Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu>
TL;DR
Delivers the complete Metric Views client experience on top of #512: applications can query typed Metric Views, format results from catalog metadata, and build interactive cross-filtering charts without duplicating query or presentation logic.
Delivered
useMetricViewwith inferred selected-row types, metadata, loading/error state, cancellation, HMR refresh, and shared warehouse-readiness reporting.useMetricViewanduseAnalyticsQuery.formatValue,formatLabel,toD3Format, andtoMetricFilterutilities.onDataClickandselected) for cross-filter interactions.Stacked on #512.