Skip to content

Conversation

@aminghadersohi
Copy link
Contributor

SUMMARY

Add support for form_data_key (Explore) and permalink_key (Dashboard) parameters in MCP tools to retrieve unsaved/temporary state.

Problem:
When a user edits a chart in Explore (changes type, metrics, etc.) but doesn't save, the current state is stored with a form_data_key. Similarly, when users apply filters in a dashboard, the state can be persisted in a permalink_key. The MCP tools were previously unaware of these keys and would only retrieve the saved version of charts/dashboards.

This meant when users asked the chatbot to explain a chart they were editing, it would describe the saved version rather than what the user actually sees.

Solution:

  • Add form_data_key parameter to chart tools (get_chart_info, get_chart_data, get_chart_preview) to retrieve unsaved chart configuration from cache
  • Add permalink_key parameter to get_dashboard_info to retrieve dashboard filter state from permalink
  • Add is_unsaved_state/is_permalink_state flags to indicate when data came from cache vs saved version

Changes:

  • superset/mcp_service/chart/schemas.py: Add form_data_key to request schemas, add is_unsaved_state and form_data_key to ChartInfo
  • superset/mcp_service/chart/tool/get_chart_info.py: Retrieve cached form_data when form_data_key provided
  • superset/mcp_service/chart/tool/get_chart_data.py: Build query context from cached form_data
  • superset/mcp_service/dashboard/schemas.py: Add permalink_key to request schema, add filter_state and is_permalink_state to DashboardInfo
  • superset/mcp_service/dashboard/tool/get_dashboard_info.py: Retrieve filter state from permalink

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A - API changes only

TESTING INSTRUCTIONS

  1. Start Superset with MCP service enabled
  2. Create or open an existing chart in Explore
  3. Make changes to the chart (e.g., change viz type, metrics) without saving
  4. Copy the form_data_key from the URL
  5. Call get_chart_info with the chart ID and the form_data_key
  6. Verify the response contains is_unsaved_state: true and the form_data reflects the unsaved changes

For dashboards:

  1. Open a dashboard and apply filters
  2. Copy the permalink_key from the URL (if using permalinks)
  3. Call get_dashboard_info with the dashboard ID and the permalink_key
  4. Verify the response contains is_permalink_state: true and filter_state with the applied filters

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

Add support for form_data_key (Explore) and permalink_key (Dashboard)
parameters in MCP tools to retrieve unsaved/temporary state.

When a user edits a chart in Explore but hasn't saved, the current state
is stored with a form_data_key. Similarly, when users apply filters in a
dashboard, the state can be persisted in a permalink. The MCP tools were
previously unaware of these keys and would only retrieve the saved version.

Changes:
- Add form_data_key parameter to GetChartInfoRequest, GetChartDataRequest,
  and GetChartPreviewRequest schemas
- Add permalink_key parameter to GetDashboardInfoRequest schema
- Update get_chart_info to retrieve cached form_data when form_data_key
  is provided
- Update get_chart_data to build query context from cached form_data
- Update get_dashboard_info to retrieve filter state from permalink
- Add is_unsaved_state/is_permalink_state flags to response schemas
  to indicate when data came from cache vs saved version

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@codeant-ai-for-open-source
Copy link
Contributor

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@dosubot dosubot bot added the api Related to the REST API label Jan 15, 2026
@netlify
Copy link

netlify bot commented Jan 15, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit ef20ba0
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/69697a32abeaf50008987912
😎 Deploy Preview https://deploy-preview-37183--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@codeant-ai-for-open-source
Copy link
Contributor

CodeAnt AI finished reviewing your PR.

Copy link
Contributor

@bito-code-review bito-code-review bot left a comment

Choose a reason for hiding this comment

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

Code Review Agent Run #d14d53

Actionable Suggestions - 4
  • superset/mcp_service/chart/tool/get_chart_info.py - 2
  • superset/mcp_service/chart/tool/get_chart_data.py - 1
    • Blind exception catch without specific type · Line 56-56
  • superset/mcp_service/dashboard/tool/get_dashboard_info.py - 1
Review Details
  • Files reviewed - 5 · Commit Range: ef20ba0..ef20ba0
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/tool/get_chart_data.py
    • superset/mcp_service/chart/tool/get_chart_info.py
    • superset/mcp_service/dashboard/schemas.py
    • superset/mcp_service/dashboard/tool/get_dashboard_info.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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 [email protected].

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines +47 to +52
try:
cmd_params = CommandParameters(key=form_data_key)
return GetFormDataCommand(cmd_params).run()
except Exception as e:
logger.warning("Failed to retrieve form_data from cache: %s", e)
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

Blind exception catch too broad

Avoid catching bare Exception at line 50. Catch specific exception types (e.g., KeyError, ValueError) to improve error handling clarity and prevent masking unexpected errors.

Code suggestion
Check the AI-generated fix before applying
Suggested change
try:
cmd_params = CommandParameters(key=form_data_key)
return GetFormDataCommand(cmd_params).run()
except Exception as e:
logger.warning("Failed to retrieve form_data from cache: %s", e)
return None
try:
cmd_params = CommandParameters(key=form_data_key)
return GetFormDataCommand(cmd_params).run()
except (KeyError, ValueError) as e:
logger.warning("Failed to retrieve form_data from cache: %s", e)
return None

Code Review Run #d14d53


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Contributor

Choose a reason for hiding this comment

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

This question isn’t related to the pull request. I can only help with questions about the PR’s code or comments.

Comment on lines +132 to +138
await ctx.info(
"Chart form_data overridden with unsaved state from cache"
)
except Exception as e:
await ctx.warning(
"Failed to parse cached form_data: %s. "
"Using saved chart configuration." % (str(e),)
Copy link
Contributor

Choose a reason for hiding this comment

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

Blind exception catch too broad

Avoid catching bare Exception at line 135. Catch specific exception types (e.g., json.JSONDecodeError, ValueError) to improve error handling clarity.

Code suggestion
Check the AI-generated fix before applying
Suggested change
await ctx.info(
"Chart form_data overridden with unsaved state from cache"
)
except Exception as e:
await ctx.warning(
"Failed to parse cached form_data: %s. "
"Using saved chart configuration." % (str(e),)
await ctx.info(
"Chart form_data overridden with unsaved state from cache"
)
except (ValueError, TypeError) as e:
await ctx.warning(
"Failed to parse cached form_data: %s. "
"Using saved chart configuration." % (str(e),)

Code Review Run #d14d53


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

try:
cmd_params = CommandParameters(key=form_data_key)
return GetFormDataCommand(cmd_params).run()
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

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

Blind exception catch without specific type

Replace bare Exception catch with specific exception types. Consider catching (KeyError, ValueError, TypeError) or other specific exceptions expected from GetFormDataCommand.run().

Code suggestion
Check the AI-generated fix before applying
Suggested change
except Exception as e:
except (KeyError, ValueError, TypeError) as e:

Code Review Run #d14d53


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Copy link
Member

Choose a reason for hiding this comment

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

^ this

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a code suggestion to improve exception handling by catching specific exceptions (KeyError, ValueError, TypeError) instead of the broad 'Exception' in the _get_cached_form_data function. It aims to be more precise about what errors to handle when retrieving cached form data.

superset/mcp_service/chart/tool/get_chart_data.py

except (KeyError, ValueError, TypeError) as e:

result = GetDashboardPermalinkCommand(permalink_key).run()
# Convert TypedDict to regular dict for type compatibility
return dict(result) if result else None
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

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

Blind exception catch too broad

Replace bare Exception catch with specific exception types (e.g., Exception from the command or ValueError). This prevents masking unexpected errors.

Code suggestion
Check the AI-generated fix before applying
Suggested change
except Exception as e:
except (ValueError, KeyError, Exception) as e:

Code Review Run #d14d53


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Contributor

Choose a reason for hiding this comment

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

This question isn’t related to the pull request. I can only help with questions about the PR’s code or comments.

@codecov
Copy link

codecov bot commented Jan 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.85%. Comparing base (82d74d1) to head (ef20ba0).

Additional details and impacted files
@@             Coverage Diff             @@
##           master   #37183       +/-   ##
===========================================
+ Coverage        0   67.85%   +67.85%     
===========================================
  Files           0      642      +642     
  Lines           0    48077    +48077     
  Branches        0     5261     +5261     
===========================================
+ Hits            0    32622    +32622     
- Misses          0    14168    +14168     
- Partials        0     1287     +1287     
Flag Coverage Δ
hive 42.85% <ø> (?)
mysql 65.87% <ø> (?)
postgres 65.92% <ø> (?)
presto 46.42% <ø> (?)
python 67.82% <ø> (?)
sqlite 65.64% <ø> (?)
unit 100.00% <ø> (?)

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

☔ View full report in Codecov by Sentry.
📢 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.

try:
cmd_params = CommandParameters(key=form_data_key)
return GetFormDataCommand(cmd_params).run()
except Exception as e:
Copy link
Member

Choose a reason for hiding this comment

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

^ this

Comment on lines +164 to +166
cached_form_data = _get_cached_form_data(request.form_data_key)

if cached_form_data:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
cached_form_data = _get_cached_form_data(request.form_data_key)
if cached_form_data:
if cached_form_data := _get_cached_form_data(request.form_data_key):

# If using cached form_data, we need to build query_context from it
if using_unsaved_state and cached_form_data_dict:
# Build query context from cached form_data (unsaved state)
from superset.common.query_context_factory import QueryContextFactory
Copy link
Member

Choose a reason for hiding this comment

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

Let's move imports to top-level, unless they're here to prevent circular imports.

Comment on lines +44 to +45
from superset.commands.explore.form_data.get import GetFormDataCommand
from superset.commands.explore.form_data.parameters import CommandParameters
Copy link
Member

Choose a reason for hiding this comment

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

Move to top-level if possible.

Comment on lines +47 to +52
try:
cmd_params = CommandParameters(key=form_data_key)
return GetFormDataCommand(cmd_params).run()
except Exception as e:
logger.warning("Failed to retrieve form_data from cache: %s", e)
return None
Copy link
Member

Choose a reason for hiding this comment

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

+1

try:
result = GetDashboardPermalinkCommand(permalink_key).run()
# Convert TypedDict to regular dict for type compatibility
return dict(result) if result else None
Copy link
Member

Choose a reason for hiding this comment

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

result is already a dict, all you're doing here is losing type information.

It's better to make:

def _get_permalink_state(permalink_key: str) -> DashboardPermalinkValue | None:

And fix whatever is calling _get_permalink_state().

result = GetDashboardPermalinkCommand(permalink_key).run()
# Convert TypedDict to regular dict for type compatibility
return dict(result) if result else None
except Exception as e:
Copy link
Member

Choose a reason for hiding this comment

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

+1

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

Labels

api Related to the REST API size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants