Skip to content

fix(pivot-table): exclude rollup totals from conditional formatting scale - #43481

Merged
aminghadersohi merged 3 commits into
masterfrom
aminghadersohi/pivot-table-conditional-formatting-totals
Aug 26, 2026
Merged

fix(pivot-table): exclude rollup totals from conditional formatting scale#43481
aminghadersohi merged 3 commits into
masterfrom
aminghadersohi/pivot-table-conditional-formatting-totals

Conversation

@aminghadersohi

Copy link
Copy Markdown
Contributor

SUMMARY

Pivot table conditional formatting produced washed-out cells once Show rows total / Show column total were enabled.

transformProps built the color scale from mainQuery.data, i.e. the raw chart-data response. Since #41184 (SIP-216), a chart with a non-additive metric (a saved-metric reference, AVG, COUNT_DISTINCT, or any SQL/adhoc metric — see isAdditiveMetric) issues one GROUPING SETS query whose result carries every rollup level alongside the leaf rows. Turning on a totals toggle adds the corresponding collapsed level to grouping_sets, so the subtotals and the grand total end up in that response — and therefore in the color domain.

Those aggregates are sums of the very cells being shaded, so they dominate Math.max(...allValues) and compress every real cell toward transparent. With the four-cell example in the new test (leaf max 40, grand total 100), the largest detail cell rendered at alpha 0x6E (~43%) instead of fully saturated. Disabling the totals was the only workaround, which matches the reported behaviour: the totals levels are simply not queried in that case.

The fix threads a single colorScaleRows variable out of the two branches that already split the response, so the domain spans only the leaf (detail) cells:

  • non-additive — take the leaf level out of the split GROUPING SETS result, identified as the level that groups every dimension (the same definition splitGroupingSetsResult itself uses).
  • additive — keep using the raw leaf query rows. That query returns leaf rows only, so no totals can leak in, and this path is therefore completely unchanged.

Totals cells themselves are unaffected — the renderer never colors them (TableRenderers.tsx deliberately omits getCellColor on pvtTotal).

Known limitation / scope: this corrects the domain only. It does not start coloring the totals cells; they are intentionally uncolored today and changing that is a separate design question.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

No screenshots. I don't have a running instance with the sample dataset in this environment, so rather than post a mock-up I captured the regression as an exact color assertion instead — the observable symptom is the alpha channel of the cell background:

largest detail cell (leaf max 40, grand total 100)
Before #ACE1C46E — ~43% opacity, visibly unshaded
After #ACE1C4FF — fully saturated

TESTING INSTRUCTIONS

Two tests in superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts:

  • conditional formatting scales over leaf cells only, not rollup totals — feeds a realistic GROUPING SETS response (leaf cells + row totals + column totals + grand total, with __superset_grouping markers) and asserts the largest leaf cell is fully saturated. Fails on master with #ACE1C46E, passes with this change.
  • conditional formatting on the additive path uses the raw leaf query rows — the additive counterpart, pinning that domain to the leaf max. Passes both before and after, documenting that the additive path is untouched.
npx jest plugins/plugin-chart-pivot-table
# 9 suites, 109 tests passed

Manual:

  1. Pivot table on a dataset with a non-additive metric — a saved metric, or AVG(...)/COUNT_DISTINCT(...).
  2. Put a dimension on rows and another on columns.
  3. Enable Show rows total and Show column total.
  4. Add conditional formatting metric > 0 with a color and gradient enabled.
  5. Before: cells are barely tinted, and get fainter the more totals are shown. After: the gradient spans the detail cells, with the largest one fully saturated.

Review guidance

One hunk in superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts; read it first, then the tests.

Both review points from the first round are addressed:

  • Additive path semantics (@CodeAnt-AI) — the original patch selected the leaf frame out of the already-split data, which on the additive path meant the synthesized leaf level. synthesizeAdditiveLevels coerces through Number and drops non-numeric values, so that could shift the domain for additive metrics, contradicting my claim that the path was unchanged. It now uses the raw leaf query rows on that branch, so the claim holds by construction rather than by argument. Good catch.
  • Misleading test comment (@Copilot) — reworded to state explicitly that isAdditiveMetric treats any string metric as a non-additive saved-metric reference regardless of its label, which is exactly why a saved metric named SUM(sales) takes this path.

The ?? [] on the non-additive branch only covers a findIndex miss, which buildGroupbyCombinations makes unreachable — the full-length prefix on both axes is always emitted and the combineMetric filters keep it.

Risk & rollback

Frontend-only, no feature flag, no migration. Blast radius is the pivot table's conditional formatting color domain. The only behavioural change is for non-additive metrics with totals enabled, where the current output is wrong. Additive metrics are unchanged. Revert the commits to roll back.

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

…cale

The conditional-formatting color scale was derived from the raw query
result. For non-additive metrics that result is a single GROUPING SETS
response carrying the rollup levels alongside the leaf rows, so enabling
"Show rows total"/"Show column total" pulled the subtotals and grand
total into the color domain. Those aggregates dominate the max, leaving
every detail cell nearly unshaded.

Derive the scale from the leaf level instead, so it spans only the cells
being shaded. Additive metrics are unaffected: their query already
returns leaf rows only.
Address review feedback:

- The additive fast path now feeds conditional formatting the raw leaf
  query rows again instead of the synthesized leaf level. The synthesis
  coerces values through Number and drops non-numeric ones, so routing
  the additive path through it could shift the color domain -- the
  additive path is now provably untouched, matching the claim made for
  it. Its query returns leaf rows only, so no totals can leak in.
- Select the non-additive leaf level by the level that groups every
  dimension (the splitter's own definition) rather than by counting
  dimensions.
- Clarify in the regression test that a string metric is a saved-metric
  reference treated as non-additive whatever its label, so "SUM(sales)"
  is not mistaken for an additive adhoc metric.
- Add the additive counterpart test pinning its domain to the leaf max.
@dosubot dosubot Bot added change:frontend Requires changing the frontend viz:charts:pivot Related to the Pivot Table charts labels Aug 24, 2026
@bito-code-review

bito-code-review Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #978b65

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: c557220..b5d17b9
    • superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts
    • superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ 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

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. The current implementation filters the domain for conditional formatting to include only leaf rows, which prevents subtotals from skewing the color scale, but it inadvertently causes subtotals to lose their conditional formatting because they are no longer part of the domain used by getCellColor.

To resolve this, you should include the rendered subtotal values in the domain while ensuring they do not influence the detail scale. This can be achieved by calculating the color scale based only on leaf rows, but allowing the getCellColor function to evaluate subtotals against that established scale.

Would you like me to fetch all other comments on this PR to validate and implement fixes for them as well?

superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts

const metricColorFormatters = getColorFormatters(
    pivotConditionalFormatting,
    colorScaleRows,
    theme,
  );

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.94%. Comparing base (fd3849c) to head (0f86a07).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #43481      +/-   ##
==========================================
+ Coverage   78.93%   78.94%   +0.01%     
==========================================
  Files        2877     2878       +1     
  Lines      165232   165115     -117     
  Branches    38184    38162      -22     
==========================================
- Hits       130418   130356      -62     
+ Misses      32352    32309      -43     
+ Partials     2462     2450      -12     
Flag Coverage Δ
javascript 74.32% <100.00%> (+<0.01%) ⬆️

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.

@gabotorresruiz gabotorresruiz 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 the thorough writeup @aminghadersohi, the PR body made this straightforward to verify.

I checked the claims empirically on this branch:

  • The new regression test fails on master with exactly #ACE1C46E and passes here. The additive counterpart passes on both sides, pinning that path as unchanged.
  • Full plugin suite is green locally with this change: 9 suites, 110 tests.
  • buildQuery.ts confirms both query shapes: the additive path issues a single leaf query with no grouping_sets, so queriesData[0].data cannot contain totals, and the non-additive path sends one grouping-sets level per displayed total.
  • TableRenderers.tsx has three getCellColor call sites (column headers, row headers, pvtVal cells) and none of them touch the pvtTotal cells or the totals row, so totals rendering is indeed untouched.

On the CodeAnt thread about subtotals losing formatting: the delta is real, but only for the None comparator, and I would not treat it as a blocker. I ran the exact scenario on both sides of the fix (non-additive metric, rowSubTotals/colSubTotals on, leaf max 40, subtotal 70):

  • None comparator: before, getColorFromValue(70) returned #ACE1C4AB; after, it returns undefined, so a subtotal cell above the leaf max loses its tint.
  • Bounded comparators (> 0 and friends): getOpacity clamps at full opacity, so the same subtotal renders fully saturated before and after.

Three reasons I read that delta as the correct behavior rather than a regression:

  1. The additive path already behaves exactly this way on master. I ran the same subtotal scenario with a SIMPLE SUM metric against master and None returns undefined for the subtotal there too. This PR makes the non-additive path consistent with it.
  2. It restores the pre-SIP-216 behavior: before #41184 the color domain was queriesData[0].data, which was the leaf-level query.
  3. The Table chart keeps its totals in a separate query and feeds only the base query rows to getColorFormatters, so a leaf-only domain is the established convention.

Whether subtotal and total cells should be colored against a leaf-derived scale at all is the separate design question you already scoped out, and I agree it does not belong in this fix.

LGTM.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Thank you @gabotorresruiz for the exceptionally thorough validation and approval — especially for reproducing the None and bounded-comparator subtotal cases across both query paths. I’ve replied to and resolved the CodeAnt thread with that rationale: the leaf-only domain is intentional and restores the established behavior without reintroducing rollup-driven scale distortion.

@aminghadersohi
aminghadersohi merged commit b8308a4 into master Aug 26, 2026
80 checks passed
@aminghadersohi
aminghadersohi deleted the aminghadersohi/pivot-table-conditional-formatting-totals branch August 26, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:frontend Requires changing the frontend plugins size/L viz:charts:pivot Related to the Pivot Table charts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants