Skip to content

Phase D5 — KPI panel type: readKpi/formatKpiValue core (100%), column-role cfg + convention defaults, KPI row #154

Description

@BorisTyshkevich

Goal

KPI tiles for the Dashboard — implemented as the kpi panel type on #166's panel
registry: a favorited query returning a single row renders as a big-number card (value, unit,
signed delta with good/bad coloring), and any query can be explicitly configured as a KPI
in the workbench Panel tab.

Phase D5 of #149. Build on #166 (Panels) — this issue ships the whole kpi arm:
the union entry in panel-cfg.js/saved-io.js validation, the registry renderer, and the
autoPanel single-row rule (#166 deliberately ships no kpi stub — a selectable type that
renders nothing is a bad product state).

Design (from #149 §4, adapted to panel cfg)

Panel cfg: { type: 'kpi', value?, unit?, delta?, higher_is_better? } — each field an
optional column-role override, by column name (per #166's field policy: name-based roles
for convention-driven types; no panel.key — a failed name lookup is the mismatch signal,
and mismatch retains the type and re-derives roles from the convention).

Explicit kpi with 0 or N rows renders an honest warning state ("no data" / "expected 1
row, got N") — never a silent fallback to another type. Grafana-style reducers (first/last/
mean over N rows) are a noted future extension, not v1.

Convention defaults (when roles are omitted): the result's column names bind the roles —
value (the big number, formatting inferred from column type: integer/large → 6.71M /
402.0K, float → 13.2), unit (suffix; % switches to fixed-decimal), delta (signed →
▲/▼, computed in the query, e.g. vs prior period), higher_is_better (0/1, colors the delta
green/red — delays/cancellations set 0).

Bare-scalar fallback: a single-row, single-numeric-column result with no aliases still
renders — that column is value, no unit/delta.

Auto-detection: autoPanel (#166) proposes {type:'kpi'} for single-row results — the
explicit cfg overrides, same recovery path as every panel type.

Layout: KPI tiles float to the top row of the dashboard grid (per #149 §2); tile footer
shows 1 row × N cols + query time. KPI-row visibility persists with the dashboard layout.

Scope

  • new src/core/kpi.js (pure, 100%): readKpi(columns, row, cfg){ value, unit, delta, higherIsBetter } (cfg roles win over name convention, bare-scalar fallback,
    type-driven formatting via formatKpiValue), formatKpiValue(value, type, unit).
  • src/ui/panels.js (Panels: visualization registry + Panel drawer tab + Library panel field #166) — the kpi entry: controls (four column-role dropdowns) +
    card renderer (workbench preview ≡ dashboard tile).
  • src/ui/dashboard.js — KPI-row placement; single-row favorites stop being skipped
    (the "N not shown" tally shrinks accordingly).

Tests

tests/unit/kpi.test.js (100%): every role by convention and by cfg override (cfg wins);
bare-scalar fallback; non-numeric value column rejected (falls back to table via
autoPanel); integer/float/percent formatting boundaries (6.71M, 402.0K, 13.2,
fixed-decimal on %); delta sign/color × higher_is_better; missing/NULL delta renders no
arrow. Integration: single-row favorite renders as KPI without cfg; Panel-tab KPI preview
matches the tile; KPI row floats and persists visibility.

Acceptance criteria

  • A favorited single-row query renders as a KPI card with no configuration (convention +
    bare-scalar fallback).
  • An explicit kpi panel with zero or multiple rows renders the warning state, stays
    visible, and never silently falls back.
  • The kpi union arm validates/normalizes in panel-cfg.js + saved-io.js (added
    here, not in Panels: visualization registry + Panel drawer tab + Library panel field #166).
  • Explicit {type:'kpi'} cfg with column-role overrides wins over the convention and
    persists in library.json (Panels: visualization registry + Panel drawer tab + Library panel field #166 format).
  • Delta shows ▲/▼ with green/red controlled by higher_is_better.
  • Workbench Panel-tab preview is identical to the dashboard tile.
  • src/core/kpi.js at 100%; gates hold; no new dependency.

Non-goals

Gauge / bar-gauge variants (later panel types, #166's catalog); sparklines in the card;
per-KPI thresholds; time-comparison automation (deltas are computed in the author's SQL by
design).

Tracking

#149 Phase D5 · roadmap #68 Phase 6 · builds on #166 (Panels), after PR #168 (D9).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions