Repository navigation
fix(canvas): replay canceled draft widgets, keep nullable resets, expose op schema - #45826
Conversation
…ose op schema Draft lifecycle: - Keep a draft's new widget entities until the final replay, so drafts that add and then remove (or re-add) a new widget commit the intended tree without orphan widget rows. - Log explicit null `set_scope.scope` / `set_settings.value` resets so they survive draft replay and commit. - Commit returns a typed result with the canvas revision actually written or observed; an empty draft close reports the current revision instead of the fork revision. MCP and REST contract: - `apply_canvas_draft_ops.ops` and `create_canvas.ops` advertise the canonical operation union in their input schemas while keeping flat arguments, camelCase keys, omitted-vs-null semantics and indexed error paths. - Conflict and error results carry `error_type` plus explicit draft/canvas revision provenance (camelCase on REST, snake_case on MCP); legacy keys are unchanged. - Tool descriptions lead with publish intent and conflict recovery so they survive tool-search truncation. Tests cover the lifecycle regressions, the native and proxied MCP contract, real SQLite aggregate queries through ChartDataCommand, and REST payloads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
| if changed: | ||
| raise DraftConflictError(canvas.revision, widgets=sorted(changed)) | ||
| raise DraftConflictError( | ||
| canvas.revision, | ||
| widgets=sorted(changed), | ||
| current_canvas_revision=canvas.revision, | ||
| ) |
There was a problem hiding this comment.
Suggestion: A concurrent widget edit during a normal MCP apply raises DraftConflictError, which _apply_canvas_ops_impl does not catch, so the tool fails instead of returning the conflict payload.
Assessment: 🟠 Major · 🔁 Occurrence: Rarely · 🏷️ Api mismatch
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset/commands/canvas/apply_ops.py
**Line:** 274:279
**Comment:**
*Api Mismatch: A concurrent widget edit during a normal MCP apply raises `DraftConflictError`, which `_apply_canvas_ops_impl` does not catch, so the tool fails instead of returning the conflict payload.
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| slug: str | None = None, | ||
| description: str | None = None, | ||
| ops: list[dict[str, Any]] | None = None, | ||
| ops: InitialOpsInput | None = None, |
There was a problem hiding this comment.
Suggestion: ops runs after the canvas is committed in a separate transaction; if it fails, a concurrent successful edit can be erased by the cleanup delete.
Assessment: 🔴 Critical · 🔁 Occurrence: Rarely · 🏷️ Race condition
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset/mcp_service/canvas/tool/create_canvas.py
**Line:** 95:95
**Comment:**
*Race Condition: `ops` runs after the canvas is committed in a separate transaction; if it fails, a concurrent successful edit can be erased by the cleanup delete.
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 Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## msyavuz/feat/canvas #45826 +/- ##
=======================================================
+ Coverage 84.37% 84.41% +0.03%
=======================================================
Files 3142 3142
Lines 206454 206493 +39
Branches 47922 47925 +3
=======================================================
+ Hits 174196 174306 +110
+ Misses 28929 28863 -66
+ Partials 3329 3324 -5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| On any error nothing changes, and the error names the operation and the | ||
| path at fault. ``ValidationError`` leaves the draft unchanged. | ||
|
|
||
| Operations (placement ids are readable slugs; ``parent`` defaults to |
There was a problem hiding this comment.
Since the input schema now carries the full Operation union, can this list shrink to the rules the schema can't express (1-based grid, scope: null resets, widgetId vs widgetType)? Otherwise agents get the grammar twice.
There was a problem hiding this comment.
Good call, done in 360221f. Every listing that shows the full docstring also serves the input schema, and search results cut the description at 300 chars before the list starts, so the list was duplication wherever an agent could see it. The docstring now keeps only what the schema can't express: widgetType+props vs widgetId on add (never both), set_props as a complete replacement read from get_canvas_draft's widgets, the required-nullable scope/value resets, and the 1-based layout grid plus overlap rules. The native description drops from 2,637 to 1,970 chars; the regenerated inventory also refreshes commit_canvas_draft's stale entry.
…hema can't express The ops input schema carries the full Operation union, so the docstring no longer repeats each operation's shape. It keeps the rules the schema cannot express: widgetType+props vs widgetId on add, complete-replacement set_props, required-nullable scope/value resets, and the 1-based layout grid. Regenerate the native tool inventory; this also refreshes the commit_canvas_draft entry, whose description changed after the last regeneration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tez1802/canvas-upstream-functional-fixes Resolve the native_tool_inventory.json totals conflict by regenerating the inventory on the merged tree: only the Canvas entries differ from the base. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SUMMARY
Functional fixes for the Canvas draft lifecycle and its MCP/REST contract, targeting
msyavuz/feat/canvas.Draft lifecycle (3 bugs, each reproduced before the fix)
keep_unplaced). The commit then drops new widgets that end up unplaced, so no orphan widget rows are written. Edits to existing widgets are never pruned.set_scope.scope: nullandset_settings.value: nullwere removed from the replay log (exclude_none), so replay failed withField required. The nulls are now logged explicitly. An omitted field is still a validation error, never a reset.base_revision, even after another publish.CommitCanvasDraftCommandnow returns a typedCommitResultwith the canvas revision actually written or observed. Both callers (REST and MCP) are updated.MCP contract
apply_canvas_draft_ops.opsandcreate_canvas.opsadvertise the canonicalOperationunion in their input schemas (BeforeValidator(json_schema_input_type=...)). There is no second, handwritten grammar.create_canvasstill acceptsopsomitted,nullor[]./ops/1/index) are preserved.error_type(ValidationError,DraftUnavailable,DraftRevisionConflict,CanvasRevisionConflict,WidgetRevisionConflict),draft_revisionandcurrent_canvas_revision. Legacy keys are unchanged.commit_canvas_draftandapply_canvas_draft_opsnow lead with publish intent and conflict recovery. That guidance survives the 300-character tool-search truncation.REST
error_type,draftRevisionandcurrentCanvasRevisionnext to the existingrevisionandcanvasChanged.error_type: DraftUnavailable.Behavior worth knowing (pinned by tests, documented in
mcp-server.mdx): a draft whose operations cancel out, e.g. add a new widget and then remove it, commits as a real write. That is the same result as publishing those operations without a draft. If the canvas changed meanwhile, the commit returns 409CanvasRevisionConflict. Treating such a draft as an empty close would be a new behavior and is left for a follow-up.The query translator and chart-data path were already correct. The new real-SQL tests confirm this, and no query code changed.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A (backend and MCP only).
TESTING INSTRUCTIONS
tests/unit_tests/mcp_serviceplustests/unit_tests/canvasgives 13,665 passed and 0 failed (base: 13,502 passed).Test additions:
tests/unit_tests/canvas/widget_entities_test.py,draft_test.py,api_test.py: lifecycle regressions, conflict precedence and provenance, REST payloads.tests/unit_tests/canvas/placement_data_test.py: real SQLite aggregate queries (top and bottom 5, unsorted limit, query-text visibility) through an unmockedChartDataCommand.tests/unit_tests/mcp_service/canvas/tool/test_canvas_contract.py(new): schema exposure, and native vs.search_tools/call_toolproxy parity with protected wrappers and RBAC enabled.native_tool_inventory.jsonand theapply_canvas_draft_ops/create_canvasbudgets intest_tool_inventory.pywere regenerated or raised. The inlined operation grammar grows each entry to about 5.3 KB. Only Canvas entries and catalog totals changed.ADDITIONAL INFORMATION
CANVAS🤖 Generated with Claude Code