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


##########
superset/annotation_layers/schemas.py:
##########
@@ -64,3 +64,24 @@ class AnnotationLayerPutSchema(Schema):
     descr = fields.String(
         metadata={"description": annotation_layer_descr}, required=False
     )
+
+
+class ImportV1AnnotationSchema(Schema):
+    """Schema for validating individual annotations within an exported 
layer."""
+
+    start_dttm = fields.DateTime(allow_none=True)
+    end_dttm = fields.DateTime(allow_none=True)
+    short_descr = fields.String(allow_none=True, validate=Length(0, 500))
+    long_descr = fields.String(allow_none=True)
+    json_metadata = fields.Dict(allow_none=True)
+    uuid = fields.UUID(required=True)
+
+
+class ImportV1AnnotationLayerSchema(Schema):
+    """Schema for importing V1 annotation layers from YAML export files."""
+
+    name = fields.String(required=True, validate=Length(1, 250))
+    descr = fields.String(allow_none=True)
+    uuid = fields.UUID(required=True)
+    version = fields.String(required=True)
+    annotation = fields.List(fields.Nested(ImportV1AnnotationSchema), 
load_default=[])

Review Comment:
   **Suggestion:** The mutable list used as `load_default` is shared across 
schema loads. If the importer or downstream processing mutates the default 
annotation collection, later imports that omit `annotation` can unexpectedly 
receive annotations from an earlier import. Use a callable default such as 
`list` so each load receives a fresh list. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Annotation data can leak between imports sharing one schema instance.
   - ⚠️ Imported layers without annotations may receive stale annotations.
   - ⚠️ Chart-to-annotation relationships can be restored incorrectly.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Construct `ImportV1AnnotationLayerSchema` from
   `superset/annotation_layers/schemas.py:80-87`.
   
   2. Call `schema.load()` with valid `name`, `uuid`, and `version` fields but 
omit
   `annotation`; Marshmallow uses the non-callable default list defined at
   `superset/annotation_layers/schemas.py:87`.
   
   3. Mutate the returned `annotation` list, for example by appending an 
annotation while
   downstream import processing handles the loaded layer.
   
   4. Call `schema.load()` again with another valid layer that also omits 
`annotation`; the
   second result can contain the annotation appended during the first load 
because both loads
   reuse the same list object. Using `load_default=list` gives each load an 
independent
   collection.
   ```
   </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=48a4498faf0f4f919901024396e0c536&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=48a4498faf0f4f919901024396e0c536&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/annotation_layers/schemas.py
   **Line:** 87:87
   **Comment:**
        *Possible Bug: The mutable list used as `load_default` is shared across 
schema loads. If the importer or downstream processing mutates the default 
annotation collection, later imports that omit `annotation` can unexpectedly 
receive annotations from an earlier import. Use a callable default such as 
`list` so each load receives a fresh list.
   
   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%2F39857&comment_hash=5aeb6526f2e0a1bc2b1756b314cd4b2921f310b17a962c8dc209238dbfa5b552&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=5aeb6526f2e0a1bc2b1756b314cd4b2921f310b17a962c8dc209238dbfa5b552&reaction=dislike'>👎</a>



##########
superset/commands/chart/export.py:
##########
@@ -93,16 +142,61 @@ def enable_tag_export(cls) -> None:
 
     @staticmethod
     def _export(
-        model: Slice, export_related: bool = True
+        model: Slice,
+        export_related: bool = True,
+        _seen: set[int] | None = None,
     ) -> Iterator[tuple[str, Callable[[], str]]]:
+        # Guard against circular annotation references (A→B→A).
+        # _seen is passed down the call stack so no class-level state is 
needed.
+        if _seen is None:
+            _seen = set()
+        if model.id in _seen:
+            return
+        _seen.add(model.id)
         yield (
             ExportChartsCommand._file_name(model),
             lambda: ExportChartsCommand._file_content(model),
         )
 
+        # Parse params once for deck_multi and annotation layer handling
+        try:
+            model_params = json.loads(model.params or "{}")
+        except json.JSONDecodeError:
+            model_params = {}
+
+        if (
+            model.viz_type == "deck_multi"
+            and export_related
+            and model_params.get("deck_slices")
+        ):
+            slice_ids = model_params.get("deck_slices")
+            yield from ExportChartsCommand(slice_ids).run()
+
         if model.table and export_related:
             yield from ExportDatasetsCommand([model.table.id]).run()
 
+        # Export charts referenced as annotation sources (table/line 
sourceType)
+        if export_related:
+            annotation_layers = model_params.get("annotation_layers", [])
+            chart_annotation_ids = [
+                layer["value"]
+                for layer in annotation_layers
+                if layer.get("sourceType") in ("table", "line")
+                and isinstance(layer.get("value"), int)
+            ]
+            if chart_annotation_ids:
+                yield from ExportChartsCommand(chart_annotation_ids).run()

Review Comment:
   **Suggestion:** The circular-reference guard is ineffective because 
recursive exports call `.run()` without passing `_seen`. If chart A references 
chart B and chart B references chart A, each nested command creates a new seen 
set and recursively exports forever until recursion depth is exceeded. 
Propagate the shared visited set through related chart exports or maintain 
deduplication at the command level. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Dashboard export can fail for cyclic chart annotations.
   - ❌ Referenced charts cannot be exported reliably.
   - ⚠️ Export requests may consume excessive recursive processing.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Create chart A with an annotation layer whose `sourceType` is `table` or 
`line` and
   whose `value` references chart B; create chart B with the reciprocal 
reference to chart A,
   matching the annotation-layer structure consumed by
   `superset/commands/chart/export.py:179-186`.
   
   2. Start a dashboard export through `superset/dashboards/api.py:1449`, which 
invokes
   `ExportChartsCommand`.
   
   3. `ExportChartsCommand._export()` at 
`superset/commands/chart/export.py:143-188` adds
   chart A to its local `_seen` set, then calls
   `ExportChartsCommand(chart_annotation_ids).run()` at line 188 for chart B.
   
   4. The nested command creates a new `_seen` set because `.run()` does not 
receive the
   parent set; exporting chart B then invokes another command for chart A, and 
the cycle
   continues until recursion depth or export processing fails.
   ```
   </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=fd4141d2207f4e1abb2ada3758974bf5&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=fd4141d2207f4e1abb2ada3758974bf5&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/commands/chart/export.py
   **Line:** 175:188
   **Comment:**
        *Logic Error: The circular-reference guard is ineffective because 
recursive exports call `.run()` without passing `_seen`. If chart A references 
chart B and chart B references chart A, each nested command creates a new seen 
set and recursively exports forever until recursion depth is exceeded. 
Propagate the shared visited set through related chart exports or maintain 
deduplication at the command level.
   
   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%2F39857&comment_hash=ead53b54daa8ffa32357c2ec4cacdaad5b702d91c9ab41f0af83157092d5ed62&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=ead53b54daa8ffa32357c2ec4cacdaad5b702d91c9ab41f0af83157092d5ed62&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