codeant-ai-for-open-source[bot] commented on code in PR #40959:
URL: https://github.com/apache/superset/pull/40959#discussion_r3482569042


##########
superset/mcp_service/dashboard/tool/duplicate_dashboard.py:
##########
@@ -0,0 +1,393 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""
+MCP tool: duplicate_dashboard
+
+Duplicates an existing dashboard, optionally deep-copying its charts.
+Canonical workflow: clone a template dashboard, then edit the copy
+(e.g. to create a regional or staging variant).
+"""
+
+import logging
+from typing import Any
+
+from fastmcp import Context
+from sqlalchemy.exc import SQLAlchemyError
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.dashboard.schemas import (
+    _sanitize_dashboard_info_for_llm_context,
+    DashboardInfo,
+    DuplicateDashboardRequest,
+    DuplicateDashboardResponse,
+    serialize_chart_summary,
+)
+from superset.mcp_service.privacy import user_can_view_data_model_metadata
+from superset.mcp_service.utils.url_utils import get_superset_base_url
+from superset.utils import json
+
+logger = logging.getLogger(__name__)
+
+
+def _positions_reference_charts(positions: dict[str, Any]) -> bool:
+    """Return whether a layout maps any chart into the dashboard.
+
+    ``DashboardDAO.set_dash_metadata`` rebuilds the new dashboard's slice
+    list solely from the chart IDs found in ``positions``, so a layout
+    with no ``CHART`` entries yields an empty dashboard regardless of the
+    source's ``slices`` relationship.
+    """
+    return any(
+        isinstance(value, dict)
+        and value.get("type") == "CHART"
+        and value.get("meta", {}).get("chartId")
+        for value in positions.values()
+    )

Review Comment:
   **Suggestion:** The layout guard only checks whether any `CHART` node has a 
truthy `chartId`, but it does not verify that those IDs actually resolve to 
real charts on the source dashboard. If `position_json` contains 
stale/mismatched chart IDs, the guard passes and `set_dash_metadata` later 
rebuilds slices from non-existent IDs, yielding an empty copy despite source 
charts existing. Validate chart IDs against `source.slices` (or at least 
DB-resolved IDs) before allowing duplication. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ MCP duplicate_dashboard may return empty dashboards unexpectedly.
   - ⚠️ Users lose charts in copies when layout IDs go stale.
   - ⚠️ AI-assisted cloning workflow can silently produce broken dashboards.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Create a dashboard whose `slices` relationship still contains charts 
(queried by
   `DashboardDAO.copy_dashboard` at `superset/daos/dashboard.py:5-21`), but 
whose persisted
   `position_json` only references chart IDs that do not correspond to any real 
`Slice` rows
   (e.g. manually edit `dashboards.position_json` to include `{"type": "CHART", 
"meta":
   {"chartId": 999999}}` where `Slice.id=999999` does not exist).
   
   2. Start the Superset MCP service so that the `duplicate_dashboard` tool from
   `superset/mcp_service/dashboard/tool/__init__.py:18-32` is registered, and 
connect an MCP
   client.
   
   3. Call the `duplicate_dashboard()` MCP tool at
   `superset/mcp_service/dashboard/tool/duplicate_dashboard.py:276-289` with 
`dashboard_id`
   pointing to the corrupted dashboard; inside the tool, 
`_build_copy_payload()` at
   `duplicate_dashboard.py:64-102` loads `position_json` and passes it to
   `_positions_reference_charts()` at `duplicate_dashboard.py:48-61`.
   
   4. `_positions_reference_charts()` returns `True` at lines 56-61 because it 
only checks
   for any `CHART` node with a truthy `meta["chartId"]`, without verifying that 
those IDs map
   to existing slices on the source. The guard at 
`duplicate_dashboard.py:311-324` (`if
   getattr(source, "slices", None) and not layout_has_charts`) is therefore 
bypassed,
   `CopyDashboardCommand(source, data).run()` executes, and
   `DashboardDAO.set_dash_metadata()` at `superset/daos/dashboard.py:5-25` 
computes
   `slice_ids` from the stale `chartId`s and queries `Slice.id.in_(slice_ids)` 
at line 22.
   Because there are no matching `Slice` rows, `dashboard.slices` is set to an 
empty list at
   line 25, yielding a duplicated dashboard with no charts even though the 
source
   `source.slices` was non-empty and the tool did not return an error.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=970466adf4704544a0bd51164f40ccc3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=970466adf4704544a0bd51164f40ccc3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/dashboard/tool/duplicate_dashboard.py
   **Line:** 56:61
   **Comment:**
        *Logic Error: The layout guard only checks whether any `CHART` node has 
a truthy `chartId`, but it does not verify that those IDs actually resolve to 
real charts on the source dashboard. If `position_json` contains 
stale/mismatched chart IDs, the guard passes and `set_dash_metadata` later 
rebuilds slices from non-existent IDs, yielding an empty copy despite source 
charts existing. Validate chart IDs against `source.slices` (or at least 
DB-resolved IDs) before allowing duplication.
   
   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
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40959&comment_hash=20ca186b48b4321173e5003f77781b806b546e57d44b4001de06f52b7d4639f3&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40959&comment_hash=20ca186b48b4321173e5003f77781b806b546e57d44b4001de06f52b7d4639f3&reaction=dislike'>👎</a>



##########
superset/mcp_service/dashboard/tool/duplicate_dashboard.py:
##########
@@ -0,0 +1,393 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""
+MCP tool: duplicate_dashboard
+
+Duplicates an existing dashboard, optionally deep-copying its charts.
+Canonical workflow: clone a template dashboard, then edit the copy
+(e.g. to create a regional or staging variant).
+"""
+
+import logging
+from typing import Any
+
+from fastmcp import Context
+from sqlalchemy.exc import SQLAlchemyError
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.dashboard.schemas import (
+    _sanitize_dashboard_info_for_llm_context,
+    DashboardInfo,
+    DuplicateDashboardRequest,
+    DuplicateDashboardResponse,
+    serialize_chart_summary,
+)
+from superset.mcp_service.privacy import user_can_view_data_model_metadata
+from superset.mcp_service.utils.url_utils import get_superset_base_url
+from superset.utils import json
+
+logger = logging.getLogger(__name__)
+
+
+def _positions_reference_charts(positions: dict[str, Any]) -> bool:
+    """Return whether a layout maps any chart into the dashboard.
+
+    ``DashboardDAO.set_dash_metadata`` rebuilds the new dashboard's slice
+    list solely from the chart IDs found in ``positions``, so a layout
+    with no ``CHART`` entries yields an empty dashboard regardless of the
+    source's ``slices`` relationship.
+    """
+    return any(
+        isinstance(value, dict)
+        and value.get("type") == "CHART"
+        and value.get("meta", {}).get("chartId")
+        for value in positions.values()
+    )
+
+
+def _build_copy_payload(
+    source: Any, dashboard_title: str, duplicate_slices: bool
+) -> tuple[dict[str, Any], bool]:
+    """Build the data payload expected by ``CopyDashboardCommand``.
+
+    Mirrors what the frontend "Save as" flow sends to the
+    ``/api/v1/dashboard/<id>/copy/`` endpoint: the source dashboard's
+    current ``json_metadata`` with a ``positions`` key holding the current
+    layout (``position_json``). ``DashboardCopySchema`` requires
+    ``json_metadata``, and ``DashboardDAO.copy_dashboard`` reads
+    ``positions`` from it to remap chart IDs when ``duplicate_slices``
+    is enabled.
+
+    Returns the payload and a flag indicating whether the layout maps any
+    chart, so the caller can refuse to produce a silently empty copy.
+    """
+    try:
+        metadata = json.loads(source.json_metadata or "{}")
+    except (json.JSONDecodeError, TypeError):
+        metadata = {}
+    if not isinstance(metadata, dict):
+        metadata = {}

Review Comment:
   **Suggestion:** Parsing failures for the source dashboard's `json_metadata` 
are silently converted to `{}`, so malformed persisted metadata produces a 
"successful" duplicate with defaulted/stripped settings instead of surfacing a 
repairable error. This can silently lose filter config, color config, and other 
dashboard behavior. Return a structured duplication error when `json_metadata` 
cannot be decoded (instead of defaulting), matching the tool's stated behavior 
of mirroring the source state. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ MCP duplicate_dashboard can drop all JSON-based settings.
   - ⚠️ Copied dashboards may lose filters and customizations silently.
   - ⚠️ AI workflows see copies behave differently than source dashboards.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Create or identify a dashboard record whose persisted `json_metadata` 
column is
   malformed JSON (e.g. set `dashboards.json_metadata` to `"{invalid"` in the 
database so
   that `source.json_metadata` read at
   `superset/mcp_service/dashboard/tool/duplicate_dashboard.py:81` is not valid 
JSON).
   
   2. Start the Superset MCP service and connect an MCP client that exposes the
   `duplicate_dashboard` tool from 
`superset/mcp_service/dashboard/tool/__init__.py:18-32`.
   
   3. Invoke the MCP tool `duplicate_dashboard()` defined at
   `superset/mcp_service/dashboard/tool/duplicate_dashboard.py:276-289` with 
`dashboard_id`
   pointing to the malformed dashboard and a valid `dashboard_title`.
   
   4. During execution, `_build_copy_payload()` at
   `superset/mcp_service/dashboard/tool/duplicate_dashboard.py:64-102` calls
   `json.loads(source.json_metadata or "{}")` inside the `try` at line 80; the 
malformed JSON
   raises `json.JSONDecodeError`, which is caught at line 82 and replaced with 
`metadata =
   {}` at lines 83-85. The function then builds a payload with
   `json_metadata=json.dumps(metadata)` (line 100), so `CopyDashboardCommand` 
(instantiated
   at line 327 and implemented in `superset/daos/dashboard.py:372-34`) receives 
a sanitized
   `{}` instead of failing. The duplicate dashboard is successfully created but 
loses any
   settings that were present in the original `json_metadata` (filter scopes, 
default
   filters, chart customization config as consumed in 
`superset/daos/dashboard.py:36-76` and
   `superset/views/utils.py:372-27`), contrary to the expectation that invalid 
stored
   metadata would surface as an error as handled in the `(ValueError, 
TypeError)` block at
   `duplicate_dashboard.py:370-387`.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=2626d74076644f1d9ee531e8a1c85a5b&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=2626d74076644f1d9ee531e8a1c85a5b&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/dashboard/tool/duplicate_dashboard.py
   **Line:** 80:85
   **Comment:**
        *Logic Error: Parsing failures for the source dashboard's 
`json_metadata` are silently converted to `{}`, so malformed persisted metadata 
produces a "successful" duplicate with defaulted/stripped settings instead of 
surfacing a repairable error. This can silently lose filter config, color 
config, and other dashboard behavior. Return a structured duplication error 
when `json_metadata` cannot be decoded (instead of defaulting), matching the 
tool's stated behavior of mirroring the source state.
   
   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
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40959&comment_hash=5f29b4444f3b5d5a64369dded2975ee81726238c3ebadcae5fcb27431fa8da9f&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40959&comment_hash=5f29b4444f3b5d5a64369dded2975ee81726238c3ebadcae5fcb27431fa8da9f&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to