Skip to content

fix(mcp): resolve dashboard permalinks - #42659

Closed
aminghadersohi wants to merge 6 commits into
apache:masterfrom
aminghadersohi:aminghadersohi/resolve-dashboard-permalinks
Closed

fix(mcp): resolve dashboard permalinks#42659
aminghadersohi wants to merge 6 commits into
apache:masterfrom
aminghadersohi:aminghadersohi/resolve-dashboard-permalinks

Conversation

@aminghadersohi

Copy link
Copy Markdown
Contributor

SUMMARY

Dashboard lookup tools can resolve shared /dashboard/p/<key>/ links and bare permalink keys, returning the dashboard identifier together with the permalink's active-tab and filter state. This extends the existing tools rather than adding a resolver round trip: their response models already carry dashboard metadata and state, so transparent resolution keeps the workflow discoverable without losing context.

Invalid or expired permalinks return an actionable error asking for a fresh shared link. Numeric IDs, UUIDs, and slugs retain their existing lookup path; ambiguous bare strings are attempted as permalinks only after ordinary identifier lookup fails.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Not applicable; this changes MCP tool behavior and schemas only.

TESTING INSTRUCTIONS

ruff format superset/mcp_service/dashboard/schemas.py superset/mcp_service/dashboard/permalink.py superset/mcp_service/dashboard/tool/get_dashboard_info.py superset/mcp_service/dashboard/tool/get_dashboard_layout.py tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py
ruff check superset/mcp_service/dashboard/schemas.py superset/mcp_service/dashboard/permalink.py superset/mcp_service/dashboard/tool/get_dashboard_info.py superset/mcp_service/dashboard/tool/get_dashboard_layout.py tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py
pytest -q tests/unit_tests/mcp_service/dashboard/
pre-commit run --files superset/mcp_service/app.py superset/mcp_service/dashboard/schemas.py superset/mcp_service/dashboard/permalink.py superset/mcp_service/dashboard/tool/get_dashboard_info.py superset/mcp_service/dashboard/tool/get_dashboard_layout.py tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py

The dashboard MCP unit suite passes (362 tests). Changed-file pre-commit passes. The repository-wide pre-commit run was also attempted; unrelated existing type/lint failures and missing frontend dependencies prevent that full-tree gate from completing.

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

Blast radius

Apache Superset's optional MCP service only. No migrations, feature flags, authentication changes, workspace isolation changes, or database query behavior changes.

Risk and rollback

The main risk is interpreting an unknown slug-like identifier as a permalink after normal lookup fails. Existing identifiers are resolved first, and reverting this commit restores the previous behavior.

Eval evidence

The deployment-backed agent eval suite was not run for this draft because no deployed build is available. The deterministic MCP unit coverage exercises permalink resolution, active-tab/filter context, invalid links, and existing identifier forms.

Cost and latency delta

No model, prompt-routing, or token changes. A bare permalink key can add one permalink lookup after an unsuccessful ordinary identifier lookup; explicit permalink inputs and shared URLs resolve directly. Deployment-backed latency measurements were not available for this draft.

Prompt / non-determinism

Tool descriptions were updated to identify /dashboard/p/<key>/ links and direct agents to the lookup tools. The resolution behavior is deterministic; no model prompt or routing behavior changed.

Review guidance

Start with dashboard/permalink.py and the request/response schema changes, then review how each lookup tool reuses the resolved dashboard ID and safely exposes permalink state. The most important behavior is the ordinary-identifier-first fallback for bare strings.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 31.78295% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.79%. Comparing base (22396d5) to head (2ada7dc).
⚠️ Report is 23 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/dashboard/permalink.py 33.70% 59 Missing ⚠️
...t/mcp_service/dashboard/tool/get_dashboard_info.py 14.28% 12 Missing ⚠️
...mcp_service/dashboard/tool/get_dashboard_layout.py 8.33% 11 Missing ⚠️
superset/mcp_service/dashboard/schemas.py 57.14% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42659      +/-   ##
==========================================
- Coverage   66.83%   66.79%   -0.05%     
==========================================
  Files        2876     2877       +1     
  Lines      164061   164310     +249     
  Branches    37860    37922      +62     
==========================================
+ Hits       109653   109748      +95     
- Misses      52233    52377     +144     
- Partials     2175     2185      +10     
Flag Coverage Δ
hive 38.09% <31.78%> (-0.01%) ⬇️
mysql 57.77% <31.78%> (-0.06%) ⬇️
postgres 57.80% <31.78%> (-0.06%) ⬇️
presto 40.03% <31.78%> (-0.02%) ⬇️
python 59.23% <31.78%> (-0.06%) ⬇️
sqlite 57.49% <31.78%> (-0.06%) ⬇️
unit 100.00% <ø> (ø)

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.

@aminghadersohi
aminghadersohi marked this pull request as ready for review August 4, 2026 19:36
@dosubot dosubot Bot added api Related to the REST API dashboard Namespace | Anything related to the Dashboard labels Aug 4, 2026
@github-actions github-actions Bot removed the api Related to the REST API label Aug 4, 2026
Comment thread superset/mcp_service/dashboard/tool/get_dashboard_info.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. In the current implementation of _lookup_dashboard in superset/mcp_service/dashboard/tool/get_dashboard_info.py, if both identifier and permalink_key are provided, the code prioritizes the permalink_value to determine the lookup_identifier, effectively ignoring the explicit identifier provided by the user. This can lead to a mismatch if the two refer to different dashboards.

To resolve this, you should validate that the dashboardId resolved from the permalink_value matches the provided identifier (if both are present), or explicitly decide which one takes precedence based on your business logic. Here is a concise fix for the _lookup_dashboard function:

    # ... inside _lookup_dashboard ...
    lookup_identifier = (
        permalink_value.get("dashboardId")
        if permalink_value is not None
        else request.identifier
    )
    
    # Validate identifier match if both are provided
    if permalink_value and request.identifier and str(lookup_identifier) != str(request.identifier):
        # Handle mismatch: raise error or prioritize one
        return (
            DashboardError.create("Identifier and permalink refer to different dashboards.", "mismatch"),
            permalink_key,
            None,
        )

    result = tool.run_tool(lookup_identifier)  # type: ignore[arg-type]
    # ...

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

superset/mcp_service/dashboard/tool/get_dashboard_info.py

lookup_identifier = (
        permalink_value.get("dashboardId")
        if permalink_value is not None
        else request.identifier
    )
    
    # Validate identifier match if both are provided
    if permalink_value and request.identifier and str(lookup_identifier) != str(request.identifier):
        return (
            DashboardError.create("Identifier and permalink refer to different dashboards.", "mismatch"),
            permalink_key,
            None,
        )

    result = tool.run_tool(lookup_identifier)  # type: ignore[arg-type]

@pull-request-size pull-request-size Bot added size/XL and removed size/L labels Aug 4, 2026
@aminghadersohi
aminghadersohi marked this pull request as draft August 4, 2026 19:49
@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

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

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Addressed the identifier/permalink precedence feedback in 067dfaa45b: explicit identifier values remain the lookup target, while permalink state is applied only when its dashboard ID matches. The shared resolver is used by both dashboard lookup tools, with mismatch regression tests for each.

@aminghadersohi
aminghadersohi marked this pull request as ready for review August 5, 2026 00:01
@dosubot dosubot Bot added api Related to the REST API dashboard:properties Related to the properties of the Dashboard labels Aug 5, 2026
@github-actions github-actions Bot removed the api Related to the REST API label Aug 5, 2026
@aminghadersohi
aminghadersohi force-pushed the aminghadersohi/resolve-dashboard-permalinks branch from 8a8e109 to a721e5e Compare August 5, 2026 01:00
@aminghadersohi
aminghadersohi marked this pull request as draft August 5, 2026 01:00
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

CI follow-up: I rebased this branch onto current master and triggered a fresh run. The two Last hour failures in test_time_range_validation.py are not introduced by this PR: the same assertions fail on the current master run (30962350241), and this PR does not modify that validator or its tests. The earlier Playwright/Cypress jobs were cancelled before test execution; the replacement E2E jobs on the prior head completed successfully. The fresh head is being validated now.

@aminghadersohi
aminghadersohi force-pushed the aminghadersohi/resolve-dashboard-permalinks branch from 786fef6 to 52453c3 Compare August 5, 2026 04:51
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Fixed the remaining Python CI failure in 52453c3802. The failing upstream test asserted clock-dependent parser behavior: Last hour only raised at certain times of day. Freezing the test clock at the boundary it is explicitly exercising makes all parametrized cases deterministic without changing production behavior.

@aminghadersohi
aminghadersohi marked this pull request as ready for review August 5, 2026 07:19
@bito-code-review

bito-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #0bb033

Actionable Suggestions - 0
Additional Suggestions - 4
  • superset/mcp_service/dashboard/schemas.py - 1
    • Missing docstring for new feature · Line 317-318
      Add docstring explaining the new `permalink_key` field in `GetDashboardLayoutRequest`. The class docstring should describe that it supports permalink keys to resolve dashboard filter state, similar to how `GetDashboardInfoRequest` documents this feature.
  • superset/mcp_service/dashboard/permalink.py - 1
    • Move import to module-level · Line 79-79
      Move this import to module-level. BITO.md rule [12745] requires all imports at module-level unless a circular dependency exists and is documented. No circular dependency here.
  • tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py - 1
    • Incomplete precedence verification · Line 637-639
      This test verifies that identifier takes precedence over permalink_key for the dashboard ID, but doesn't verify that permalink filter state was correctly excluded from the response. Other permalink tests (e.g., line 566) assert on filter_state presence.
  • superset/mcp_service/dashboard/tool/get_dashboard_layout.py - 1
    • Duplicate permalink logic · Line 97-146
      The permalink-handling block (lines 97–146) replicates the logic from `get_dashboard_info.py` verbatim: identical `int()` extraction, `user_can_view_data_model_metadata()` check, and `sanitize_for_llm_context()` call. Maintaining two parallel implementations creates divergence risk if one path is updated without the other.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py - 1
Review Details
  • Files reviewed - 9 · Commit Range: 1683804..6b9ed48
    • superset/mcp_service/app.py
    • superset/mcp_service/dashboard/permalink.py
    • superset/mcp_service/dashboard/schemas.py
    • superset/mcp_service/dashboard/tool/get_dashboard_info.py
    • superset/mcp_service/dashboard/tool/get_dashboard_layout.py
    • tests/unit_tests/mcp_service/common/test_time_range_validation.py
    • tests/unit_tests/mcp_service/dashboard/test_permalink.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • 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 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

@aminghadersohi
aminghadersohi force-pushed the aminghadersohi/resolve-dashboard-permalinks branch from 6b9ed48 to e038e93 Compare August 6, 2026 06:41
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Addressed all four suggestions from the latest Bito review in e038e93775:

  • expanded the layout request docstring to explain permalink tab/filter context;
  • moved GetDashboardPermalinkCommand to a module-level import;
  • added an explicit regression assertion that mismatched permalink filter state is excluded;
  • centralized dashboard-ID matching, metadata redaction, and LLM sanitization in get_matching_dashboard_permalink_state, shared by both dashboard tools.

The affected MCP tests pass (52 passed), along with Ruff and targeted mypy.

@aminghadersohi
aminghadersohi marked this pull request as draft August 6, 2026 06:42
@aminghadersohi
aminghadersohi marked this pull request as ready for review August 12, 2026 04:29
@github-actions github-actions Bot removed the api Related to the REST API label Aug 13, 2026
@bito-code-review

bito-code-review Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #a2c76b

Actionable Suggestions - 0
Additional Suggestions - 3
  • superset/mcp_service/dashboard/permalink.py - 2
    • Missing unit tests for critical permalink logic · Line 103-154
      The `lookup_dashboard_reference` function (lines 103-154) handles critical permalink resolution logic but has zero test coverage. The complex branching at lines 125-135 (identifier found) and 139-152 (permalink fallback) includes conditional calls to `get_dashboard_permalink()` and `lookup(value['dashboardId'])` that are not exercised. Per BITO.md adaptive rule [11730], new code should cover success paths, error scenarios, validation failures, and edge cases — especially when downstream callers (`get_dashboard_info`, `get_dashboard_layout`) depend on the contract.
    • Missing unit tests for privacy-sensitive function · Line 155-182
      The `get_matching_dashboard_permalink_state` function (lines 155-182) has zero test coverage despite containing privacy-sensitive redaction logic via `redact_filter_state_data_model_metadata` and multiple error-handling branches. The privacy decision (line 173) and the try/except for ID extraction (lines 164-167) are entirely untested.
  • tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py - 1
    • Test error message mismatch · Line 964-966
      The `test_get_dashboard_info_requires_reference` test (line 964) expects error message matching "identifier or permalink_key", but `GetDashboardInfoRequest._require_identifier_or_permalink()` at schema line 313 raises `ValueError("Provide identifier or permalink_key")`. The test passes due to regex substring matching, but the mismatch creates maintenance risk if error messages are refactored.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/mcp_service/dashboard/schemas.py - 1
Review Details
  • Files reviewed - 9 · Commit Range: c8441f1..f8d92e5
    • superset/mcp_service/app.py
    • superset/mcp_service/dashboard/permalink.py
    • superset/mcp_service/dashboard/schemas.py
    • superset/mcp_service/dashboard/tool/get_dashboard_info.py
    • superset/mcp_service/dashboard/tool/get_dashboard_layout.py
    • tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py
    • tests/unit_tests/mcp_service/dashboard/test_permalink.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • 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 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

@aminghadersohi
aminghadersohi requested review from gabotorresruiz and removed request for richardfogaca August 14, 2026 16:53

@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 Amin, transparent permalink resolution in the lookup tools is the right shape for this, and the identifier-precedence contract you settled with the earlier bot comment reads well. I found one blocking issue though: the dashboardId match doesn't survive contact with real permalinks, so the shared state gets dropped in practice. Details inline, happy to dig in with you.

Comment thread superset/mcp_service/dashboard/permalink.py Outdated
Comment thread superset/mcp_service/dashboard/tool/get_dashboard_info.py Outdated
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Pushed 8a01ea6fdc addressing @gabotorresruiz's two review comments — details in the inline replies:

  1. Permalink dashboardId is a UUID string, not an int. The int(...) comparison dropped shared state for every permalink created since 3.1 and warned "belongs to a different dashboard" for the same dashboard. The permalink-resolved path no longer re-verifies at all (DashboardLookupResult.resolved_from_permalink), and the explicit identifier + permalink_key combination compares the reference against the dashboard's id, uuid, and slug. Covered by new UUID-dashboardId tests for both get_dashboard_info and get_dashboard_layout, a legacy-slug test, and parametrized helper tests in test_permalink.py.

  2. Identifier typos no longer report the permalink message. The original not-found error is preserved whenever the lookup produced one; the "ask for a fresh shared dashboard link" wording is reserved for requests that had to resolve through a permalink. {"identifier": "sales-dashbord"} again returns DashboardInfo with identifier 'sales-dashbord' not found, asserted for both tools.

On the remaining bot suggestion (mock_permalink.assert_not_called() in test_get_dashboard_layout_identifier_takes_precedence_over_permalink): that one is incorrect and was not applied. When identifier=10 and permalink_key are both supplied, lookup_dashboard_reference deliberately does resolve the permalink — the identifier picks the dashboard, and the permalink still contributes state when it points at that same dashboard. The assertion would fail. The precedence contract is already asserted by mock_find.assert_called_once_with(10, query_options=None) and is_permalink_state is False.

pytest tests/unit_tests/mcp_service/dashboard/ — 393 passed.

@aminghadersohi
aminghadersohi force-pushed the aminghadersohi/resolve-dashboard-permalinks branch from b36d56e to b6cb9c9 Compare August 20, 2026 17:36
@bito-code-review

bito-code-review Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #973bad

Actionable Suggestions - 0
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/mcp_service/dashboard/tool/get_dashboard_info.py - 1
    • Inconsistent function signature across callers · Line 79-79
  • superset/mcp_service/dashboard/tool/get_dashboard_layout.py - 1
    • Wrong error type for non-permalink failures · Line 104-111
Review Details
  • Files reviewed - 6 · Commit Range: f8d92e5..b6cb9c9
    • superset/mcp_service/dashboard/permalink.py
    • superset/mcp_service/dashboard/tool/get_dashboard_info.py
    • superset/mcp_service/dashboard/tool/get_dashboard_layout.py
    • tests/unit_tests/mcp_service/dashboard/test_permalink.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • 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 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

@gabotorresruiz

Copy link
Copy Markdown
Contributor

Hey Amin, I went to re-review and hit a surprise: 8a01ea6fdc looks great but it never made it onto the PR branch. The current head b36d56e is only a master merge on top of e076608; the PR's own files are unchanged, 8a01ea6fdc is not in the PR's commit list, and on GitHub it is not the head of any branch in your fork. I re-ran my live check against a build of the current head and the shared state is still dropped, while the same harness with 8a01ea6fdc applied on top passes everything: permalink-only, identifier plus permalink_key, shared URL as identifier, and the typo error message all behave exactly as your replies describe, and 11 of the 13 new tests fail without the source fix, so they pin it properly. It also applies cleanly onto the current head. Looks like the push went missing; once it is on the branch and CI is green I am happy to approve.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Hey Amin, I went to re-review and hit a surprise: 8a01ea6fdc looks great but it never made it onto the PR branch. The current head b36d56e is only a master merge on top of e076608; the PR's own files are unchanged, 8a01ea6fdc is not in the PR's commit list, and on GitHub it is not the head of any branch in your fork. I re-ran my live check against a build of the current head and the shared state is still dropped, while the same harness with 8a01ea6fdc applied on top passes everything: permalink-only, identifier plus permalink_key, shared URL as identifier, and the typo error message all behave exactly as your replies describe, and 11 of the 13 new tests fail without the source fix, so they pin it properly. It also applies cleanly onto the current head. Looks like the push went missing; once it is on the branch and CI is green I am happy to approve.

Hi Gabriel, thanks for the review. not sure what happened but fixing now.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Thanks for the careful re-check — and good news: the commit is on the branch, it just has a new SHA.

I rebased onto master, which rewrote 8a01ea6fdc into b6cb9c91bc (same author, same subject, same content) and dropped the old merge-based history including b36d56e. That is exactly why your checks came back the way they did: 8a01ea6fdc is not in the commit list and is not the head of any branch in my fork, because the rebase gave that work a different SHA.

From the PR timeline:

  • head_ref_force_pushed -> b6cb9c91bc at 2026-08-20T17:36:06Z
  • master merge 277855578a at 2026-08-20T21:21:53Z

Current head is 277855578a. Ancestry on that head:

  • b6cb9c91bc — ancestor (the fix)
  • b36d56e — not an ancestor (force-pushed away)
  • 8a01ea6fdc — not an ancestor (rewritten by the rebase)

I diffed the current PR head against my local branch across all eight files in the PR diff and they are byte-identical:

superset/mcp_service/dashboard/permalink.py
superset/mcp_service/dashboard/schemas.py
superset/mcp_service/dashboard/tool/get_dashboard_info.py
superset/mcp_service/dashboard/tool/get_dashboard_layout.py
tests/unit_tests/mcp_service/dashboard/test_permalink.py
tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py
tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py
tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py

So the state your harness validated with 8a01ea6fdc applied on top is the state already on the branch. Re-running against 277855578a should show all four scenarios passing and the 13 new tests green.

On CI: 45 checks pass, one fails — docker-build (lean), for two unrelated environmental reasons:

  • ##[error]Username and password required — Docker Hub login, secrets are not exposed to fork PRs
  • E: Failed to fetch .../chrome-stable/... File has unexpected size (1415 != 1416). Mirror sync in progress? — transient apt mirror

docker-build (dev) passed, as did unit-tests, test-postgres, both Playwright matrices, Cypress, and the license check. Nothing in that failure touches the Python changes here.

Sorry for the confusion the force-push caused — happy to re-run anything you want against the current head.

aminghadersohi and others added 5 commits August 21, 2026 03:23
CreateDashboardPermalinkCommand stores dashboardId as str(dashboard.uuid),
so the int() comparison dropped shared state for virtually every real
permalink. The permalink-only path now records that it selected the
dashboard itself and skips re-verification, while the explicit
identifier + permalink_key combination compares the reference against the
dashboard id, uuid, and slug.

A plain identifier that simply does not exist also no longer reports the
permalink wording; its own not-found error is preserved and the permalink
message is reserved for requests that had to resolve through a permalink.

Co-Authored-By: Claude <noreply@anthropic.com>
@aminghadersohi
aminghadersohi force-pushed the aminghadersohi/resolve-dashboard-permalinks branch from 2778555 to daf1838 Compare August 21, 2026 03:49
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Correction and update to my previous comment — that comment was accurate about the SHA, but incomplete, and I should flag what I missed.

What I got right: the fix was on the branch; the rebase had rewritten 8a01ea6fdc into a new SHA.

What I missed: the PR was in a CONFLICTING / DIRTY merge state against master. #43202 ("preserve user-authored result values") landed on the same MCP dashboard files after this branch was last synced, and it did more than collide textually:

  1. It removed sanitize_for_llm_context from the MCP service entirely. This branch still called it in permalink.py, so on current master the module fails to import outright — ImportError: cannot import name 'sanitize_for_llm_context'. A naive merge would have shipped a broken import.
  2. It rewrote _apply_permalink_state from model_dump/model_validate to model_copy(update=...), specifically so results are not re-validated. Resolving that conflict in this branch's favour would have silently reverted that fix.

I have rebased onto 22396d504a and reconciled both rather than taking either side wholesale:

  • Dropped the sanitize_for_llm_context call. Sanitization is no longer the tool layer's job per #43202. The privacy redaction (user_can_view_data_model_metadata / redact_filter_state_data_model_metadata) is kept — that is access control, not value rewriting, and is unaffected.
  • Kept master's model_copy form of _apply_permalink_state.
  • Removed two imports in test_dashboard_tools.py that #43202 made dead (LLM_CONTEXT_* delimiters, now that _wrapped is identity).
  • Master had five refresh_request_user_for_permalink_access tests in test_dashboard_tools.py; this branch moved that helper to permalink.py. Four are covered by the parametrized tests in test_permalink.py, but keeps_user_when_reload_fails (reload returns None, user preserved — permalink.py:85-86) was not. I ported it so no coverage is lost in the move.

New head is daf1838f93, five linear commits on master, fixups squashed into their originating commits so the history stays bisectable. GitHub now reports MERGEABLE.

Verification: 399 dashboard MCP tests pass; full tests/unit_tests/mcp_service/ is 3528 passed / 1 failed, and that one failure (test_mcp_e2e_smoke.py::test_tools_call_health_check_over_real_asgi_transport) reproduces identically on a clean origin/master worktree, so it is pre-existing and not from this branch. pre-commit passes on all nine changed files (mypy, ruff, pylint included).

Worth re-running your harness against daf1838f93 rather than the old SHA — the source changed materially in the reconciliation, so the previous build is not representative.

@bito-code-review bito-code-review Bot 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.

Code Review Agent Run #c9ea69

Actionable Suggestions - 1
  • superset/mcp_service/dashboard/permalink.py - 1
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • tests/unit_tests/mcp_service/dashboard/test_permalink.py - 1
    • CWE-483: Mock target path incorrect · Line 85-85
Review Details
  • Files reviewed - 9 · Commit Range: 0a77b35..daf1838
    • superset/mcp_service/app.py
    • superset/mcp_service/dashboard/permalink.py
    • superset/mcp_service/dashboard/schemas.py
    • superset/mcp_service/dashboard/tool/get_dashboard_info.py
    • superset/mcp_service/dashboard/tool/get_dashboard_layout.py
    • tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py
    • tests/unit_tests/mcp_service/dashboard/test_permalink.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py
    • tests/unit_tests/mcp_service/dashboard/tool/test_get_dashboard_layout.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • 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 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

Comment on lines +103 to +149
def lookup_dashboard_reference(
*,
identifier: int | str | None,
permalink_key: str | None,
lookup: Callable[[int | str], LookupResultT],
is_found: Callable[[LookupResultT], bool],
) -> DashboardLookupResult[LookupResultT]:
"""Look up a dashboard while preserving identifier precedence.

A supplied identifier selects the dashboard and an explicit permalink only
contributes state. Shared permalink URLs and permalink-only requests select
the dashboard embedded in the permalink. Ambiguous bare strings use normal
identifier lookup first, then fall back to permalink resolution.
"""
key = permalink_key
identifier_is_permalink_url = False
if isinstance(identifier, str):
extracted_key = extract_dashboard_permalink_key(identifier)
identifier_is_permalink_url = extracted_key != identifier
if identifier_is_permalink_url:
key = extracted_key

if identifier is not None and not identifier_is_permalink_url:
result = lookup(identifier)
if is_found(result):
resolved = get_dashboard_permalink(key) if key else None
return DashboardLookupResult(
result=result,
permalink_key=resolved[0] if resolved else key,
permalink_value=resolved[1] if resolved else None,
)
if permalink_key is not None or not isinstance(identifier, str):
return DashboardLookupResult(result=result, permalink_key=key)
else:
result = None

reference = key or (identifier if isinstance(identifier, str) else None)
resolved = get_dashboard_permalink(reference) if reference else None
if resolved is None:
return DashboardLookupResult(result=result, permalink_key=reference)
key, value = resolved
return DashboardLookupResult(
result=lookup(value["dashboardId"]),
permalink_key=key,
permalink_value=value,
resolved_from_permalink=True,
)

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.

Untested public function

The lookup_dashboard_reference function handles four distinct code paths with complex branching logic (identifier type checks, permalink URL detection, fallback resolution). It has zero test coverage despite being a public utility used by dashboard tools. Missing tests increase the risk of undetected regressions as the MCP service evolves.

Code Review Run #c9ea69


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

  • Yes, avoid them

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair catch — confirmed. lookup_dashboard_reference was exercised indirectly through the get_dashboard_info / get_dashboard_layout tool tests, but it had no direct tests, and its branching is the part of this PR most likely to regress quietly.

Added nine direct tests in tests/unit_tests/mcp_service/dashboard/test_permalink.py, one per resolution path:

  • identifier only, no permalink involved
  • identifier wins, permalink contributes state only (resolved_from_permalink is False)
  • dashboard kept when the permalink is expired/unresolvable
  • numeric identifier not found — never falls back to permalink resolution
  • string identifier not found with an explicit permalink_key — keeps the identifier's own not-found result
  • shared /dashboard/p/<key>/ URL passed as identifier
  • permalink-only request
  • ambiguous bare string, identifier lookup first then permalink fallback
  • fully unresolvable reference

To check these actually pin behaviour rather than just passing, I ran three mutations against the source and confirmed each is caught:

Mutation Result
Remove the identifier-precedence guard test_..._identifier_not_found_with_explicit_permalink fails
Force resolved_from_permalink=False 3 tests fail
Ignore permalink-URL extraction in identifier test_..._shared_url_identifier fails

get_dashboard_permalink is patched at the module boundary so these stay unit tests with no DB or request context. Full dashboard suite is 408 passed; pre-commit (mypy, ruff, pylint) clean.

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.

The addition of nine direct unit tests in tests/unit_tests/mcp_service/dashboard/test_permalink.py effectively addresses the concern regarding the lack of test coverage for lookup_dashboard_reference. By covering each resolution path and verifying behavior through mutation testing, the implementation is now robustly validated against regressions.

@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.

LGTM

Add direct unit tests for the identifier/permalink resolution branches:
identifier-only, identifier plus permalink state, unresolvable permalink,
numeric and string not-found precedence, shared URL identifiers,
permalink-only requests, bare-string fallback, and fully unresolvable
references.
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

@gabotorresruiz thanks for the approval, and for the two catches that got it here — both were real and both were reproducible exactly as you described.

Recapping how each landed, since the branch was rebased after your first review and the line numbers have moved:

1. UUID dashboardId (permalink.py) — you were right that CreateDashboardPermalinkCommand stores str(dashboard.uuid), so the old int(...) comparison failed for essentially every real permalink. Implemented both options you offered rather than one: get_matching_dashboard_permalink_state now compares the reference against id, uuid and slug, and DashboardLookupResult.resolved_from_permalink marks the permalink-only path so it skips re-verification entirely. Slug is included so pre-3.1 permalinks keep working. Tests use a real UUID-string dashboardId and assert filter_state is present for both tools — test_dashboard_tools.py:652 and test_get_dashboard_layout.py:355 — plus a slug case at test_dashboard_tools.py:679.

2. Swallowed not-found error (get_dashboard_info.py) — also confirmed. Both tools now keep the identifier's own error whenever result is not None and reserve the permalink wording for permalink-only requests (get_dashboard_layout.py:104 has the matching guard you flagged). Locked in by test_get_dashboard_info_unknown_slug_keeps_not_found_error and test_get_dashboard_layout_unknown_slug_keeps_not_found_error, both asserting the typo string appears in the message.

Heads-up: I pushed one commit after your approval. 2ada7dc8a3 is tests-only — it adds direct coverage for lookup_dashboard_reference, which a bot review correctly pointed out had no direct tests despite four branching paths. No source files changed in that commit. GitHub kept the approval, but flagging it explicitly so it is your call rather than mine — happy to revert it into a follow-up PR if you would rather approve exactly what you reviewed.

Also worth noting for the record: this branch was rebased onto master after #43202 landed, which removed sanitize_for_llm_context and rewrote _apply_permalink_state. Reconciled both rather than taking either side — details in the comment above.

@bito-code-review

bito-code-review Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #62d793

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: daf1838..2ada7dc
    • tests/unit_tests/mcp_service/dashboard/test_permalink.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • 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

@aminghadersohi aminghadersohi closed this by deleting the head repository Aug 23, 2026
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Superseded by #43482.

This PR could not be reopened: the source fork repository was deleted, and GitHub permanently severs the link in that case (state cannot be changed. The repository that submitted this pull request has been deleted). The fork has since been recreated, but a recreated fork gets a new internal ID, so reopening is not possible — a replacement PR was the only option.

#43482 contains the same work rebased onto current master, including both fixes from @gabotorresruiz's review here (UUID/slug dashboardId matching, and preserving the identifier's own not-found error) and the added lookup_dashboard_reference coverage. The review history from this thread is summarized in the new PR's description so the context is not lost.

@gabotorresruiz apologies for the churn — this needs a fresh approval on #43482 since the original one could not carry over.

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

Labels

dashboard:properties Related to the properties of the Dashboard dashboard Namespace | Anything related to the Dashboard size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants