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


##########
superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py:
##########
@@ -435,25 +435,60 @@ def add_chart_to_existing_dashboard(
 
         # Re-fetch the dashboard with eager-loaded relationships to avoid
         # "Instance is not bound to a Session" errors when serializing
-        # chart .tags and .owners.
+        # chart .tags and .owners.  The preceding command.run() commit may
+        # invalidate the session in multi-tenant environments; on failure,
+        # return a minimal response using only scalar attributes that are
+        # already loaded — relationship fields (owners, tags, slices) would
+        # trigger lazy-loading on the same dead session.
         from sqlalchemy.orm import subqueryload
 
-        from superset.daos.dashboard import DashboardDAO
         from superset.models.dashboard import Dashboard
         from superset.models.slice import Slice
 
-        updated_dashboard = (
-            DashboardDAO.find_by_id(
+        try:
+            updated_dashboard = (
+                DashboardDAO.find_by_id(
+                    updated_dashboard.id,
+                    query_options=[
+                        
subqueryload(Dashboard.slices).subqueryload(Slice.owners),
+                        
subqueryload(Dashboard.slices).subqueryload(Slice.tags),
+                        subqueryload(Dashboard.owners),
+                        subqueryload(Dashboard.tags),
+                    ],
+                )
+                or updated_dashboard
+            )
+        except SQLAlchemyError:
+            logger.warning(
+                "Re-fetch of dashboard %s failed; returning minimal response",
                 updated_dashboard.id,
-                query_options=[
-                    subqueryload(Dashboard.slices).subqueryload(Slice.owners),
-                    subqueryload(Dashboard.slices).subqueryload(Slice.tags),
-                    subqueryload(Dashboard.owners),
-                    subqueryload(Dashboard.tags),
-                ],
+                exc_info=True,
+            )
+            try:
+                db.session.rollback()  # pylint: 
disable=consider-using-transaction
+            except SQLAlchemyError:
+                logger.warning(
+                    "Database rollback failed during dashboard re-fetch error 
handling",
+                    exc_info=True,
+                )
+            dashboard_url = (
+                
f"{get_superset_base_url()}/superset/dashboard/{updated_dashboard.id}/"
+            )
+            position_info = {
+                "row": row_key,
+                "chart_key": chart_key,
+                "row_key": row_key,
+            }
+            return AddChartToDashboardResponse(
+                dashboard=DashboardInfo(
+                    id=updated_dashboard.id,
+                    dashboard_title=updated_dashboard.dashboard_title,
+                    url=dashboard_url,
+                ),

Review Comment:
   **Suggestion:** In the fallback path where the dashboard re-fetch fails, the 
minimal `DashboardInfo` response omits `chart_count` (and `published`), so 
clients will see a chart_count of 0 even though a chart was successfully added; 
this is a logic error and can be fixed by populating `chart_count` from the 
already-materialized `all_chart_objects` list and including `published` from 
the existing `updated_dashboard` object, both of which are safe scalar data and 
consistent with the main success path. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ MCP dashboard clients misreport chart counts after re-fetch failures.
   - ⚠️ Clients relying on published flag see null despite published.
   ```
   </details>
   
   ```suggestion
                       chart_count=len(all_chart_objects),
                       published=updated_dashboard.published,
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. From an MCP client, invoke the `add_chart_to_existing_dashboard` tool 
(entrypoint
   `add_chart_to_existing_dashboard` in
   
`superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py:327-588`)
 with a
   valid `dashboard_id` and `chart_id` so that validation succeeds and the code 
reaches the
   update block and then the re-fetch block around lines 430–491 (see re-fetch 
try/except in
   `add_chart_to_existing_dashboard.py` read via tools).
   
   2. Let `UpdateDashboardCommand.run()` complete successfully in the
   `mcp.add_chart_to_dashboard.db_write` context so that `updated_dashboard` is 
a committed
   ORM instance and `all_chart_objects = list(dashboard.slices) + [new_chart]` 
has been
   constructed in-memory just before the re-fetch (lines just above the 
re-fetch block in
   `add_chart_to_existing_dashboard.py`).
   
   3. Cause the re-fetch to fail so that the `except SQLAlchemyError:` path 
executes at
   
`superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py:32-42` 
of the
   snippet we inspected: for example, in a unit test, patch 
`DashboardDAO.find_by_id` (used
   in the re-fetch at lines 19–29 of the same block) to raise 
`SQLAlchemyError`, or in a real
   environment, reproduce a PostgreSQL SSL connection drop so that the session 
is poisoned
   and the re-fetch query fails.
   
   4. Observe the returned `AddChartToDashboardResponse` (schema in
   `superset/mcp_service/dashboard/schemas.py:410-423`): its `dashboard` field 
is a
   `DashboardInfo` constructed by the minimal path at lines 482–491, which only 
sets `id`,
   `dashboard_title`, and `url`. Because `DashboardInfo.chart_count` defaults 
to `0` and
   `published` defaults to `None` (`schemas.py:290-310`), the client will see
   `dashboard.chart_count == 0` and `dashboard.published is None` even though a 
chart was
   actually added and `updated_dashboard.published` is a valid boolean. In the 
normal success
   path (eager-load succeeds), `dashboard_info` sets
   `chart_count=len(updated_dashboard.slices)` and 
`published=updated_dashboard.published`
   (lines 70–77, 509–511), demonstrating that the fallback path is inconsistent 
and returns
   incorrect metadata.
   ```
   </details>
   <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/add_chart_to_existing_dashboard.py
   **Line:** 487:487
   **Comment:**
        *Logic Error: In the fallback path where the dashboard re-fetch fails, 
the minimal `DashboardInfo` response omits `chart_count` (and `published`), so 
clients will see a chart_count of 0 even though a chart was successfully added; 
this is a logic error and can be fixed by populating `chart_count` from the 
already-materialized `all_chart_objects` list and including `published` from 
the existing `updated_dashboard` object, both of which are safe scalar data and 
consistent with the main success path.
   
   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.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38934&comment_hash=9abcd1cd451895e563ce246815e8da6866736fa3e27689c6e3e398a2ae90b477&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38934&comment_hash=9abcd1cd451895e563ce246815e8da6866736fa3e27689c6e3e398a2ae90b477&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