feat(analytics): useMetricViews - #488
Conversation
|
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 824 KB (+33 KB) | 288 KB (+12 KB) |
| Type declarations | 304 KB (+19 KB) | 104 KB (+6.9 KB) |
| Source maps | 1.6 MB (+77 KB) | 540 KB (+26 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 2.7 MB (+130 KB) | 936 KB (+44 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 (+502 B) | 2.5 KB | 89 KB (+502 B) | external | 283 KB (+1.5 KB) |
./beta |
44 KB (+4.6 KB) | 429 B (+198 B) | 45 KB (+4.8 KB) | external | 129 KB (+9.5 KB) |
./type-generator |
19 KB (+249 B) | 0 B | 19 KB (+249 B) | external | 55 KB (+883 B) |
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 | 29 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-options.js |
initial | 220 B |
./beta |
supervisor-api.js |
lazy | 184 B |
./beta |
databricks.js |
lazy | 132 B |
./beta |
index.js |
lazy | 113 B |
./type-generator |
index.js |
initial | 19 KB |
@databricks/appkit-ui ⚠️ over budget
npm tarball (packed): 335 KB (+30 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 381 KB (+22 KB) | 128 KB (+8.5 KB) |
| Type declarations | 225 KB (+19 KB) | 82 KB (+7.2 KB) |
| Source maps | 737 KB (+51 KB) | 243 KB (+19 KB) |
| CSS | 16 KB | 3.3 KB |
| Total | 1.3 MB (+93 KB) | 456 KB (+34 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 (+659 B) | 49 KB | 54 KB (+659 B) | 208 KB | 13 KB (+1.7 KB) |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
431 KB (+1.6 KB) | 49 KB | 479 KB (+1.6 KB) | 1.3 MB (+10 B) | 173 KB (+4.8 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 | 429 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.
useMetricViews
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>
🤖 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 30469835857 -R databricks/appkit -n appkit-template-0.48.0-pr.5995e79-mv-hook-488 -D appkit-pr-488 \
&& unzip -o "appkit-pr-488/appkit-template-0.48.0-pr.5995e79-mv-hook-488.zip" -d "appkit-pr-488" \
&& databricks apps init --template "appkit-pr-488"The template pins |
… 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>
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.