Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c191824
fix: advise and smoke ask one height question of tables and pivots
debabsah Oct 5, 2026
c074783
fix: smoke queries each chart with the dashboard's filter defaults
debabsah Oct 5, 2026
863608a
feat: markdown and header blocks inside sketch layouts
debabsah Oct 5, 2026
6545349
feat: mixed-chart areas, rolling trendline KPIs, heatmap axis order, …
debabsah Oct 5, 2026
3837b9b
feat: waterfall chart, with bridges in their own order on 6.1.0 and a…
debabsah Oct 5, 2026
fb7d0d8
feat: a waterfall bridge can open with a total, on a theme that keeps…
debabsah Oct 5, 2026
1b255f9
feat: box plot chart, a measure's distribution per group, with a chec…
debabsah Oct 5, 2026
b8936b6
feat: fitted trendline KPIs, no table chrome over rows that show, nam…
debabsah Oct 5, 2026
e9fea7e
fix: plan is clean straight after apply, however a height or metric i…
debabsah Oct 5, 2026
51726d2
feat: heatmap label steps and left margin; brain 12 fills left_margin
debabsah Oct 5, 2026
6ea32c7
feat: size.markdown-fit warns when markdown text runs past its block
debabsah Oct 5, 2026
b2d5e46
fix: plan compares a table's hidden labels as a set
debabsah Oct 5, 2026
459507c
feat: rank a bar by its total or by another metric (sort_by)
debabsah Oct 5, 2026
ad6359c
feat: a threshold on a horizontal bar, verified and documented
debabsah Oct 5, 2026
bd7292e
docs: a mixed chart's line has no width or dash of its own on any rel…
debabsah Oct 5, 2026
f73ac3c
docs: y_label_every counts up the y axis from its bottom label
debabsah Oct 5, 2026
f3c713f
feat: size.markdown-fit reads the dashboard's CSS, in a 1440 px window
debabsah Oct 5, 2026
8c7aa3f
feat: colour a big number by its value
debabsah Oct 6, 2026
9bca4c7
feat: per-column cell bars, sign colours and absolute bars on tables
debabsah Oct 6, 2026
f4a30bf
fix: warn for colour-rule options older Superset releases draw differ…
debabsah Oct 6, 2026
79b0295
feat: readability floors in the design brain, for table, chart and bi…
debabsah Oct 6, 2026
8654c58
fix: place a horizontal bar's axis titles for the axes Superset swaps
debabsah Oct 6, 2026
f4470c0
feat: design brain 8 asks every axis chart to title its axes
debabsah Oct 6, 2026
bc24286
Merge #38: fix: advise and smoke ask one height question of tables an…
debabsah Oct 6, 2026
26fdf5d
Merge #39: fix: smoke queries each chart with the dashboard's filter …
debabsah Oct 6, 2026
b50ee39
Merge #40: feat: mixed-chart areas, rolling trendline KPIs, heatmap a…
debabsah Oct 6, 2026
5815547
Merge #41: feat: markdown and header blocks inside sketch layouts
debabsah Oct 6, 2026
f1ba957
integration: recount the offline suite after merging #38-#41
debabsah Oct 5, 2026
86c6303
Merge #42 and #43: feat: waterfall chart, with bridges in their own o…
debabsah Oct 6, 2026
f05dc23
Merge #44: feat: fitted trendline KPIs, no table chrome over rows tha…
debabsah Oct 6, 2026
2c48cfa
Merge #45: fix: plan is clean straight after apply, however a height …
debabsah Oct 6, 2026
509df24
Merge #45: fix: plan is clean straight after apply, however a height …
debabsah Oct 6, 2026
8174b31
Merge #46: feat: size.markdown-fit warns when markdown text runs past…
debabsah Oct 6, 2026
c299d9b
Merge #47: feat: heatmap label steps and margin, bar sort_by, thresho…
debabsah Oct 6, 2026
6d35389
Merge #47: feat: heatmap label steps and margin, bar sort_by, thresho…
debabsah Oct 6, 2026
9c688d3
Merge #46: feat: size.markdown-fit warns when markdown text runs past…
debabsah Oct 6, 2026
312ed1e
Merge #48: feat: colour a big number by its value
debabsah Oct 6, 2026
2038582
Merge #49: feat: per-column cell bars, sign colours and absolute bars…
debabsah Oct 6, 2026
f50b565
fix: plan reads a big number rule's hex as the shade the number paints
debabsah Oct 6, 2026
1bfb346
Merge #50: fix: warn for colour-rule options older Superset releases …
debabsah Oct 6, 2026
8bdf248
Merge #51: feat: readability floors in the design brain, for table, c…
debabsah Oct 6, 2026
ff487d6
Merge #52: feat: design brain 8 asks every axis chart to title its ax…
debabsah Oct 6, 2026
52537e2
docs: one chart-types line in FEATURES (a merge kept the old 15 besid…
debabsah Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ jobs:
- name: Dashboard-controls apply + slice-id stability
if: ${{ !cancelled() }}
run: python tools/ci_live_check.py --base-url http://localhost:8098 --spec tests/fixtures/live_dashboard_controls.json
# A waterfall bridge on a virtual dataset made for it: built and read back on
# 6.1.0; refused before 6.1.0, where the plain waterfalls are built instead.
- name: Waterfall bridge apply + round trip
if: ${{ !cancelled() }}
run: python tools/ci_live_waterfall.py --base-url http://localhost:8098 --spec tests/fixtures/live_waterfall.json
# A chart added in Superset: re-apply takes it off the dashboard (4.1.4 and 5.0.0
# merge chart links on import), never deletes it, and restores match their backups.
- name: UI-added chart taken off on re-apply
Expand Down Expand Up @@ -86,6 +91,16 @@ jobs:
- name: Locked text visible on the rendered dashboard
if: ${{ !cancelled() && steps.visual.outcome == 'success' }}
run: python tools/ci_live_visible.py --base-url http://localhost:8098
# Markdown blocks at the height advise --fix gives them show every line with
# Windows-style scrollbars on, and a block size.markdown-fit calls cut off is.
- name: Markdown blocks fit at the height advise gives them
if: ${{ !cancelled() && steps.visual.outcome == 'success' }}
run: python tools/ci_live_markdown_fit.py --base-url http://localhost:8098
# The type sizes the readability rules model (table and pivot cells, titles,
# ECharts labels, big numbers by height) are what this release draws.
- name: Type sizes match the readability model
if: ${{ !cancelled() && steps.visual.outcome == 'success' }}
run: python tools/record_readability_measurements.py --check --base-url http://localhost:8098
# Saved queries captured from Superset's own frontend, so CSV reports work.
- name: Chart queries saved for reports
if: ${{ !cancelled() && steps.visual.outcome == 'success' }}
Expand Down
128 changes: 128 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ as-is.
automatically. Dashboards Chartwright did not create are never overwritten;
to bring a hand-built one under a spec, decompile it and build it at a new
slug.
- **What a spec can express**: 15 chart types, metrics as you write them,
- **What a spec can express**: 17 chart types, metrics as you write them,
per-chart filters, a native filter bar, tabs, markdown notes, a header and
footer shown on every tab, and layouts you can draw as ASCII sketches.
- **Environment promotion**: specs name their data (connection, schema,
Expand Down
10 changes: 2 additions & 8 deletions chartwright/absorb.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import json
from dataclasses import asdict, dataclass, field

from .compiler import ROW_UNITS_PER_SPEC_UNIT, _position
from .compiler import _position, spec_units
from .spec import DashboardSpec


Expand All @@ -38,12 +38,6 @@ def to_json(self) -> str:
return json.dumps({"stage": "absorb", **asdict(self)}, indent=2)


def _exact_units(row_units: int) -> float | int:
"""Superset row units (8px) -> spec units (40px), exactly representable."""
units = row_units / ROW_UNITS_PER_SPEC_UNIT
return int(units) if units.is_integer() else round(units, 1)


def absorb_heights(spec: DashboardSpec, spec_data: dict, live_position: dict) -> tuple[dict, AbsorbReport]:
"""Pure core: returns (patched copy of spec_data, report). spec_data is the
raw JSON the spec file holds; only charts[*].height values are touched."""
Expand Down Expand Up @@ -82,7 +76,7 @@ def absorb_heights(spec: DashboardSpec, spec_data: dict, live_position: dict) ->
live_h = have.get("height")
if not isinstance(live_h, int) or live_h == want["height"]:
continue
charts_by_name[chart.name]["height"] = _exact_units(live_h)
charts_by_name[chart.name]["height"] = spec_units(live_h)
report.absorbed.append({
"chart": chart.name,
"from_px": want["height"] * 8,
Expand Down
2 changes: 1 addition & 1 deletion chartwright/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ def _auto_restore(reason: str) -> None:

report.stage = "smoke"
results: list[SmokeResult] = smoke(spec, resolution, client)
report.smoke_results = [asdict(s) for s in results]
report.smoke_results = [s.as_dict() for s in results]
report.warnings.extend(f"{s.chart}: {s.detail}" for s in results if s.warning)
if any(not s.ok for s in results):
return report
Expand Down
15 changes: 9 additions & 6 deletions chartwright/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,16 +278,19 @@ def delete_chart(self, chart_id: int) -> None:
# -- themes (6.0.0+) ----------------------------------------------------

def themes(self) -> list[dict]:
"""Every theme on the instance: id, uuid and theme_name, from the theme list API
(ThemeRestApi, resource "theme", superset/themes/api.py at 6.0.0 and 6.1.0).
Themes are few, so all pages are read and names matched here: theme_name has no
unique constraint (models/core.py class Theme), and the API's only name search is
a substring match over name and JSON alike (themes/filters.py ThemeAllTextFilter)."""
"""Every theme on the instance: id, uuid, theme_name and json_data (the tokens the
design brain's readability rules read), from the theme list API (ThemeRestApi,
resource "theme", superset/themes/api.py at 6.0.0 and 6.1.0, whose list_columns
hold json_data, :110-120). Themes are few, so all pages are read and names matched
here: theme_name has no unique constraint (models/core.py class Theme), and the
API's only name search is a substring match over name and JSON alike
(themes/filters.py ThemeAllTextFilter)."""
out: list[dict] = []
page = 0
while page < 100: # 10,000 themes: a runaway guard, not an expected ceiling
rows = self.get("/api/v1/theme/", q={
"columns": ["id", "uuid", "theme_name"], "page": page, "page_size": 100,
"columns": ["id", "uuid", "theme_name", "json_data"], "page": page,
"page_size": 100,
})["result"]
out += rows
if len(rows) < 100:
Expand Down
Loading
Loading