feat(analytics): metric-view runtime hook + format utilities + payload metadata (PR5)#488
Draft
atilafassina wants to merge 4 commits into
Draft
feat(analytics): metric-view runtime hook + format utilities + payload metadata (PR5)#488atilafassina wants to merge 4 commits into
atilafassina wants to merge 4 commits into
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
npm tarball (packed): 307 KB (+11 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 360 KB (+10 KB) | 120 KB (+3.9 KB) |
| Type declarations | 209 KB (+8.2 KB) | 75 KB (+3.1 KB) |
| Source maps | 692 KB (+23 KB) | 227 KB (+8.4 KB) |
| CSS | 16 KB | 3.3 KB |
| Total | 1.2 MB (+41 KB) | 425 KB (+15 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.8 KB (+469 B) | 49 KB | 54 KB (+469 B) | 208 KB | 13 KB (+1.2 KB) |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
429 KB (+923 B) | 49 KB | 477 KB (+923 B) | 1.3 MB (+1 B) | 166 KB (+2.7 KB) |
./react/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 4.6 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 |
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.