Skip to content

feat: Use gridstack for the layout - #43096

Open
michael-s-molina wants to merge 1 commit into
apache:dashboard-v2from
michael-s-molina:gridstack-layout
Open

feat: Use gridstack for the layout#43096
michael-s-molina wants to merge 1 commit into
apache:dashboard-v2from
michael-s-molina:gridstack-layout

Conversation

@michael-s-molina

Copy link
Copy Markdown
Member

SUMMARY

Migrates the dashboard v2 prototype's root grid from react-grid-layout to GridStack.js, and uses the swap to add real positioning flexibility that react-grid-layout's own isDroppable couldn't support: a block can now be dropped or dragged to any position on the canvas — including directly onto another block — and the canvas reacts by opening exactly the room that block needs.

Concretely, an author can now:

  • Drop anywhere in open space — not just "append to the end". A palette block dropped into a gap between two existing blocks lands in that gap, sized to fit it.
  • Split an existing block left/right — dropping (or dragging an existing block) onto the left or right half of another block shrinks that block to the other half and inserts the new one beside it, live. The block being shrunk is the real block visibly resizing during the hover, not a placeholder box standing in for it — the preview is provably what you get.
  • Insert a full-width row above or below any existing block by hovering its top/bottom edge, pushing siblings down rather than overwriting them.
  • Do all of the above by repositioning an existing block, not just by dragging a fresh one in from the palette — dragging one block onto another's half splits it exactly the same way a palette drop would.

None of this was reachable with react-grid-layout's isDroppable, whose live drop preview is driven by an internal fake-drag simulation that produced contradictory failures with no reliable way to debug them (see commit history for the earlier attempts). GridStack's lower-level imperative API replaces that with code this PR owns and can debug directly: React stays the single source of truth for layout (a "single writer" rule threaded through useGridStack.ts), GridStack is used purely as a renderer and gesture source, and the palette keeps its existing native HTML5 drag (GridStack's own drag-in system is pointer-based and can't see it) with our own drop-preview drawn on top instead of a library-owned placeholder.

Also fixes several bugs found during manual verification of the swap (GridStack's own auto: true silently claiming widgets present at mount before this code got a chance to register them properly; an over-broad cancel selector that vetoed every drag inside the grid, not just presses on nested containers; a couple of containerRef-as-callback casting bugs that collapsed the drop-preview's width to its own border), and moves the canvas's scroll ownership to the page-level Canvas container so RootGrid and Canvas no longer fight over the same scroll gesture.

react-grid-layout is removed from package.json now that nothing references it.

VIDEO

Screen.Recording.2026-08-12.at.15.55.39.mov

TESTING INSTRUCTIONS

Added both unit tests (gridPacking.test.ts, RootGrid.test.tsx, DashboardBuilderV2/index.test.tsx) and a new Playwright E2E suite under playwright/tests/experimental/dashboard-v2/ (run with INCLUDE_EXPERIMENTAL=true npm run playwright:test), covering placement, the split gesture, drag/resize persistence, and scroll ownership. The E2E suite specifically needs a real browser — jsdom has no layout engine, so none of the bugs this PR fixes were visible to a jsdom-based test while they were live.

To verify manually, on /dashboard/v2/new/: drag palette blocks around an empty and a sparsely-filled canvas, drop one onto another's left/right half (and try dragging an existing block onto a half too), reposition/resize an existing block, and scroll a tall canvas — everything above should behave as described in the Summary.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Introduces new feature or API
  • Removes existing feature or API

@dosubot dosubot Bot added change:frontend Requires changing the frontend dashboard:component Related to the drag&drop components of the Dashboard labels Aug 12, 2026
@bito-code-review

bito-code-review Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped - Branch Excluded

Bito didn't auto-review because the source or target branch is excluded from automatic reviews.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change the branch exclusion settings here, or contact your Bito workspace admin at evan@preset.io.

@netlify

netlify Bot commented Aug 12, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

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

* is untouched by it — `availableDropSpan` still returns exactly that gap's
* own width, never wider.
*/
export const FALLBACK_COL_SPAN = Math.floor(DEFAULT_COLUMNS / 2);

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: FALLBACK_COL_SPAN is fixed at 12 columns, but RootGrid passes it unchanged for containers whose configured column count differs from DEFAULT_COLUMNS. An empty 12-column (or narrower) grid therefore previews and places an open-space drop at full width, defeating the new behavior intended to keep the drop width responsive to the cursor. Derive the cap from the active container's columns value, or clamp this fallback to less than the container width when appropriate. [logic error]

Severity Level: Major ⚠️
- ⚠️ Custom 12-column root grids lose responsive open-space drop sizing.
- ⚠️ Palette drops preview and land at the entire active grid width.
- ⚠️ Cursor position becomes ineffective in empty custom-column grids.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-frontend/src/core/dashboard/placement.ts
**Line:** 100:100
**Comment:**
	*Logic Error: `FALLBACK_COL_SPAN` is fixed at 12 columns, but `RootGrid` passes it unchanged for containers whose configured column count differs from `DEFAULT_COLUMNS`. An empty 12-column (or narrower) grid therefore previews and places an open-space drop at full width, defeating the new behavior intended to keep the drop width responsive to the cursor. Derive the cap from the active container's `columns` value, or clamp this fallback to less than the container width when appropriate.

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 flagged issue is correct. The FALLBACK_COL_SPAN constant is currently hardcoded to 12, which causes open-space drops to default to full width even in grids configured with fewer than 12 columns. To resolve this, FALLBACK_COL_SPAN should be derived dynamically from the active container's column count rather than being a static constant.

Since FALLBACK_COL_SPAN is used in multiple places, you should update its definition in superset-frontend/src/core/dashboard/placement.ts to be a function or a value that respects the container's configuration. If you would like me to implement this fix and check the remaining comments on this PR, please let me know.

superset-frontend/src/core/dashboard/placement.ts

// Instead of a static constant:
// export const FALLBACK_COL_SPAN = Math.floor(DEFAULT_COLUMNS / 2);

// Consider a function that accepts the container's column count:
export const getFallbackColSpan = (columns: number) => Math.floor(columns / 2);

Comment on lines +68 to +69
function cancelSelectorFor(nodeId: string): string {
return `[data-container-id]:not([data-container-id="${CSS.escape(nodeId)}"]),[data-block-remove],[data-block-resize],[data-block-header-control]`;

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: CSS.escape is invoked during every render, but the jsdom test environment does not provide this API or install a polyfill. Mounting RootGrid therefore throws before GridStack initialization, causing the dashboard tests (and any supported runtime without CSS.escape) to fail. Use an available escaping utility or provide the required polyfill before constructing the selector. [runtime compatibility]

Severity Level: Major ⚠️
- ❌ RootGrid jsdom tests fail during component rendering.
- ❌ Dashboard v2 fails in runtimes lacking `CSS.escape`.
- ⚠️ GridStack initialization never runs.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-frontend/src/core/dashboard/RootGrid.tsx
**Line:** 68:69
**Comment:**
	*Runtime Compatibility: `CSS.escape` is invoked during every render, but the jsdom test environment does not provide this API or install a polyfill. Mounting `RootGrid` therefore throws before GridStack initialization, causing the dashboard tests (and any supported runtime without `CSS.escape`) to fail. Use an available escaping utility or provide the required polyfill before constructing the selector.

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
👍 | 👎

Comment on lines +490 to +512
if (placement.shrink) {
const updates: Record<string, Partial<LayoutProps>> = {};
items.forEach(({ id, rect: itemRect }) => {
updates[id] = {
col: itemRect.x + 1,
row: itemRect.y + 1,
colSpan: itemRect.w,
rowSpan: itemRect.h,
};
});
updates[gesture.id] = {
col: placement.rect.x + 1,
row: placement.rect.y + 1,
colSpan: placement.rect.w,
rowSpan: placement.rect.h,
};
updates[placement.shrink.id] = {
col: placement.shrink.rect.x + 1,
row: placement.shrink.rect.y + 1,
colSpan: placement.shrink.rect.w,
rowSpan: placement.shrink.rect.h,
};
provider.updateLayouts(updates);

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: The split path combines GridStack's post-collision positions from items with a manually calculated split based on the pre-drag packed map, then writes them through updateLayouts, which does not run collision resolution. For example, GridStack can displace a sibling while the dragged item overlaps the target; this code moves the target back to its original row and shrinks it while retaining the displaced sibling's position, allowing the target and sibling to overlap in the persisted layout. Reconcile the split against the final occupancy or run the same collision resolution before committing. [logic error]

Severity Level: Major ⚠️
- ❌ Existing-block split can persist overlapping siblings.
- ⚠️ Subsequent grid rendering may reposition affected blocks.
- ⚠️ Stored layout coordinates can diverge from GridStack's final occupancy.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-frontend/src/core/dashboard/RootGrid.tsx
**Line:** 490:512
**Comment:**
	*Logic Error: The split path combines GridStack's post-collision positions from `items` with a manually calculated split based on the pre-drag `packed` map, then writes them through `updateLayouts`, which does not run collision resolution. For example, GridStack can displace a sibling while the dragged item overlaps the target; this code moves the target back to its original row and shrinks it while retaining the displaced sibling's position, allowing the target and sibling to overlap in the persisted layout. Reconcile the split against the final occupancy or run the same collision resolution before committing.

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
👍 | 👎

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.45833% with 39 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (dashboard-v2@46be6a0). Learn more about missing BASE report.

Files with missing lines Patch % Lines
superset-frontend/src/core/dashboard/RootGrid.tsx 73.98% 32 Missing ⚠️
...et-frontend/src/pages/DashboardBuilderV2/index.tsx 86.48% 5 Missing ⚠️
...perset-frontend/src/core/dashboard/useGridStack.ts 97.14% 2 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##             dashboard-v2   #43096   +/-   ##
===============================================
  Coverage                ?   65.38%           
===============================================
  Files                   ?     2828           
  Lines                   ?   159215           
  Branches                ?    36485           
===============================================
  Hits                    ?   104100           
  Misses                  ?    53138           
  Partials                ?     1977           
Flag Coverage Δ
javascript 71.47% <86.45%> (?)

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.

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 dashboard:component Related to the drag&drop components of the Dashboard dependencies:npm size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants