Skip to content

feat(appkit-ui): generic ResourceStatusProvider + indicator (with analytics adapter)#416

Merged
MarioCadenas merged 9 commits into
mainfrom
mario/appkit-ui-resource-status-indicator
Jun 5, 2026
Merged

feat(appkit-ui): generic ResourceStatusProvider + indicator (with analytics adapter)#416
MarioCadenas merged 9 commits into
mainfrom
mario/appkit-ui-resource-status-indicator

Conversation

@MarioCadenas
Copy link
Copy Markdown
Collaborator

@MarioCadenas MarioCadenas commented Jun 3, 2026

Summary

Adds a plugin-agnostic readiness aggregator and a drop-in Sonner toast indicator so any plugin (SQL warehouses today; Lakebase / model-serving later) can publish "I'm warming up" status into a shared provider, and a single global affordance surfaces the worst pending state.

Also fixes the misleading shimmering chart skeleton during a 30s–2min warehouse cold start — charts and the global indicator only react once SSE reports a real warehouse state.

What's new in @databricks/appkit-ui/react

  • ResourceStatusProvider — aggregator context (mount once near root).
  • useResourceStatus(filter?) — read the aggregate; optional { kind } filter.
  • useResourceStatusPublisher(id, label, { kindHint? }) — plugins publish ResourceStatus snapshots. Severity ("pending" | "warning" | "error") drives cross-kind ordering. kindHint registers slots for kind-scoped activeCount but does not drive the indicator until a real status arrives.
  • ResourceStatusIndicator — mounts its own <Toaster /> and drives a sticky Sonner toast (toast.loading / toast.error, default top-right). Per-kind copy via renderers={...}, full render escape hatch.
  • useResourceStatusToaster — hook-only variant when the app already mounts its own <Toaster />. Exported from hooks and react.

Hook surface change

useAnalyticsQuery now returns warehouseStatus and auto-publishes to the provider via an internal useAnalyticsWarehousePublisher adapter — every chart's warmup shows up globally with zero per-chart wiring.

UX behavior

Phase Global toast Chart (ChartWrapper)
Query starts (pre-SSE) Hidden Shimmer skeleton
First SSE event is RUNNING Hidden Shimmer skeleton
STARTING / STOPPED / etc. Warming-up toast Quiet ResourceWaitingPlaceholder
RUNNING (SQL still fetching) Dismissed Shimmer skeleton
Result / error Dismissed Chart or error state

ChartWrapper (used by BarChart, LineChart, etc.) only shows the quiet placeholder after SSE explicitly reports a non-RUNNING warehouse state. Error states (DELETED / DELETING) skip the placeholder.

Wiring

  • template/client/src/main.tsx — every newly-scaffolded app ships with the provider + indicator.
  • apps/dev-playground/client/src/main.tsx — same wiring in the playground.

Relationship to #415

#415 (merged) ships the backend SSE that drives warehouseStatus. This PR consumes those events. Without SSE, warehouseStatus stays null and both the toast and chart placeholders stay silent (shimmer only).

Test plan

  • Unit tests for ResourceStatusProvider / useResourceStatus / useResourceStatusPublisher / ResourceStatusIndicator (cross-kind aggregation, severity ranking, kind filter, kindHint slot registration, loading→error toast morph, custom render, 1Hz elapsed tick).
  • Unit tests for the analytics warehouse adapter — including no toast before SSE and no toast when first event is RUNNING.
  • useAnalyticsQuery tests covering the warehouseStatus field on STARTING / RUNNING / result event ordering.
  • Full repo: pnpm vitest run passing.
  • Biome + tsc clean across appkit-ui and dev-playground/client.
  • Manual smoke: warehouse already RUNNING → no toast, no "waiting for warehouse" on charts.
  • Manual smoke against a STOPPED warehouse (top-right toast + quiet chart placeholders, both clear when RUNNING).

@MarioCadenas MarioCadenas requested a review from a team as a code owner June 3, 2026 17:17
@MarioCadenas MarioCadenas requested a review from calvarjorge June 3, 2026 17:17
@MarioCadenas MarioCadenas force-pushed the mario/appkit-ui-resource-status-indicator branch 5 times, most recently from 1097ca3 to 6b5f703 Compare June 4, 2026 16:40
…ytics adapter

Adds a small, plugin-agnostic readiness aggregator and a drop-in indicator
so that any plugin (SQL warehouses today, Lakebase / model-serving / etc.
tomorrow) can publish "I'm warming up" status into a shared provider, and
a single global affordance reflects the worst pending state.

Public API in @databricks/appkit-ui/react:

- ResourceStatusProvider — aggregator context (mount once near root).
- useResourceStatus(filter?) — read the aggregate; filter by kind.
- useResourceStatusPublisher(id, label, { kindHint? }) — plugins push
  ResourceStatus snapshots; severity ("pending" | "warning" | "error")
  drives cross-kind ordering.
- ResourceStatusIndicator — floating card (default bottom-right) with
  built-in copy for known kinds, kind-specific overrides via renderers,
  and a render prop for full UI replacement.

useAnalyticsQuery now exposes warehouseStatus and auto-publishes into
the provider so every chart's warmup is reflected globally without
per-chart wiring. Existing analytics-specific helpers
(AnalyticsWarehouseStatusProvider, AnalyticsWarehouseBanner,
useAnalyticsWarehouseStatus) are kept as thin adapters over the generic
API for back-compat.

UX: ChartWrapper detects the warmup state and renders a quiet
ResourceWaitingPlaceholder ("Waiting for warehouse…") instead of a
shimmering skeleton — a 30s–2min cold start no longer pretends results
are "any second now". The skeleton returns once the warehouse is
RUNNING and the actual SQL fetch begins.

The provider + indicator are wired into:
- template/client/src/main.tsx — every new app gets it by default.
- apps/dev-playground/client/src/routes/__root.tsx — playground.

Backend SSE wiring for the warehouseStatus events ships separately
(see #415); this PR works dormant until that lands — warehouseStatus
simply stays null.
The store is event-driven; with the backend now de-duplicating equal
successive states, the indicator can go tens of seconds without a new
notification during a cold start. Without an internal tick, the
displayed `elapsedMs` froze at the value derived on the first publish.

The indicator now drives its own setInterval at 1Hz while a wait is
active and recomputes the live elapsed locally from `worst.startedAt`,
shadowing the stale snapshot field. The store stays pure and
event-driven; only the visible stopwatch consumer ticks.

Pinned by a new test that publishes a STARTING status, advances fake
timers by 3.5s without any further publishes, and asserts the rendered
description advances from "0s" to "3s".
The card less often overlaps with chart legends, footers, or floating
action buttons (which conventionally sit bottom-right) when anchored to
the top-right corner. Consumers can still override via the `position`
prop. Docs updated to match.
@MarioCadenas MarioCadenas force-pushed the mario/appkit-ui-resource-status-indicator branch from 92c5f5e to f52ca39 Compare June 5, 2026 15:57
The indicator no longer renders its own floating card. Instead it mounts a
sonner `<Toaster />` and drives a single sticky toast (`toast.loading` for
cold starts, `toast.error` for unrecoverable states) that mirrors the worst
pending status. Sonner handles animation, theming, stacking, and portal
placement, so the indicator becomes a thin effects-only bridge.

API changes:

- `<ResourceStatusIndicator />` now embeds `<Toaster />` and forwards its
  props (`position`, `theme`, `richColors`, `expand`, …). `position`
  defaults to `top-right`.
- New `useResourceStatusToaster()` hook for apps that already render their
  own `<Toaster />` for unrelated app toasts and want resource-status
  toasts to share it.
- `className` was renamed to `toastClassName` (the toast itself) to free
  `className` for the Toaster wrapper via the inherited `ToasterProps`.

Drive-by simplification of `ResourceStatusStore`: `recompute()` always ran
after a `version` bump, so `snapshotsEqual()` (and its `recordsEqual()`
helper) were unreachable dead code. Folded into a single `bump()` method
and unified `aggregate()`'s return type.

The starter template and dev-playground now mount only
`<ResourceStatusIndicator />` instead of `<Toaster />` + indicator.
Cut multi-paragraph rationale chains down to single-line invariants and
gotchas, drop redundant JSDoc that restated field names, and fix the now-
incorrect 'renders nothing' line on ResourceStatusIndicator (it always
renders the embedded Toaster).

Net -167 lines across 10 files, no behavioral change.
ResourceStatusStore: replace arrow class fields with regular methods;
wrap subscribe/getSnapshot in useMemo so useSyncExternalStore keeps a
stable bound reference. Extract aggregateForKind() from the kind-filter
path in useResourceStatus.

useAnalyticsQuery: extract handleAnalyticsSseMessage() and a warehouse
status type guard from the inline onMessage callback; share the generic
load-error string.
Synthesize pending from kindHint-only slots, gate chart placeholders on
warehouse state, include severity in toast ids, and export the toaster hook.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Drop synthetic kindHint pending so RUNNING clears the indicator, and only
show chart placeholders after SSE reports a non-RUNNING warehouse state.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas MarioCadenas merged commit 2609f34 into main Jun 5, 2026
13 of 15 checks passed
@MarioCadenas MarioCadenas deleted the mario/appkit-ui-resource-status-indicator branch June 5, 2026 18:52
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.

3 participants