Skip to content

feat(analytics): metric-view runtime hook + format utilities + payload metadata (PR5)#488

Draft
atilafassina wants to merge 4 commits into
mainfrom
mv-hook
Draft

feat(analytics): metric-view runtime hook + format utilities + payload metadata (PR5)#488
atilafassina wants to merge 4 commits into
mainfrom
mv-hook

Conversation

@atilafassina

Copy link
Copy Markdown
Contributor

What

Ships PR5 of the Metric Views runtime: the client-facing useMetricView hook, 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):

  • Phase 0 — contracts. MetricColumnMeta/MetricViewsMetadata in shared; optional per-column metadata on the SSE result message; base MetricRegistry + useMetricView option/result types in appkit-ui.
  • Phase 1 — generator. Emit metric-views.ts (was .d.ts) carrying both the erasable declare module augmentation and a runtime export const metricViewsMetadata = {…} as const. Type-only header — no runtime side-effect import on the Node server.
  • Phase 2 — server. analytics({ metricViewsMetadata }) injection; the route stamps the responding metric's per-column slice (scoped to the requested measures/dimensions) into the result payload. Metadata is response decoration — it never enters the cache key or alters SQL.
  • Phase 3 — hook. useMetricView(key, opts) mirroring useAnalyticsQuery (SSE, abort-on-arg-change, autoStart) → { data, loading, error, errorCode, metadata }.
  • Phase 4 — formatters. Pure, React-free, tree-shakeable formatValue / formatLabel / toD3Format taking the format spec / column metadata as args.
  • Phase 5 — integration + docs. dev-playground /metric-views demo route (chart + table, labels/formats sourced from metadata, never hand-typed); docs in plugins/analytics.md (Plotly + ECharts examples) and development/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

  • Full backpressure green: build, docs:build, check:fix, typecheck, test (3697 passed / 1 skipped).
  • Live-validated against a real UC Metric View DESCRIBE (dogfood warehouse): the generated metric-views.ts was byte-for-byte identical to the committed artifact, and the /metric-views page renders correctly with metadata-driven formatting (verified in-browser).
  • Also fixed a pre-existing latent port collision: analytics.integration.test.ts and server.integration.test.ts both hardcoded port 9879; 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)

  • useMetricView doesn't surface warehouseStatus (unlike useAnalyticsQuery) — a cold warehouse shows an indefinite spinner rather than "warehouse starting…". The SSE already carries the events; the hook drops them.
  • Measure values arrive as strings ("8.18E7") while the generated row type says number — shared JSON_ARRAY delivery behavior with /query, worth a typed-coercion pass.
  • Close feat: UC Metric Views on Analytics #341 with links to the landed PRs once this reaches 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 targets main directly.

This pull request and its description were written by Isaac.

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>
@github-actions

Copy link
Copy Markdown
Contributor

🔬  Run evals on this PR  ·  Go to Evals Monitor →

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 758 KB (+5.1 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant