Skip to content

feat(mcp): gauge chart type plugin - #43568

Open
gkneighb wants to merge 4 commits into
apache:masterfrom
gkneighb:feat/mcp-gauge-plugin
Open

feat(mcp): gauge chart type plugin#43568
gkneighb wants to merge 4 commits into
apache:masterfrom
gkneighb:feat/mcp-gauge-plugin

Conversation

@gkneighb

Copy link
Copy Markdown
Contributor

SUMMARY

Adds a generate_chart MCP plugin for the gauge chart type (viz_type: gauge_chart), so the MCP generate_chart tool can produce gauge charts. Gauge is a shipped Superset viz type that had no MCP plugin; this closes that gap.

The plugin mirrors the frontend Gauge buildQuery contract: a single metric whose value the dial displays, plus an optional multi groupby — with no groupby the chart is a single dial; with a groupby it renders one dial per row (capped at the frontend's limit of 10). min_val/max_val fix the dial scale (both default to auto).

Follows the established plugin pattern (pie/funnel/waterfall) — a config schema, a map_*_config mapper, a plugin class, and registration:

  • GaugeChartConfigmetric required; optional groupby (list, one dial per row), row_limit (default 10, capped 1–10 to match the frontend), min_val/max_val, filters, color_scheme. Added to the ChartConfig discriminated union and the get_chart_type_schema adapters. groupby entries may not be saved_metric/sql_expression (dimensions, not metrics).
  • map_gauge_config — maps the config to form_data; saved metrics pass through as a bare name string, ad-hoc metrics as SIMPLE/SQL adhoc objects (via the shared create_metric_object).
  • GaugeChartPlugin — registered in the plugin registry; gauge_chart was already present in the recommendation category map.

The field set is intentionally minimal (core query contract + the dial scale). Cosmetic ECharts controls (start/end angle, intervals, pointer, progress, etc.) are left for a follow-up.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — backend/MCP only, no UI change.

TESTING INSTRUCTIONS

pytest tests/unit_tests/mcp_service/chart/test_gauge_chart.py — 17 unit tests cover schema validation (metric required, groupby optional, row-limit cap at 10, groupby-not-a-metric, extra-field rejection), ChartConfig union dispatch, form_data mapping (viz_type/groupby/metric/row_limit/min_val/max_val/filters/saved-metric), and registry integration (registration, resolve_viz_type, display_name, pre_validate with and without groupby).

To exercise end-to-end: call the generate_chart MCP tool with
{"chart_type": "gauge_chart", "metric": {"name": "progress", "aggregate": "AVG"}}.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.21839% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.25%. Comparing base (040b33c) to head (0ba613c).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/chart/plugins/gauge.py 46.93% 26 Missing ⚠️
superset/mcp_service/chart/chart_utils.py 45.45% 6 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master   #43568   +/-   ##
=======================================
  Coverage   79.24%   79.25%           
=======================================
  Files        2888     2889    +1     
  Lines      166446   166543   +97     
  Branches    38529    38548   +19     
=======================================
+ Hits       131908   131996   +88     
- Misses      32045    32059   +14     
+ Partials     2493     2488    -5     
Flag Coverage Δ
hive 37.90% <43.67%> (+<0.01%) ⬆️
mysql 57.55% <43.67%> (-0.03%) ⬇️
postgres 57.59% <43.67%> (-0.03%) ⬇️
presto 39.80% <43.67%> (+<0.01%) ⬆️
python 83.75% <63.21%> (+<0.01%) ⬆️
sqlite 57.28% <43.67%> (-0.03%) ⬇️
unit 74.17% <63.21%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gkneighb
gkneighb force-pushed the feat/mcp-gauge-plugin branch from 8811240 to 8fe90c2 Compare August 27, 2026 10:32
@gkneighb
gkneighb marked this pull request as ready for review August 27, 2026 11:18
@dosubot dosubot Bot added change:backend Requires changing the backend viz:charts:gauge Related to the Gauge chart labels Aug 27, 2026
Comment on lines +1073 to +1076
None, description="Minimum value of the dial scale (default: auto)"
)
max_val: float | None = Field(
None, description="Maximum value of the dial scale (default: auto)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Validate that the configured scale bounds are ordered before mapping them to form_data. As written, a request with min_val greater than max_val is accepted and reaches the frontend, where the negative scale range produces invalid or misleading gauge axis calculations. Reject reversed bounds with a model validator. [incorrect condition logic]

Severity Level: Major ⚠️
- ⚠️ Gauge charts can display inverted axis ranges.
- ⚠️ Configured minimum and maximum semantics are not enforced.
- ⚠️ Gauge interval colors may use incorrect bounds.

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/mcp_service/chart/schemas.py
**Line:** 1073:1076
**Comment:**
	*Incorrect Condition Logic: Validate that the configured scale bounds are ordered before mapping them to `form_data`. As written, a request with `min_val` greater than `max_val` is accepted and reaches the frontend, where the negative scale range produces invalid or misleading gauge axis calculations. Reject reversed bounds with a model validator.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@bito-code-review

Copy link
Copy Markdown
Contributor

The suggestion to validate that min_val is less than max_val in GaugeChartConfig is correct. You can implement this by adding a model validator to the GaugeChartConfig class in superset/mcp_service/chart/schemas.py.

superset/mcp_service/chart/schemas.py

@model_validator(mode="after")
    def validate_scale_bounds(self) -> "GaugeChartConfig":
        if self.min_val is not None and self.max_val is not None and self.min_val >= self.max_val:
            raise ValueError("min_val must be less than max_val")
        return self

@bito-code-review

bito-code-review Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #262b50

Actionable Suggestions - 0
Review Details
  • Files reviewed - 6 · Commit Range: 8fe90c2..8fe90c2
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/plugins/__init__.py
    • superset/mcp_service/chart/plugins/gauge.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/tool/get_chart_type_schema.py
    • tests/unit_tests/mcp_service/chart/test_gauge_chart.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@aminghadersohi aminghadersohi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — it's a clean, well-structured plugin and one of seven in the family, so I've split findings into gauge-specific and template-level (all 7). Everything below was verified against head 8fe90c2a; I ran the unit tests and executed the repros noted as RAN.

What's genuinely good here

  • The plugin contract is faithful to the frontend. map_gauge_config mirrors the Gauge/buildQuery + controlPanel contract precisely: groupby → one dial per row, metric via create_metric_object, and color_scheme defaulting to 'supersetColors'. row_limit: Field(10, ge=1, le=10) is correct, not off-by-one — the frontend controlPanel caps choices to [...Array(10).keys()].map(n => n + 1) (i.e. 1–10), so le=10 matches the canonical cap. Nice attention to detail. (RAN: test_gauge_row_limit_capped_at_ten passes; INSPECTED Gauge/controlPanel.tsx.)
  • Bad column names are validated, not silently dropped. normalize_column_refs routes metric/groupby/filters through DatasetValidator.get_canonical_*, so this plugin does not repeat the SC-117852 silent-discard shape for column resolution. Good.
  • The registry is defensive (registry.py:154): duplicate chart_type and native_viz_types collisions both log warnings. gauge_chart is a unique literal, so no collision with siblings.
  • The 2 deleted lines in schemas.py are benign — they're the two lines of the ChartConfig discriminator description string, rewritten to insert 'gauge_chart'. No behavioral regression to the shared file.

Template-level (applies to all 7 siblings)

1. The reject_metric_style_groupby guard has a hole: it misses aggregate ⚠️ (highest-value item)

reject_metric_style_groupby rejects sql_expression and saved_metric on a groupby entry, but not aggregate — even though a ColumnRef with aggregate set is metric-like. The result is a silent drop:

# RAN at 8fe90c2a:
GaugeChartConfig(chart_type="gauge_chart",
                 metric={"name":"x","aggregate":"AVG"},
                 groupby=[{"name":"team","aggregate":"SUM"}])
# → accepted; map_gauge_config(...)["groupby"] == ["team"]   (the SUM is silently discarded)

This is the same class codeant flagged on the treemap sibling (#43569) — and I verified the reject_metric_style_groupby validator is byte-identical across gauge, treemap (#43569), and radar (#43571), so the hole is cloned. The repo already has the right predicate: ColumnRef.is_metric (schemas.py:752) returns aggregate or saved_metric or sql_expression, and _metric_display_label treats all three as metric markers. Suggested shared fix: have the guard reject col.is_metric (covering all three markers) rather than checking saved_metric alone. One convention fixes it in all the siblings that have a groupby.

2. Merge-conflict surface: all 7 rewrite the same lines

Every sibling edits the identical ChartConfig discriminator description line ("'pie', 'pivot_table', ..." → inserting its own literal) and inserts into the same import blocks in schemas.py, chart_utils.py, plugins/__init__.py, and get_chart_type_schema.py, plus the same union-member region. (INSPECTED all 6 sibling patches.) Whichever merges first, the other six will hit conflicts on that hunk. Not a defect in this PR — just worth coordinating merge order (or expecting six rebases) now rather than after the fact.

3. On the "schemas encode types but not semantic constraints" synthesis — partially refuted

Each of the 7 configs does ship a @model_validator(mode="after") encoding a semantic constraint, so it's not "types only." The sharper, accurate statement is: the semantic validators are incomplete and copy-pasted — see finding #1. That's the pattern worth fixing convention-wide.

Gauge-specific

4. codeant's inverted-bounds claim — CONFIRMED, but Minor (not Major)

min_val/max_val are bare float | None with no ordering check, and there's no @model_validator covering them (the one that exists only guards groupby).

# RAN at 8fe90c2a:
GaugeChartConfig(chart_type="gauge_chart", metric={"name":"progress","aggregate":"AVG"},
                 min_val=100, max_val=0)
# → accepted; form_data == {min_val: 100.0, max_val: 0.0}

Downstream (traced, not inferred): Gauge/transformProps.ts:245 computes range(min, max, (max - min) / splitNumber). With min>max the step is negative, so lodash range yields a descending axis ([100, 90, …]) and min/max pass straight into the echarts gauge series (:342-343). It renders an inverted/misleading dial — it does not crash or go blank. So "Major" overstates it; I'd call it Minor.

Two caveats that lower the priority further, worth stating so it's judged fairly:

  • This is not a fail-open divergence from the canonical path. The frontend min_val/max_val are plain TextControls with no ordering validation either — the Explore UI accepts min>max and renders the same inverted dial. The MCP layer is mirroring existing behavior, not weakening a check the canonical path enforces.
  • It's gauge-specific: no sibling has an ordered scalar pair (I checked — the others' fields are all ColumnRefs or single row_limits), so this particular bug does not replicate six more times.

Still a reasonable hardening: a @model_validator(mode="after") rejecting min_val is not None and max_val is not None and min_val >= max_val would give the agent-facing path a guardrail the UI lacks. Low cost, clear message.

5. Test adequacy (Rule 26)

test_gauge_chart.py (+169) is well-targeted — reverting only the production files fails ~all 15 tests (they import GaugeChartConfig/map_gauge_config/the registry). (RAN: 15 passed.) It includes real negative tests for row_limit>10 and groupby saved_metric. But as expected there is no inverted-bounds test (the happy-path test_gauge_form_data_with_dials_and_range uses ordered 0/100) and no aggregate-on-groupby test. If you act on #1 and #4, please add those two negatives.

Minor / nits

  • get_chart_type_schema.py example is metric-only — fine, since the Field descriptions are strong, but a groupby + min_val/max_val example would show the multi-dial + scale case to an agent.

Bottom line: the template is sound and worth propagating to the other six — the plugin/registry/mapping structure is solid and the field-level validation is thoughtful. The one thing I'd fix as a shared convention before the family lands is finding #1 (use is_metric in the groupby guard); #4 is a nice-to-have hardening specific to gauge. Not resolving codeant's thread; leaving that to you.

CI at 8fe90c2a, re-derived deduped (latest run per check name): 46 success / 3 neutral / 12 skipped / 0 failures across 61 distinct checks (125 raw runs) + netlify StatusContext success.

@netlify

netlify Bot commented Aug 28, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 6e8ab39
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a959a40ad961200088b5e9a
😎 Deploy Preview https://deploy-preview-43568--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@aminghadersohi aminghadersohi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 follow-up — reviewed at head e264888d. Both items from my earlier review are addressed, and I verified them by running the suite (RAN: 17 passed).

Both fixes land correctly ✅

  • groupby metric-marker gapreject_metric_style_groupby now gates on col.is_metric (schemas.py:751), which unifies aggregate / saved_metric / sql_expression. This closes the aggregate-on-groupby hole exactly as hoped, using the in-repo predicate rather than an ad-hoc check. RAN: groupby=[{name:'team', aggregate:'SUM'}] now raises ValidationError (previously accepted, SUM silently dropped). The new test_gauge_groupby_rejects_aggregate covers it.
  • Inverted min_val/max_val (codeant's open thread) — the new reject_inverted_bounds validator rejects min_val >= max_val. Using >= (not >) is the right call: equal bounds are also degenerate (range(min, max, 0) → empty axis). RAN: min_val=100, max_val=0 now raises; test_gauge_rejects_inverted_bounds covers it. This resolves the substance of codeant's inline suggestion on schemas.py:1076 — I'm leaving the thread itself for you to close.

Nice, minimal fixes — both reuse existing conventions and each ships a matching negative test.

One extra check I ran on this family — gauge is clean here

The sibling PRs in this family have a recurring shape where a query transform that lives only in the frontend buildQuery.ts is lost on the MCP path (which builds the query server-side and never runs buildQuery.ts). I checked gauge specifically for it and it does not apply:

  • min_val/max_val are render-only — they feed the dial axis scale in Gauge/transformProps.ts, not the query. Nothing to lose.
  • Gauge's only buildQuery.ts query transform is sort_by_metric && { orderby: [[metric, false]] }. But sort_by_metric is opt-in and off by default in the frontend (no default in the shared control, and Gauge/DEFAULT_FORM_DATA doesn't set it), so the frontend default emits no orderby either. map_gauge_config emits no orderby/sort_by_metric — which matches the frontend default. So there's no silent divergence for gauge.

If you ever want to expose the "sort dials by metric" toggle for the high-cardinality-groupby case, that'd be a sort_by_metric field mapped through to the server's sort_by_metric → orderby conversion — but it's a feature, not a gap, and out of scope here.

Code-wise this looks good to me. As an external-contributor PR I'm keeping this a comment rather than a formal approval, but I have no remaining blockers on the gauge plugin itself.

@bito-code-review

bito-code-review Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #9e950d

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 8fe90c2..e264888
    • superset/mcp_service/chart/schemas.py
    • tests/unit_tests/mcp_service/chart/test_gauge_chart.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Greg Neighbors and others added 3 commits August 31, 2026 11:11
Adds a generate_chart plugin for the gauge viz type (viz_type 'gauge_chart').
Mirrors the frontend Gauge buildQuery contract: a single metric the dial
displays, plus an optional multi groupby that renders one dial per row
(capped at the frontend's 10). min_val/max_val fix the dial scale.

- GaugeChartConfig schema (metric required; optional groupby, row_limit
  <=10, min_val/max_val, filters, color_scheme) added to the ChartConfig
  discriminated union and the get_chart_type_schema adapters.
- map_gauge_config maps the config to form_data; saved metrics pass through
  as a bare name string, ad-hoc metrics as SIMPLE/SQL adhoc objects.
- GaugeChartPlugin registered in the plugin registry.
- 17 unit tests covering schema validation (optional groupby, row-limit
  cap, dimension-not-a-metric), union dispatch, form_data mapping, and
  registry integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…unds (review)

Addresses aminghadersohi's review:
- reject_metric_style_groupby gates on ColumnRef.is_metric, so an aggregate on
  a dial dimension is rejected (previously only saved_metric/sql_expression).
- New reject_inverted_bounds validator rejects min_val >= max_val, which would
  render an inverted/degenerate dial (the Explore UI lacks this guard; this
  gives the agent-facing path one).
- Negative tests for both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds gauge_chart to the tool docstring's core chart_type list so the
LLM-facing discovery string matches the registered adapters (aminghadersohi
round-3 note).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gkneighb
gkneighb force-pushed the feat/mcp-gauge-plugin branch from e264888 to 6e8ab39 Compare August 31, 2026 15:14

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Faithful, well-tested gauge plugin — the form_data mapping matches the frontend contract exactly — but the two docstrings LLMs actually read to discover chart types never mention gauge, which undercuts the PR's own goal of making gauge reachable via MCP.

🟡 Medium — generate_chart docstring never lists gauge_chart

The generate_chart tool docstring (superset/mcp_service/chart/tool/generate_chart.py:88-90) enumerates the valid chart_type values ('xy', 'table', 'pie', … 'box_plot', 'waterfall', "plus host-gated types") and gauge is absent — and gauge is not host-gated (plugins/__init__.py registers it with a plain register(GaugeChartPlugin())), so the escape clause doesn't cover it. It's the only registered non-host-gated type missing from the tool that generates charts. An LLM asked for a gauge reads this docstring, sees no gauge, and declines or falls back to big_number — even though the schema/registry/union wiring all work if gauge_chart is passed.

Fix: add gauge to the one of: list (line ~90), a per-type entry in the ~127–135 block (chart_type='gauge_chart': required metric; optional groupby, min_val, max_val, row_limit), and a "gauge" / "dial" / "speedometer" -> chart_type='gauge_chart' line in the ~150 quick-lookup.

🟢 Low — get_chart_type_schema docstring contradicts its own code

This PR adds "gauge_chart": TypeAdapter(GaugeChartConfig) to _CHART_TYPE_ADAPTERS and a gauge example, but the function docstring (get_chart_type_schema.py:293-295) still says "Core types are xy, table, pie, … and waterfall" — no gauge. Severity limited because the impl's error path lists sorted(get_registry().all_types()), which does include gauge_chart. Fix: append gauge_chart to that sentence.

🟢 Low — multi-dial "top N" is unordered (pie opts in, gauge doesn't)

map_pie_config emits sort_by_metric (schema default True) so pie's row-limited output is deterministically the top N by metric; GaugeChartConfig/map_gauge_config emit no sort_by_metric, and gauge's buildQuery.ts only sets orderby when sort_by_metric is truthy. So a gauge with a groupby and more rows than row_limit (≤10) gets LIMIT with no ORDER BY — an arbitrary, run-to-run-unstable set of dials. Honest caveat: the shared sort_by_metric control has no default, so a UI-built gauge behaves the same way; this is a consistency gap with pie, not a regression. Consider adding sort_by_metric to the schema (default True) to make dial selection deterministic.

🟢 Low — test gaps on the non-obvious defaults

test_gauge_chart.py is otherwise thorough (10-cap, inverted-bounds, groupby-purity, saved-metric→string, union dispatch), but the one behavior with a non-obvious default — map_gauge_config defaulting color_scheme to "supersetColors" when None — has no assertion, nor does emission of the min_val/max_val keys. Fix: assert map_gauge_config(config)["color_scheme"] == "supersetColors" for a scheme-less config, plus an explicit-scheme passthrough.


What's solid (no changes needed): the form_data mapping is faithful to the frontend — every emitted key (groupby, metric, row_limit, min_val, max_val, color_scheme) is a real gauge control that buildQuery/transformProps consume; row_limit ge=1, le=10 matches the frontend's 1–10 dial cap exactly; min/max = None maps to the frontend's null auto-scale; to_form_data ignoring dataset_id, the color_scheme or "supersetColors" default, and the normalize_column_refs canonicalization all match PieChartPlugin. The two gauge-specific validators (reject_inverted_bounds, reject_metric_style_groupby) are more complete than the siblings' guards. None of the findings touch the security model.

Net: none of these block a single-dial gauge (the common case); the Medium is about discoverability, which is the PR's stated purpose, so worth fixing before merge.

@aminghadersohi aminghadersohi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 3 follow-up at head 6e8ab393, focused on independently verifying @rebenitez1802's CHANGES_REQUESTED. I checked each point against the code at this head (not the review's snapshot). Net: the discoverability point is real, but one item is already fixed here and one is partly inaccurate.

🟡 Medium — generate_chart docstring omits gauge_chartholds up (confirmed)

Verified the premise: gauge is not host-gated — plugins/__init__.py:52 registers it plainly and gauge.py has no is_enabled/feature-flag override (contrast interactive_pivot.py:131, which gates on feature_flag_manager). So the generate_chart docstring's escape clause ("plus host-gated types returned by get_chart_type_schema") genuinely does not cover gauge, and gauge is absent from its one of: list, its per-type block, and its natural-language quick-lookup. An LLM driving generate_chart won't discover it. Fair call, and it's the one worth acting on since discoverability is this PR's stated purpose. Note generate_chart.py is currently outside the PR's 6-file diff, so this is a small scope expansion rather than a fix to a changed line.

🟢 get_chart_type_schema docstring — already fixed at this head (finding is stale)

The claim that the docstring "still says … no gauge" no longer matches head 6e8ab393. The PR's latest commit ("docs(mcp): list gauge_chart in get_chart_type_schema docstring") changed exactly that line — the authoritative PR diff shows - table, pie, pivot_table,+ table, pie, gauge_chart, pivot_table,, and the docstring at head now reads "Core types are xy, table, pie, gauge_chart, pivot_table, …". So this one's done; no further change needed.

🟢 Unordered multi-dial top-N (sort_by_metric) — holds up, and it matches my round-2 note

This is the same consistency gap I flagged earlier: map_gauge_config emits no sort_by_metric/orderby, so a gauge with a groupby exceeding row_limit (≤10) gets LIMIT with no ORDER BY → an arbitrary dial set. As @rebenitez1802 fairly caveats, the shared sort_by_metric control has no default so a UI-built gauge behaves identically — it's a consistency gap with pie, not a regression. Optional hardening, not a blocker.

🟢 Test gaps — half confirmed

  • color_scheme default (config.color_scheme or "supersetColors"): untested — confirmed, no color_scheme/supersetColors assertion in test_gauge_chart.py. Worth a one-line assertion.
  • min_val/max_val key emission: already testedtest_gauge_form_data_with_dials_and_range asserts form_data["min_val"] == 0 and ["max_val"] == 100 (test file lines ~130–131). So only the color_scheme default is the real gap.

No new issues beyond the above in the 6-file diff, and the earlier is_metric/reject_inverted_bounds fixes remain intact (17 tests still pass locally). codeant's inverted-bounds thread is still open but its substance was addressed by reject_inverted_bounds — leaving resolution to the author. CI at 6e8ab393: 48 success / 2 neutral / 9 skipped, no failing or pending legs.

As before, I'm keeping this a comment (external-contributor PR). Recommendation to the author: the Medium (add gauge to generate_chart's docstring) plus the one-line color_scheme test are the only substantive to-dos; the get_chart_type_schema item is already handled.

@bito-code-review

bito-code-review Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #578ec5

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/mcp_service/chart/schemas.py - 1
    • Missing metric validation · Line 1061-1065
      `metric` is documented to require an aggregate/saved_metric, but unlike `BigNumberChartConfig.validate_metric_aggregate` there is no validator enforcing `self.metric.is_metric`. A plain column passes schema validation and `create_metric_object` silently defaults it to SUM, which may surprise callers. Consider adding the symmetric check to `reject_metric_style_groupby`.
Review Details
  • Files reviewed - 6 · Commit Range: 2cb1624..6e8ab39
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/plugins/__init__.py
    • superset/mcp_service/chart/plugins/gauge.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/tool/get_chart_type_schema.py
    • tests/unit_tests/mcp_service/chart/test_gauge_chart.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

… (review)

Addresses rebenitez1802's changes-requested review:
- generate_chart.py docstring now lists gauge_chart in the one-of chart_type
  list, adds a per-type entry (required metric; optional groupby/min_val/
  max_val), and a 'gauge'/'dial'/'speedometer' quick-lookup alias. gauge is not
  host-gated, so the 'plus host-gated types' clause didn't cover it — an LLM
  asked for a gauge previously saw no gauge and fell back to big_number.
- Add sort_by_metric (default True) to GaugeChartConfig + emit it, and translate
  it to a query orderby in _build_single_query_dict, so a multi-dial gauge with
  more rows than row_limit keeps the top-N dials deterministically rather than
  an arbitrary set.
- Tests: color_scheme default + min/max emission; sort_by_metric -> descending
  metric orderby in the built query.
- (get_chart_type_schema docstring already lists gauge_chart from the rebase.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gkneighb

Copy link
Copy Markdown
Contributor Author

@rebenitez1802 — thanks for the thorough pass; fixed in 0ba613c221:

  • 🟡 Medium (discoverability): added gauge_chart to the generate_chart docstring — the one of: list, a per-type entry (required metric; optional groupby/min_val/max_val), and a "gauge"/"dial"/"speedometer" -> chart_type='gauge_chart' quick-lookup line. You're right it isn't host-gated, so the "plus host-gated types" clause never covered it.
  • 🟢 get_chart_type_schema docstring: already lists gauge_chart — I added it to the "Core types are …" sentence when rebasing onto master (the branch also picked up interactive_pivot).
  • 🟢 Unordered multi-dial: added sort_by_metric (default True) to GaugeChartConfig + the mapper, and translated it to a query orderby in _build_single_query_dict (a top-level form_data['orderby'] is a no-op on the MCP path). So a gauge with a groupby and more rows than row_limit now keeps the top-N dials deterministically. Added a query-context test asserting the descending metric orderby.
  • 🟢 Test gaps: added assertions for the color_scheme default (supersetColors) + explicit-scheme passthrough, and min_val/max_val emission.

@bito-code-review

bito-code-review Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #273da9

Actionable Suggestions - 0
Review Details
  • Files reviewed - 5 · Commit Range: 6e8ab39..0ba613c
    • superset/mcp_service/chart/chart_helpers.py
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/tool/generate_chart.py
    • tests/unit_tests/mcp_service/chart/test_gauge_chart.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve: all four points from my prior review are addressed, and the sort_by_metric fix is at the right layer. Two optional nits below, none blocking.

Re-reviewed the new commits. Confirmed resolved:

  • generate_chart docstring now lists gauge_chart in the one of: types, adds a per-type entry (required metric; optional groupby/min_val/max_val), and a "gauge" / "dial" / "speedometer" quick-lookup line.
  • get_chart_type_schema docstring core-types sentence now includes gauge_chart.
  • Deterministic multi-dial orderingGaugeChartConfig.sort_by_metric (default True) is emitted by map_gauge_config, and the new chart_helpers.py _build_single_query_dict translates the flag into orderby=[(metrics[0], False)]. Nice catch putting it there: the MCP path builds the query dict directly and never derives orderby the way buildQuery.ts does, so this is the correct layer. The tuple/descending shape matches both the frontend contract (Pie/buildQuery.ts, Gauge/buildQuery.ts) and the existing deck path.
  • Tests now assert the color_scheme default, min_val/max_val emission, and that sort_by_metric produces an orderby.

🟢 Low — the shared orderby change also alters pie's MCP output, and that path is untested

_build_single_query_dict is shared, so this fix also newly adds orderby to pie MCP queries (pie has always emitted sort_by_metric=True, but the query dict previously ignored it). That's a correct frontend-alignment bugfix — a row_limit on an unordered result was dropping arbitrary rows instead of top-N — but it's a behavior change to a shipped chart type covered only by the new gauge test. Consider one assertion that map_pie_configbuild_query_dicts_from_form_data yields orderby[0][1] is False, to lock the shared behavior for the pre-existing sibling it actually changes.

🟢 Low — comment lists chart types that don't apply in the MCP path

The chart_helpers.py comment says "pie/funnel/treemap/sankey/gauge," but only pie and gauge emit sort_by_metric among MCP-generatable types; funnel/treemap/sankey have no plugin/map that sets it (it'd only apply to a passed-through saved chart). Consider trimming to "pie/gauge (and any passed-through chart whose form_data sets sort_by_metric)."

🟢 Low — misleading positional arg in the new test

test_gauge_sort_by_metric_becomes_orderby calls build_query_dicts_from_form_data(form_data, 1, "table"); the third positional is datasource_type, not viz_type. Harmless (viz_type comes from form_data and resolve_datasource_engine is monkeypatched), but "table" in a gauge test reads as a copy-paste artifact.

Nice iteration — the query-layer fix in particular is better than a docstring-only edit would have been.

@aminghadersohi

Copy link
Copy Markdown
Contributor

@gkneighb Thanks—the prior bounds, metric-shaped groupby, saved-query ordering, docs, and color-default fixes remain correct at 0ba613c2. A fresh Red Hat product-completeness pass found additional end-to-end gaps. Could you please address or coordinate these before we count Gauge support as complete?

  1. Public client tag: expose/register/document typed chart_type: "gauge" while retaining native viz_type: "gauge_chart". The current public discriminator is gauge_chart, so the stakeholder-requested gauge tag fails union validation. A legacy/native gauge_chart → gauge input adapter is fine.
  2. Native round-trip + updates: adapt native saved metric/groupby shapes and model the meaningful Gauge bounds, angles, formatting, pointer/animation, ticks/progress/caps/interval controls. Preserve omitted scale, color, filters, and presentation state across immediate, preview-first, and cached updates; explicit values and filters: [] must still override.
  3. Numeric semantics: require numeric-producing SIMPLE/saved/SQL metrics and validate resolved returned values/aliases across every row. Text MIN/MAX, non-finite values, missing aliases, and malformed rows must fail rather than render an invalid dial.
  4. Query + previews: route unsaved preview/compile through the Gauge-aware shared builder so row-limited grouped dials retain metric ordering. Unsaved previews currently become a metric-less bar/table; saved Vega can encode the first groupby string as the quantitative value. Resolve the singular metric label, bounds/intervals and grouped-dial semantics consistently, and reject query-error envelopes instead of reporting successful empty previews.
  5. Canonical roles + product tests: prefer exact-case matches, reject ambiguous casefold matches and duplicate groupby roles after canonicalization. Cover generate/update/update-preview/get-data, native round-trip, ordering, saved/form-data-key/unsaved previews, and error/empty/malformed envelopes.

I ran the 19 focused Gauge tests, 1,386 chart MCP tests, and 3,712 broader MCP tests (one unrelated environment health-test failure) plus the native-state, numeric, ordering, preview, and casing reproductions. These gaps remain with green PR CI. Shared builder/error/canonicalization work from #43737 and omission-aware update work from #43679 should be reused/rebased rather than duplicated. I did not modify or push to your branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend size/XL viz:charts:gauge Related to the Gauge chart

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants