Skip to content

databricks-aibi-dashboards skill: runtime/render gaps from a 25-dashboard Mode→Lakeview migration (fixes mapped to files) #593

Description

@CKasinski-at

Context

We migrated 80+ dashboards from Mode → Databricks Lakeview (Jul–Aug 2026) using the databricks-aibi-dashboards skill, reviewed at main (b059fd0, past v0.2.0). This is a consolidated field report of every gap that cost us time, each mapped to the file and the fix to add. Filing it in one place because the findings share a root theme.

What worked (and it mattered): the skill's structural guidance is excellent — the widget-type + per-widget version table, the disaggregated flag explanation, inline-layout JSON, and the explicit "don't use widgetType:'filter'/'text'" callouts prevented a whole class of parse-time errors. A cold, from-scratch build on the skill alone shipped a structurally correct dashboard.

The theme: every gap below is at the runtime/render layer — specs that pass every structural check the skill describes and still render blank, tiny, or numerically wrong. Those cost the most time because the docs indicated everything was correct. Two blind spots concentrate the pain:

  1. MCP-only deploy assumption — CLI deployers reverse-engineer the workflow.
  2. Custom Vega is entirely undocumented — yet it's the escape hatch for every stakeholder ask native widgets can't satisfy (stack totals, pivots, heatmaps, conditional-color tables, wide bars). The moment a real stakeholder iterates, you leave the documented surface and inherit correctness traps with no guidance.

Tier 1 — cost us hours; one doc line each would have prevented them

1. parameters on a chart/table TILE query silently blanks the tile. Table renders "Select fields to visualize" with an empty Columns panel even though SQL returns rows and the param default + encodings are correct. Root cause: parameters are dataset-scoped; we added parameters:[{name,keyword}] to the tile query to "pass the value through." 3-filters.md shows parameters on the filter widget + dataset but never states the rule. 5-troubleshooting.md's "no selected fields to visualize" lists only one cause (field-name mismatch), which didn't apply, so we chased it for hours — and propagated it to 5 dashboards.
Fix: boxed rule in 3-filters.md ("NEVER add parameters to a chart/table tile query"); add a 2nd cause under the troubleshooting entry.

2. Field/associative filters can't re-slice PRE-AGGREGATED or cumulative tiles. Counters / running-sum areas / histograms don't move when filtered. An associative filter injects WHERE ... IN (...) at the tile's output; a tile that already aggregated can't be re-sliced — it needs the value injected as a parameter into the base query before aggregation. 3-filters.md line 14 ("you don't need to pre-filter data in your SQL") is true for disaggregated tiles but misleading for aggregated KPIs.
Fix: a decision table — raw/disaggregated → associative filter is fine; counter/pre-aggregated/cumulative/histogram → parameter injected into base query (('ALL' IN (:p) OR col IN (:p))).

3. An unbound parameter is invisible to viewers. A :param with a default gives the published viewer no way to set it (our first batch of "lookup tools" shipped unusable). Docs always show param + filter-widget together but never warn that a param without a bound filter widget doesn't surface; no free-text-entry pattern (filter-single-select with only a param-binding query, no options dataset) is documented.
Fix: explicit warning + the free-entry snippet in 3-filters.md.

Tier 2 — real render failures ABSENT from the kit

4. embed_credentials (run-as-viewer vs run-as-publisher) is undocumented — two opposite failure modes. Default publish = embed_credentials=false; tiles reading a table the viewer lacks grants on render "No data"/"error in source data" while sibling tiles render fine (looks like a SQL bug, is a grant gap; fix is --embed-credentials). The reverse also bit us: embedding creds on a dashboard with SSO certs + PII in a shared folder over-exposed sensitive data. grep embed_credentials across all 7 docs = 0 hits.
Fix: an SKILL.md section on the tradeoff + a 5-troubleshooting.md entry.

5. Custom-Vega widgets render tiny without container sizing. Every custom-Vega widget needs "width":"container","height":"container" + "autosize":{"type":"fit","contains":"padding","resize":true} or it crams into the left ~third of the tile. Covered nowhere.

6. Filter dropdowns ignore the options ORDER BY. A single/multi-select filter is itself an aggregating widget; it discards the options query's ORDER BY (a "Latest" option sinks to the bottom). Fix = a hidden sort-key column + widget-level spec.sort. Not documented.

7. Native bar x-axis labelAngle is unreliable. To get consistent diagonal labels we had to convert bars to custom Vega with explicit labelAngle. The 1-widget-specifications.md bar section should note this.

8. update REPLACES the whole dashboard. No warning that lakeview update overwrites the entire serialized dashboard, silently wiping human UI edits made since the last programmatic deploy. Co-developed dashboards need get-reconcile-update.

9. Structural validation ≠ render success — should be a stated principle. Our #1 bug passed every structural check (valid JSON, matching field names, correct versions, disaggregated set) and rendered blank. Add the meta-rule: open the published dashboard and confirm tiles paint + a filter re-slices.

10. Permissions/ownership. No IS_OWNER for Lakeview; ceiling is CAN_MANAGE; /Shared inherits CAN_MANAGE for users+admins. Sharing/ownership isn't covered at all.

Tier 3 — contradictions to reconcile

11. associative_filter_predicate_group. 3-filters.md and 5-troubleshooting.md both say "DO NOT use it — SQL errors," yet Databricks' own AI/BI editor output (and common builder libs) emit COUNT_IF(\associative_filter_predicate_group`)`. Guidance and generated output disagree; worth a definitive statement.

12. Table-column numberFormat. 5-troubleshooting.md says column objects only need fieldName/displayName and to omit numberFormat — but a computed column (e.g. SUM(a)/SUM(b)) renders blank unless its encoding carries a format. Reconcile: plain columns none; computed columns require one.

13. Number formatting / significant figures — thin. We hit raw-float overflow (a counter printing 33905.0 instead of $33.9K) and in-cell Vega labels overflowing unless compact. A short "formatting for readability" callout with the exact fixes would help.

14. Grid width isn't stated up front (6-vs-12 confusion). The kit is consistently 12-column GRID_V1, stated only implicitly ("each row sums to width=12"). A one-line "positions are on a 0–11, 12-column grid; every page needs layoutVersion:GRID_V1" at the top of 1-widget-specifications.md prevents mixed-grid mistakes.


Part 2 — cold from-scratch build (skill alone, no custom builder lib)

We rebuilt a real 12-tile report using only the skill (hand-authored JSON, documented CLI workflow). Positive: it produced a structurally correct dashboard with zero tiles carrying a param — so Tier-1 #1 is a missing guardrail, not something the docs cause. But it cleared ~10 undocumented/contradictory speed bumps:

  • P2-1SKILL.md "Reference Files" table points to 2-filters.md / 4-troubleshooting.md (don't exist; actual 3-filters.md / 5-troubleshooting.md) and omits 2-advanced-widget-specifications.md.
  • P2-2 — Skill documents only the MCP tool path, never the databricks lakeview CLI: create --json <body>; serialized_dashboard must be a JSON string nested in the body; parent_path exists only inside the body (no --parent-path flag). Biggest theme.
  • P2-3 — Dataset parameters require displayName (deploy fails parameters[0].displayName should not be empty), shown in zero examples. We hit this twice independently.
  • P2-4 — No pivot / cross-tab widget spec (2-advanced lists pivot with "refer to Databricks documentation," no encoding). Forced a grouped-table fallback = fidelity loss.
  • P2-5 — Parameter-only date-range binding under-explained; the only complete example binds both a field and a param. A form param that WHEREs different columns per query needs a param-only binding across datasets. Also unstated: literal default dates work alongside relative tokens.
  • P2-6disaggregated ambiguous for grouped (GROUP BY + SUM) tables; no pattern shown for dimension+measure tables.
  • P2-7 — SQL-testing instruction self-contradicts (SKILL.md says execute_sql(); the description points elsewhere). Partly our own install layering, noted for balance.
  • P2-8 — No schema-introspection fallback for the CLI path (STEP 1 mandates MCP get_table_stats_and_schema; CLI users need DESCRIBE TABLE).
  • P2-9 — Data/value labels on bar & line charts are undocumented. encodings.label.show:true appears only on the pie/combo examples; 1-widget-specifications.md (which specs bar/line) never mentions it. It works — but a reader wouldn't know on-chart labels are possible.
  • P2-10 — Label/number formatting for fidelity is undocumented (the "sig figs" gap). Labels inherit the y-encoding format (never stated); every currency example uses "abbreviation":"compact"$17.3M, which does NOT match a source showing $17,259,808.52; decimalPlaces:{type:"fixed"} is never shown.

Part 3 — second-wave learnings (stakeholder iteration)

Once the report owner asked for the changes stakeholders always ask for, every one pushed past the native widget set into custom Vega — which the skill does not document at all.

  • P3-1 — Stacked-bar column TOTALS are unsupported and undocumented. A universal BI expectation (total on top of each stacked bar). Neither the skill nor the native bar widget supports it; grep total|stack|showTotals finds nothing usable. Only path = a hand-rolled custom-Vega 3-layer spec (bar + per-segment text + a text layer with aggregate:"sum" = the per-x total). Fix: add a showTotals bar option or document the pattern.
  • P3-2 — custom Vega is undocumented despite being the escape hatch for everything native can't do. grep vega across all 6 docs = 0 hits. The mechanism is absent: widgetType:"custom-vega-viz", spec.jsonSpec = {"type":"vega-lite","spec":<string>}, spec.data = {"queryName":...}, and the hard requirement that the Vega data source be the fixed alias {"name":"databricks_query"}. Users are dead-ended for stack totals, pivots (P2-4), heatmaps, conditional-color tables, and reliable angled labels (Rename app and starter project #7). Fix: a dedicated custom-Vega section with the data-binding contract.
  • P3-3 — custom-Vega BAR width: container sizing ≠ bar width; a temporal x collapses bars to slivers. width/height:"container" sizes the chart, not the bars. A temporal/timeUnit x over a short window renders every bar as a thin sliver with labels overflowing the axis. Fix: ORDINAL/nominal x (formatted label, sorted via hidden key) + scale:{paddingInner:0.3,paddingOuter:0.2}; size the y-domain to the stack total.
  • P3-4 — client-side aggregation over raw rows can be silently truncated by the per-widget row cap → a WRONG on-chart total. When a custom viz computes a displayed aggregate over raw rows, the row cap can truncate the input, producing a total that's wrong but looks authoritative. We avoided it by feeding a pre-aggregated dataset. Fix: warn that any custom viz rendering an aggregate must aggregate in the dataset, not client-side.
  • P3-5 — a new dataset does NOT inherit the global filter; the filter widget must bind each dataset explicitly. A dataset that merely references :date_range in SQL does not auto-join the global filter — you must add it to the filter widget's queries. Easy to miss when extending a dashboard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions