Insights: backing a card with a metric¶
An Insights dashboard card can run a published metric instead of raw SQL — pick a metric and a period, and the card resolves the metric's currently-published version and runs it against Databricks on every refresh.
This is where a governed KPI stops being a definition and becomes a number on someone's dashboard: no copied SQL, no drift, and a lineage chip that links the card straight back to the metric.
This page assumes the metric already exists
Cards have always been backed by raw SQL — you wrote a query, you got a chart. As of April 2026 a card can instead be backed by a published metric from the Metrics module. To define a metric in the first place, see Metrics and Defining metrics & joining tables; this page is about pointing a card at one that already exists.
When to reach for a metric-backed card¶
-
The number is already a metric
It's already published in the Metrics module (or could be) — reference it instead of re-writing its SQL on the card.
-
Card and metric stay in sync
Every edit to the metric shows up on the next refresh of every dashboard that references it. No dashboard-by-dashboard drift.
-
You care about lineage
"Which dashboards depend on Revenue MoM?" becomes a single lookup, not a code review of dashboard SQL.
-
Metric and dashboard share a connection
Both live on the same Databricks connection. Cross-connection references are blocked by design (see below).
Every card carries a data source discriminator, and a single dashboard can mix both:
Pick a published metric on the same connection. The card runs the metric's currently-published version on every refresh; switching grain, period, or compare is a chip on the card. The SQL is owned by the metric, not the dashboard — so the definition can't drift per-card.
Write Databricks SQL directly. Stay here when the query is dashboard-specific and would never be reused (a one-off slice, a debugging tile, an exploratory chart), when you need a column or shape the metric layer doesn't expose (free-form joins across catalogs, ad-hoc CTEs), or when no metric models what you need yet and you don't want to wait for one.
Mix both on one dashboard
The data source is per-card, not per-dashboard — governed metric cards for the numbers that matter, ad-hoc SQL for exploration, side by side on the same dashboard.
How a metric-backed card renders¶
On every refresh the card looks at its data source. A SQL card renders its filters and runs your query; a metric card hands off to the metric dispatcher, which resolves the metric's currently-published version and its compiled SQL, runs it, and renders the result — there is no snapshot.
flowchart LR
REFRESH["Card refresh"] --> DS{"data_source"}
DS -->|"sql"| SQLP["Render filters<br/>read-only validate"]
DS -->|"metric_ref"| DISP(["Metric dispatcher"])
DISP --> RES["Resolve currently-published<br/>version + compiled SQL"]
RES --> MQ["Run the metric<br/>caller identity · per-user RLS"]
SQLP --> RUNSQL["Run the SQL"]
MQ --> DBX(["Databricks SQL warehouse"])
RUNSQL --> DBX
DBX --> VIEW["Render card<br/>+ lineage chip"]
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class DISP accent
Figure 1 — Both data sources resolve to a live query on your Databricks warehouse. A metric_ref card hands off to the metric dispatcher, which resolves the currently-published version and its compiled SQL on every refresh, runs it under the viewer's identity, and renders the result behind a lineage chip. There is no snapshot — the card is a live reference.
Why teams reach for this
The definition lives in one place. Edit the metric once and every dashboard that references it picks up the change on its next refresh — no copy-pasted SQL drifting between teams — while lineage stays a single lookup and the query still runs under each viewer's own Databricks identity.
The metric owns the result shape
A metric-backed card doesn't pick warehouse columns: the metric returns a fixed shape and the card renders that. The metric layer's stable contract is the grain bucket (named for the grain, for example metric_time__month), the measure value, <measure>__previous (the value of the matching period in the compare window) and <measure>__delta_pct (the % change against that value). Chart and table column pickers only offer the metric's columns, never arbitrary warehouse columns. See Metrics → Running a metric for the full contract.
Thresholds that measure a change¶
A threshold can have a comparison window, such as "% change, month over month". Its bands are then a change from the earlier period, not a reading: > 5 % means a rise of more than five percent. A metric alert that uses the threshold judges that change, and a card judges it the same way:
| Card | What it does with a threshold on the change |
|---|---|
| A metric-backed KPI or table whose own comparison is the same window (same compare kind and grain) | Judges the change from <measure> and <measure>__previous. A flagged KPI prints the change beside the band, for example Change +7.3 % · Month over month (Month). |
| A metric-backed card that compares another window | Does not flag it, because the change it shows is a different one. While you edit the card, the KPI says so under its value. |
| A SQL, catalog or saved-query card | Does not flag it, because nothing on the card declares which period its comparison column is from. |
| A chart | Draws no reference line for it, because the value axis plots values and a line at 5 would sit at a value of five. |
A period whose earlier value is missing, or zero under a percent change, is not judged, and when nothing else on the KPI flagged, it says so to everyone. A scheduled alert run skips that period too. Under a percent change the bands print in %, whatever unit the threshold itself carries. The card editor says for each threshold whether this card judges it, next to where you pick it. If a column already holds the change, for example a growth percentage your query computes, attach a threshold without a comparison window instead.
Only the Simple KPI layout draws thresholds. A Sparkline, vs Target, Segments or Bars KPI shows none, and neither does a map card; the card editor says so. A KPI judges the value it shows, so its thresholds have no column to pick.
A threshold without a comparison window is judged on the value itself, on every card type, as before.
Connection policy — why cross-connection references are blocked¶
Every dashboard in Datahub is bound to one Databricks connection (an integration); every published metric is bound to the connection its source tables live on. Datahub requires the two to match before a card can point at a metric.
flowchart LR
PICK["Point a card<br/>at a metric"] --> GATE{"Metric connection<br/>= dashboard connection?"}
GATE -->|"yes"| BIND["Bind the card<br/>metric_definition_id stored"]
GATE -->|"no · via picker"| HIDE["Never offered<br/>picker pre-filters it out"]
GATE -->|"no · via API"| BLOCK["409 Conflict<br/>METRIC_INTEGRATION_MISMATCH"]
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class GATE accent
Figure 2 — The connection-match gate. The picker only offers metrics on the dashboard's connection, so in the UI you never hit the wall; a direct API caller that tries to bypass it gets a 409 Conflict (METRIC_INTEGRATION_MISMATCH) with a "metric integration does not match dashboard integration" message.
This is not arbitrary:
| Guarantee | What a cross-connection reference would break |
|---|---|
| Consistent auth identity | A dashboard on Databricks per-user OAuth requests one access token, scoped to its connection. A metric on another workspace would need a different token — a different consent flow, silently, mid-render. Datahub doesn't do that. |
| Predictable Unity Catalog RLS | Per-user row-level security applies on the warehouse the token authorises. A metric reaching across to a different warehouse would either bypass RLS or fail opaquely, depending on permissions. Both are bad. |
| The dashboard tells the truth | If the dashboard says its data is from prod-eu-warehouse, every card on it should query prod-eu-warehouse. No exceptions. |
Moving a metric to another warehouse
If you genuinely need the metric on a dashboard on a different connection, republish the metric there — the metric definition is portable; only its source-table binding is connection-specific. See Defining metrics & joining tables for how source binding works.
Setup — what an admin does once¶
Nothing module-specific: the metric-backed card flow re-uses everything that was already in place.
| Prereq | Where | Why |
|---|---|---|
| Metrics module enabled | /admin/modules (Module Management) |
On by default — nothing to do unless someone disabled it. |
metric.read role |
/rolegroups |
Built into every authenticated role group's baseline; needed to see the metric picker and the lineage chip. |
insights.manage role |
/rolegroups |
Required to add or edit cards. |
| One active, published metric on the dashboard's connection | Metrics module | Otherwise the picker is empty — it explains why and links back to the Metrics module to publish one. |
Adding a metric-backed card¶
- Open the dashboard in edit mode (
/insights/{id}/edit), or click a card's pencil on the viewer. - Click Add card in any empty slot, or + in a section.
- Switch the Data source toggle from From SQL to From Metric.
- Pick a metric. The dropdown is pre-filtered to metrics that are active (not archived), published (not draft), and on the same connection as the dashboard. If it's empty, the panel explains why and what to do.
- Set the runtime parameters passed to the metric on every refresh (table below).
- Configure Build, Format and Analyze as you would for any card (chart type, colour, format, thresholds), and add viewer filters on the Data tab.
- Save.
| Parameter | Options | Default |
|---|---|---|
| Period grain | day, week, month, quarter, year, fiscal_quarter, fiscal_year |
month |
| Period start / end | The window the card reports on | Last 12 months |
| Compare | pop, wow, mom, qoq, yoy_calendar_aligned, yoy_fiscal_aligned, custom_window |
yoy_calendar_aligned |
| Comparison start / end | Only shown when Compare is custom_window |
— |
Those four parameters live in the card's metric_params and are replayed on every refresh — the card never stores a snapshot of the numbers.
flowchart LR
G["Period grain<br/>month by default"] --> REFRESH
W["Period start / end<br/>last 12 months"] --> REFRESH
C["Compare<br/>yoy_calendar_aligned"] --> REFRESH
CW["Comparison start / end<br/>only if compare = custom_window"] --> REFRESH
REFRESH(["Every refresh · no snapshot"]) --> RUN["Run the published metric<br/>with these params"]
RUN --> OUT["metric_time__month · value · previous · delta %"]
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class REFRESH accent
Figure 3 — Period selection is card state, not metric state. The grain, window, and compare kind stored on the card are passed straight into the published metric on every refresh, which returns the stable column contract the card renders. Two cards on the same metric can carry different periods.
On save you'll see a one-line note: "Metric-backed cards run against the published metric on save — preview will appear on the dashboard." That's intentional (see Why there's no in-modal preview). After saving, the card appears and every refresh runs the metric end-to-end through the dispatcher.
Editing a metric-backed card¶
Edit a metric-backed card the same way you edit any card: open it and use the Data source area. You can switch the metric, period grain, date range, and comparison (for example month-over-month vs year-over-year) without leaving the card. Chart and table column pickers only offer columns from the metric's fixed result shape (time, value, comparison, delta) — you don't pick arbitrary warehouse columns.
Switching between SQL and metric¶
To promote a SQL-backed card to a governed metric, choose From Metric, pick a published metric on the same connection, and confirm when Datahub warns that your SQL will be replaced. The reverse — metric back to SQL — is available with the same confirmation, so you never lose work silently.
Letting HERC do the binding¶
With a card open in the editor, open HERC on it and ask in plain language to use a specific KPI or published metric (for example, show revenue month-over-month from the catalog). HERC lists the metrics available for this dashboard's connection and applies the binding to the form; you still Save to persist the change. You need permission to manage insights cards and to read metrics — if listing or binding is blocked, HERC says what's missing.
If no published metric matches what you need, ask HERC to draft and publish a new one, then bind the card to it. HERC uses your tenant's metrics workflow (draft specification and publishing) under your account. Publishing requires metric write access; without it you'll see a clear refusal — ask a steward to publish, or use the Metrics module's wizard yourself. The Metrics module remains the place to design and own metrics long-term; HERC is here so you're not forced to hand-write SQL when a governed path exists.
The lineage chip on the card header¶
Every metric-backed card shows a small chip beneath its title — the canonical way to answer "what is this number really measuring?"
| Chip state | When you see it | Behaviour |
|---|---|---|
| Backed by «Metric» → | Normal: you have metric.read and the metric is active. |
Clickable badge → /metrics/{id}: inspect the definition, see who owns it, view version history, and run the metric in isolation. |
| Backed by «Metric» (archived) → | The underlying metric is archived. | The chip switches colour and gains an (archived) suffix. The card still renders if the published version is queryable — the chip is your warning that the definition is no longer canonical. |
| Backed by metric | The viewer lacks metric.read. |
Degrades to a generic label without the metric's name. The card still renders. |
Empty states — what they mean and what to do¶
Metric-backed cards bypass the generic "query failed" alert in favour of in-card empty states that tell you what went wrong upstream and what to do next. The four states you can see:
| State | What it means | What to do |
|---|---|---|
| Metric is no longer available | The metric was hard-deleted (or metric_definition_id no longer resolves). |
Edit the card and pick another published metric on the same connection. |
| Metric has no published version | The metric exists but its current version is in draft state, or it was unpublished. |
In the Metrics module, publish the metric (or revert to a previously-published version). The card recovers on the next refresh. |
| Card configuration is incomplete | The card's saved metric_params is missing a required field (period, grain, or comparison). Usually only happens for cards created via API. |
Edit the card. The wizard re-seeds defaults you can adjust. |
| Dashboard has no connection | The dashboard's integration_id is not set. The metric layer can't run without a target warehouse. |
Open Dashboard Settings and pick a Databricks connection. |
Every empty state includes a "Pick another metric →" link back to the dashboard editor. None of the four is a bug; they are the metric layer telling you an upstream curation step is needed.
Editing the metric — what flows downstream¶
Because metric-backed cards are live references, every edit in the Metrics module is immediately visible to dashboards that reference the metric:
| You edit the metric to… | Effect on dashboards that reference it |
|---|---|
Change the measure expression (e.g. sum(amount) → sum(net_amount)) |
Every refresh now reflects the new expression. |
| Add or remove a join | Every refresh runs the new spec. Cards using filters that referenced removed dimensions may need editing. |
| Change source binding (catalog/schema/table) | Every refresh reads from the new source. The dashboard's connection still has to match. |
| Move the metric to a different connection | All dashboards on the old connection that referenced it fall into the Metric is no longer available empty state. Republish the metric on the dashboard's connection or repoint the dashboard. |
| Archive the metric | The lineage chip flips to the archived style. The card still renders if the published version is queryable. |
| Unpublish (revert the published version to draft) | Cards drop into the Metric has no published version empty state until republished. |
Stewards can answer "which dashboards depend on this metric?" by looking up cards.metric_definition_id in the database, or by following the lineage chip from any card.
Why there's no in-modal preview¶
The Add Card modal shows a Run Query preview button for SQL-backed cards. It's hidden for metric-backed cards, on purpose:
- The metric is already preview-able on its own page (
/metrics/{id}) with the full Run Query card, including grain/compare/period controls. - Running the metric inside the modal would require a parallel preview endpoint that duplicates the dispatcher's work; Datahub keeps one execution path that the dashboard always uses.
- After save the card appears on the dashboard immediately — that is the preview.
This trade-off is a deliberate product choice: one execution path on the live dashboard, preview on the metric's own page.
Editing a card without leaving the dashboard¶
Click the pencil icon on a card on /insights/{id} and the editor opens as a right-side slide-over panel instead of a centred modal. The dashboard stays visible behind the panel; everything outside the focused card dims out while the card you're editing keeps its full colour with a soft accent ring. The viewer auto-scrolls so the focused card sits in the visible left strip beside the panel.
You can still:
- Open HERC from the card editor — the AI panel floats on top of the slide-over (it never gets hidden behind it) and keeps the editor's full context.
- Discard unsaved changes — closing the panel with the X, the Escape key, or by clicking the dimmed area asks for confirmation when you have edits in flight.
- Resize the browser — a live resize observer keeps the spotlight cutout on the card, so the focus ring stays put even as the layout reflows.
When the data warehouse is warming up¶
A cold Databricks SQL warehouse can take one to three minutes to accept queries. When a dashboard card, or a filter with dynamic options, is still loading after a few seconds, HERC may offer a choice: keep waiting on the page, or notify me when the data is ready. Choose Notify me and go elsewhere, and you'll get an in-app success toast with a View action that returns you to this dashboard. The same pattern is available on other long-running data surfaces in Datahub, not only Insights, so the behaviour will feel familiar as it rolls out more widely.
Limitations¶
| Limit | Why | Workaround |
|---|---|---|
| No version pinning. A card always runs the metric's currently-published version. | Live reference — one source of truth. | For a snapshot (e.g. a quarterly review), take it at the dashboard level: export, archive, or copy. |
| No cross-connection references. | By design, not a bug (see § Connection policy). | Republish the metric on the dashboard's connection. |
| No metric-on-metric composition inside a card. A card references one metric. | Cards render one result shape. | Need two metrics side by side? Use two cards. |
| Period parameters are per-card. | There is no dashboard-level "metric period" that cascades. | Set the period/grain/compare on each card (two cards on the same metric can differ). |
| Card-level filters do not narrow the metric. They filter the result, the same way they'd filter SQL output. | Push-down belongs in the metric, not the card. | Model filters that need to reach the source query inside the metric itself. |
Audit & compliance¶
| Question a CISO might ask | Where to look |
|---|---|
| Does the card execute the metric under the dashboard viewer's identity? | Yes. Per-caller oauth_u2m token resolution applies — same RLS contract as SQL-backed cards. See Databricks: per-user OAuth. |
| Can a card on Dashboard A point at a metric on Connection B? | No. A mismatch returns 409 Conflict (METRIC_INTEGRATION_MISMATCH) at create/update; the picker pre-filters them out. |
| Where can I find every card that depends on a given metric? | Database: SELECT * FROM insights.cards WHERE metric_definition_id = '…' (admin / steward access), or follow the lineage chip from any card. |
| What happens if I hard-delete the metric definition? | Cards drop into the Metric is no longer available empty state on the next refresh. Stewards should archive (not hard-delete) — see the platform's archive-before-delete policy. |
| Is the metric's compiled SQL ever stored on the card? | No. The card stores only metric_definition_id. The dispatcher resolves the published MetricVersion and its compiled_sql on every execute. |
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
| The metric picker is empty. | No active, published metric targets this dashboard's Databricks connection. | Publish a metric on that connection, or move the dashboard to a connection that already has metrics. |
| You get a 409 when saving. | The metric's connection doesn't match the dashboard's — the picker should prevent this, but a direct API call can hit it. | Check the dashboard's integration_id in Dashboard Settings; pick a metric on the same connection. |
| The card renders the wrong period. | Period / grain / compare live on the card, not the metric. | Edit the card and adjust them. The metric's own page may show a different default — that's independent. |
| Chip says (archived) but the card still renders. | Expected while the published version is still queryable. | Republish a current version (or pick another metric) before archiving the definition fully. |
| Editor picker shows "Loading metrics…" forever. | The dashboard's integration_id is unset. |
Open Dashboard Settings, pick a connection, and reload the editor. |
See also¶
-
The module overview: card types, filters, caching, and the review lifecycle.
-
Define the governed KPIs that
metric_refcards run. -
Defining metrics & joining tables
The deeper how-to: the join builder, FK discovery, and where a metric reads from.
-
Matrix, heatmap & multi-metric cards
The two newer card types — both ride the same SQL and
metric_refdata sources. -
How the SQL-card result cache keys, expires, and bypasses.
-
Per-user RLS and per-user audit on every card query.