Skip to content

fix(plugin-chart-table): re-measure sticky layout when the table gains a box - #44430

Merged
sadpandajoe merged 4 commits into
masterfrom
ultraset-fix-44415
Sep 21, 2026
Merged

sadpandajoe merged 4 commits into
masterfrom
ultraset-fix-44415

Conversation

@sadpandajoe

Copy link
Copy Markdown
Member

SUMMARY

A table chart that mounts inside a display: none subtree — an inactive dashboard tab — measures its header against zero-sized boxes, so StickyWrap's layout effect bails out at its if (!theadHeight) return; guard, computes no sticky layout, and renders only its visibility: hidden sizer. The chart is blank even though its query response arrived successfully.

Nothing brings it back. The measurement effect's dependencies are maxWidth, maxHeight, setStickyState and the scrollbar size; the width and height are static grid-layout math from ChartHolder, not measurements, so none of them change when the tab becomes visible. The measurement never runs again and the chart stays blank until it is force-refreshed (a remount, this time while visible) or the window is resized (a real width change) — exactly the two workarounds in #44415.

This is why the issue reproduces only when the filter is applied from another tab: applying it on the chart's own tab does the one and only measurement while the chart has a box. It is also why only some charts blank — ECharts-based charts pass width/height explicitly into init() (plugins/plugin-chart-echarts/src/components/Echart.tsx, with a comment saying it exists to avoid depending on DOM size) and are immune.

No introducing commit exists in this repository. git log -S shows both the if (!theadHeight) return; guard and the [maxWidth, maxHeight, setStickyState, scrollBarSize] dependency list arriving with the monorepo import, 3c41ff6 (#17552), so they came in already-formed from the external superset-ui repo. For history: #10432 (2020) fixed the same class of bug in the then-separate @superset-ui/plugin-chart-table via a version bump, and #22009 (2022) fixed the dashboard-side resize trigger in the old class-based Chart.jsx; that dashboard-side guard has no equivalent in today's hooks-based Chart.tsx, but it would not help here either, since the chart's width/height props genuinely do not change on a tab switch.

The fix observes the sticky wrapper with a ResizeObserver and measures again once it gains a box. The observer is attached only while no sticky layout has been computed and disconnects as soon as one exists, so charts that measured normally are untouched; measure re-reads the DOM itself, so a still-boxless notification is a no-op. A stable wrapper ref is observed rather than the existing theadRef, because that ref is only attached while the sizer is rendered and its identity changes across renders without signalling the effect.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

No screenshots: no running Superset instance was available in the environment where this was developed, so a genuine before/after of the blank chart could not be captured. The failure and the fix are demonstrated instead by the added regression test, which fails on master and passes with this change.

TESTING INSTRUCTIONS

  1. Create a dashboard with two tabs. Put a Table chart on tab 1; leave tab 2 empty (or put an unrelated chart on it).
  2. Add a native filter (e.g. a Year value filter) scoped to the Table chart on tab 1.
  3. Load the dashboard so tab 1 renders and the table paints, then switch to tab 2.
  4. From tab 2, pick a filter value and click Apply filters.
  5. Switch back to tab 1. The table should render the filtered rows immediately. On master it is blank, and only a per-chart Force refresh or a browser-window resize brings it back.
  6. Sanity check that nothing regressed for the normal path: apply a filter while already on tab 1, resize the window, and page through the table — the sticky header and column widths should behave as before.

ADDITIONAL INFORMATION

🤖 Generated with Claude Code

sadpandajoe and others added 3 commits September 18, 2026 18:03
…s a box

A table chart that mounts inside a `display: none` subtree -- an inactive
dashboard tab, for instance -- measures its header against zero-sized boxes,
so `StickyWrap`'s layout effect bails out at its `if (!theadHeight) return;`
guard, computes no sticky layout, and renders only its `visibility: hidden`
sizer. The chart is blank even though its query response arrived fine.

None of that effect's dependencies (`maxWidth`, `maxHeight`,
`setStickyState`, the scrollbar size) change when the tab becomes visible --
the width and height are static grid-layout math, not measurements -- so the
measurement never runs again and the chart stays blank until it is
force-refreshed or the window is resized.

Observe the wrapper with a ResizeObserver while no sticky layout has been
computed, and measure again once it gains a box.

Fixes #44415

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #45e6db

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset-frontend/plugins/plugin-chart-table/test/DataTable/hooks/useSticky.test.tsx - 1
    • Leaked globals on test failure · Line 257-286
      This test mutates `globalThis.ResizeObserver` (`installResizeObserverMock`, lines 218-239) and appends `host` to `document.body`, but restores them only inline at lines 283-285, after the `expect` calls at 270 and 281. Any assertion failure skips the cleanup, leaking the mock class, the callbacks array, the hidden host node, and the `clientHeight`/`getBoundingClientRect` spies into subsequent tests. Per BITO rule 14435, restore mutated globals in `afterEach` (or `try/finally`) alongside `jest.restoreAllMocks()`.
  • superset-frontend/plugins/plugin-chart-table/src/DataTable/hooks/useSticky.tsx - 1
    • Observer gate leaves stale-width gap · Line 242-242
      The observer detaches once `columnWidths` is set, so if `maxWidth`/`maxHeight` (from `getTableSize` via `useMountedMemo`) change while the wrap has no box, the stale widths persist and nothing re-measures when the tab is re-shown. If width props can update while hidden, gating on `needSizer` would cover that path too. The blank-chart case this targets does look covered.
Review Details
  • Files reviewed - 2 · Commit Range: 9c35bfd..8000101
    • superset-frontend/plugins/plugin-chart-table/src/DataTable/hooks/useSticky.tsx
    • superset-frontend/plugins/plugin-chart-table/test/DataTable/hooks/useSticky.test.tsx
  • Files skipped - 0
  • Tools
    • 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

@sadpandajoe
sadpandajoe requested review from rusackas and a lite review from Copilot September 21, 2026 17:08

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 2 Medium severity

Open (2)
What changed in this PR

Fixes table charts rendering blank when initially mounted under display: none (e.g., inactive dashboard tab) by re-triggering sticky layout measurement once the table gains a real box.

Changes:

  • Add a ResizeObserver fallback in useSticky to re-measure when the wrapper becomes visible.
  • Add a regression test that simulates display: none mounting and verifies the table paints after a resize notification.
File Description
superset-frontend/​plugins/​plugin-chart-table/​src/​DataTable/​hooks/​useSticky.tsx Observes wrapper size until sticky layout is computed, then re-measures to avoid blank render after hidden mount.
superset-frontend/​plugins/​plugin-chart-table/​test/​DataTable/​hooks/​useSticky.test.tsx Adds a regression test and mocks to simulate hidden subtree measurements and ResizeObserver notifications.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +240 to +250
useEffect(() => {
const wrap = wrapRef.current;
if (columnWidths || !wrap || typeof ResizeObserver === 'undefined') {
return undefined;
}
const observer = new ResizeObserver(() => {
measure();
});
observer.observe(wrap);
return () => observer.disconnect();
}, [columnWidths, measure]);
Comment on lines +265 to +285
host.style.display = 'none';
document.body.append(host);

render(<StickyTableHarness />, { container: host });

expect(visibleDataCellCount(host)).toBe(0);

// Switching to the tab gives the chart a real box. Neither `maxWidth`,
// `maxHeight`, `setStickyState` nor the scrollbar size changes, so without
// the ResizeObserver the sticky layout is never recomputed and the chart
// stays blank until it is force-refreshed or the window is resized.
host.style.display = '';
act(() => {
resizeObserver.trigger();
});

expect(visibleDataCellCount(host)).toBe(data.length * columns.length);

host.remove();
resizeObserver.restore();
jest.restoreAllMocks();
…sertion failure

Move host.remove()/resizeObserver.restore()/jest.restoreAllMocks() into a
finally block so a failing assertion mid-test can't leak the mocked
ResizeObserver or the hidden host node into later tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Sep 21, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit c49887b
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ab17d4f7485840007ccfcef
😎 Deploy Preview https://deploy-preview-44430--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.

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks for tracking this down to the display:none mount race and adding a regression test for it.

@bito-code-review

bito-code-review Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #58c810

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 8000101..c49887b
    • superset-frontend/plugins/plugin-chart-table/test/DataTable/hooks/useSticky.test.tsx
  • Files skipped - 0
  • Tools
    • 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

@sadpandajoe
sadpandajoe merged commit c355f0a into master Sep 21, 2026
79 checks passed
@sadpandajoe
sadpandajoe deleted the ultraset-fix-44415 branch September 21, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Charts render blank after applying a filter from another dashboard tab (6.0.0)

3 participants