feat(analytics): useMetricViews#488
Draft
atilafassina wants to merge 8 commits into
Draft
Conversation
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>
Contributor
|
Contributor
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 787 KB (+3.8 KB) | 274 KB (+1.4 KB) |
| Type declarations | 287 KB (+2.1 KB) | 98 KB (+1.0 KB) |
| Source maps | 1.5 MB (+9.7 KB) | 513 KB (+3.4 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 2.6 MB (+16 KB) | 889 KB (+5.8 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
85 KB (+309 B) | 2.5 KB | 88 KB (+309 B) | external | 280 KB (+1.1 KB) |
./beta |
40 KB | 231 B | 40 KB | external | 119 KB |
./type-generator |
19 KB (+186 B) | 0 B | 19 KB (+186 B) | external | 55 KB (+729 B) |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 81 KB |
. |
utils.js |
initial | 4.0 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 30 KB |
./beta |
databricks.js |
initial | 5.8 KB |
./beta |
service-context.js |
initial | 3.2 KB |
./beta |
client-options.js |
initial | 220 B |
./beta |
databricks.js |
lazy | 128 B |
./beta |
index.js |
lazy | 103 B |
./type-generator |
index.js |
initial | 19 KB |
@databricks/appkit-ui ⚠️ over budget
npm tarball (packed): 321 KB (+26 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 370 KB (+20 KB) | 124 KB (+7.9 KB) |
| Type declarations | 216 KB (+15 KB) | 78 KB (+6.0 KB) |
| Source maps | 715 KB (+46 KB) | 235 KB (+17 KB) |
| CSS | 16 KB | 3.3 KB |
| Total | 1.3 MB (+82 KB) | 441 KB (+31 KB) |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
4.9 KB (+584 B) | 49 KB | 54 KB (+584 B) | 208 KB | 13 KB (+1.5 KB) |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
429 KB (+1.5 KB) | 49 KB | 478 KB (+1.5 KB) | 1.3 MB (+5 B) | 168 KB (+4.6 KB) |
./react/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 4.8 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 20 B |
./react |
index.js |
initial | 427 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 20 B |
⚠️ Over budget: a package's shipped tarball, or a browser entry's consumer bundle (deps included), grew by more than 5% (and >10 KB). This check will fail — reduce the size, or acknowledge the increase by updatingbundle-size-baseline.json.
… 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>
useMetricViews
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Ships PR5 of the Metric Views runtime: the client-facing
useMetricViewhook, pure format utilities, and the build-inlined-metadata → payload pipeline. Stacked on #474 (PR2 route + SQL engine); review/merge that first.Delivered as one branch built in five phases (each adversarially reviewed + gated on
build/docs:build/check:fix/typecheck/test):MetricColumnMeta/MetricViewsMetadatainshared; optional per-columnmetadataon the SSEresultmessage; baseMetricRegistry+useMetricViewoption/result types inappkit-ui.metric-views.ts(was.d.ts) carrying both the erasabledeclare moduleaugmentation and a runtimeexport const metricViewsMetadata = {…} as const. Type-only header — no runtime side-effect import on the Node server.analytics({ metricViewsMetadata })injection; the route stamps the responding metric's per-column slice (scoped to the requested measures/dimensions) into theresultpayload. Metadata is response decoration — it never enters the cache key or alters SQL.useMetricView(key, opts)mirroringuseAnalyticsQuery(SSE, abort-on-arg-change,autoStart) →{ data, loading, error, errorCode, metadata }.formatValue/formatLabel/toD3Formattaking the format spec / column metadata as args./metric-viewsdemo route (chart + table, labels/formats sourced from metadata, never hand-typed); docs inplugins/analytics.md(Plotly + ECharts examples) anddevelopment/type-generation.md.Why
Metadata (
display_name,format,description) lives once in the UC Metric View YAML and flows end-to-end — typegen → build-inlined const → server injection → payload → hook → formatter — so a chart renders$81,817,716.00/ "Annual Recurring Revenue" with no hand-typed format strings. Payload-carried, so it's client-agnostic (headless/non-React consumers get it too).Validation
build,docs:build,check:fix,typecheck,test(3697 passed / 1 skipped).metric-views.tswas byte-for-byte identical to the committed artifact, and the/metric-viewspage renders correctly with metadata-driven formatting (verified in-browser).analytics.integration.test.tsandserver.integration.test.tsboth hardcoded port9879; under concurrent load in the shared vitest worker pool an analytics request could hit the server-plugin app and 404. Switched the analytics test to an ephemeral port (port: 0), matching the files-plugin test.Follow-ups (not in this PR)
useMetricViewdoesn't surfacewarehouseStatus(unlikeuseAnalyticsQuery) — a cold warehouse shows an indefinite spinner rather than "warehouse starting…". The SSE already carries the events; the hook drops them."8.18E7") while the generated row type saysnumber— shared JSON_ARRAY delivery behavior with/query, worth a typed-coercion pass.main.Base
Stacked on #474 (metric-view route + SQL engine), which merged on 2026-07-23. This branch has been rebased onto the current
main(only the 3 PR5 commits replayed — no PR2 double-apply), so it targetsmaindirectly.This pull request and its description were written by Isaac.