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


##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -761,3 +764,737 @@ def test_fave_unfave_chart_command_non_owner(self, 
mock_find_by_id):
             finally:
                 if example_chart.datasource:
                     self.revoke_role_access_to_table("Gamma", 
example_chart.datasource)
+
+
+class TestExportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling in chart export."""
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotation_uuid_replacement(self, mock_g):
+        """Test that NATIVE annotation layer IDs are replaced with UUIDs on 
export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Create an annotation layer
+        ann_layer = AnnotationLayer(name="Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        # Get a chart and set params with a NATIVE annotation referencing the 
layer
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Layer 1",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()

Review Comment:
   **Suggestion:** The test permanently modifies the shared `Energy Sankey` 
chart's persisted `params` and never restores the original value. If the test 
suite reuses this fixture or the test fails before cleanup, later tests can 
observe the injected annotation configuration and become order-dependent. 
Restore `chart.params` in a `finally` block or isolate the chart mutation in a 
transaction. [state inconsistency]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Export tests can observe altered Energy Sankey parameters.
   - ⚠️ Test outcomes can depend on execution order.
   - ⚠️ Failed tests can leave persistent fixture state behind.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run 
`TestExportChartsAnnotationLayers.test_export_chart_no_annotation_layers` in
   `tests/integration_tests/charts/commands_tests.py:1002`, which obtains the 
shared `Energy
   Sankey` chart at line 1006.
   
   2. The test removes `annotation_layers` from the chart parameters at lines 
1008-1010 and
   commits the change at line 1011; the original parameters are not restored.
   
   3. Execute a later test that uses the same fixture chart, such as
   `test_export_chart_native_annotation_uuid_replacement` at line 774 or
   `test_export_chart_query_context_annotation_uuid_replacement` at line 1030. 
Those tests
   load `Energy Sankey` and read its persisted parameters.
   
   4. If the fixture/database is reused, those tests observe the modified 
parameters rather
   than the fixture's original state. If an assertion fails before line 1027, 
the test also
   leaves its database changes behind, increasing order dependence.
   ```
   </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=3d3da461131547959c2b46a2b93c0258&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=3d3da461131547959c2b46a2b93c0258&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:** tests/integration_tests/charts/commands_tests.py
   **Line:** 786:796
   **Comment:**
        *State Inconsistency: The test permanently modifies the shared `Energy 
Sankey` chart's persisted `params` and never restores the original value. If 
the test suite reuses this fixture or the test fails before cleanup, later 
tests can observe the injected annotation configuration and become 
order-dependent. Restore `chart.params` in a `finally` block or isolate the 
chart mutation in a transaction.
   
   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=60d5e388ee770ca0a78dbfff98fa5daea3f778ffe65850c5e41a2bcad4937f0c&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=60d5e388ee770ca0a78dbfff98fa5daea3f778ffe65850c5e41a2bcad4937f0c&reaction=dislike'>👎</a>



##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -761,3 +764,737 @@ def test_fave_unfave_chart_command_non_owner(self, 
mock_find_by_id):
             finally:
                 if example_chart.datasource:
                     self.revoke_role_access_to_table("Gamma", 
example_chart.datasource)
+
+
+class TestExportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling in chart export."""
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotation_uuid_replacement(self, mock_g):
+        """Test that NATIVE annotation layer IDs are replaced with UUIDs on 
export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Create an annotation layer
+        ann_layer = AnnotationLayer(name="Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        # Get a chart and set params with a NATIVE annotation referencing the 
layer
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Layer 1",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ann_layer.uuid)
+        assert layers[0]["sourceType"] == "NATIVE"
+
+        # Verify annotation layer YAML is included in export
+        ann_layer_key = [k for k in contents if 
k.startswith("annotation_layers/")]
+        assert len(ann_layer_key) == 1
+
+        # Clean up
+        db.session.query(Annotation).filter_by(layer_id=ann_layer.id).delete()
+        db.session.delete(ann_layer)
+        db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_table_annotation_uuid_replacement(self, mock_g):
+        """Test that table/line annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Get two charts: one main, one referenced as annotation source
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        # Set params with a table annotation referencing the other chart
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Table Annotation",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()

Review Comment:
   **Suggestion:** This test mutates the shared `Energy Sankey` chart's 
`params` without restoring the original configuration. The mutation remains in 
the database after the test, so subsequent tests using the same chart can 
inherit the synthetic annotation layer, especially when an assertion aborts 
before any cleanup. [state inconsistency]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Later export tests can inherit a table annotation.
   - ⚠️ Shared chart fixture state becomes order-dependent.
   - ⚠️ Failed tests can contaminate subsequent integration tests.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run
   
`TestExportChartsAnnotationLayers.test_export_chart_table_annotation_uuid_replacement`
 at
   `tests/integration_tests/charts/commands_tests.py:819`; it loads the shared 
`Energy
   Sankey` and `Heatmap` charts at lines 823-830.
   
   2. The test writes a synthetic table annotation referencing `Heatmap` into 
`Energy
   Sankey.params` at lines 834-843 and commits it at line 844.
   
   3. The export assertion completes at lines 846-858, but no code restores
   `main_chart.params` afterward.
   
   4. Run another test using `Energy Sankey`, such as
   `test_export_chart_no_annotation_layers` at line 1002. Its expectation that 
the chart has
   no annotation layers can be affected by the previously committed table 
annotation, and an
   earlier assertion failure leaves the mutation committed as well.
   ```
   </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=b46775df7df147b792141a616a61d7c3&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=b46775df7df147b792141a616a61d7c3&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:** tests/integration_tests/charts/commands_tests.py
   **Line:** 834:844
   **Comment:**
        *State Inconsistency: This test mutates the shared `Energy Sankey` 
chart's `params` without restoring the original configuration. The mutation 
remains in the database after the test, so subsequent tests using the same 
chart can inherit the synthetic annotation layer, especially when an assertion 
aborts before any cleanup.
   
   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=9a49c216538778a30b4a888af802b4fb5f2762ae38428a12d8c1fa08de169a6c&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=9a49c216538778a30b4a888af802b4fb5f2762ae38428a12d8c1fa08de169a6c&reaction=dislike'>👎</a>



##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -761,3 +764,737 @@ def test_fave_unfave_chart_command_non_owner(self, 
mock_find_by_id):
             finally:
                 if example_chart.datasource:
                     self.revoke_role_access_to_table("Gamma", 
example_chart.datasource)
+
+
+class TestExportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling in chart export."""
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotation_uuid_replacement(self, mock_g):
+        """Test that NATIVE annotation layer IDs are replaced with UUIDs on 
export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Create an annotation layer
+        ann_layer = AnnotationLayer(name="Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        # Get a chart and set params with a NATIVE annotation referencing the 
layer
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Layer 1",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ann_layer.uuid)
+        assert layers[0]["sourceType"] == "NATIVE"
+
+        # Verify annotation layer YAML is included in export
+        ann_layer_key = [k for k in contents if 
k.startswith("annotation_layers/")]
+        assert len(ann_layer_key) == 1
+
+        # Clean up
+        db.session.query(Annotation).filter_by(layer_id=ann_layer.id).delete()
+        db.session.delete(ann_layer)
+        db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_table_annotation_uuid_replacement(self, mock_g):
+        """Test that table/line annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Get two charts: one main, one referenced as annotation source
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        # Set params with a table annotation referencing the other chart
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Table Annotation",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "table"
+
+        # The referenced chart should also be exported
+        ref_chart_key = f"charts/Heatmap_{ref_chart.id}.yaml"
+        assert ref_chart_key in contents
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_line_annotation_uuid_replacement(self, mock_g):
+        """Test that line sourceType annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Line Annotation",
+                "sourceType": "line",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "dashed",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "line"
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_formula_annotation_unchanged(self, mock_g):
+        """Test that FORMULA annotations are not modified during export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Formula",
+                "sourceType": "FORMULA",
+                "value": "sin(x)",
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()

Review Comment:
   **Suggestion:** The formula test commits a modified `Energy Sankey` chart 
but never restores its original parameters. This contaminates the shared 
fixture for later tests and leaves the database in a different state even when 
the test passes. [state inconsistency]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Later exports can contain an unintended formula annotation.
   - ⚠️ Fixture-dependent tests may become order-sensitive.
   - ⚠️ Persistent test data differs from the expected baseline.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run 
`TestExportChartsAnnotationLayers.test_export_chart_formula_annotation_unchanged`
   at `tests/integration_tests/charts/commands_tests.py:899`; it loads the 
shared `Energy
   Sankey` chart at line 903.
   
   2. The test adds a `FORMULA` annotation with value `sin(x)` at lines 905-914 
and commits
   the chart at line 915.
   
   3. The export assertion runs at lines 917-925, but there is no restoration of
   `chart.params` after the assertion.
   
   4. Run another test that expects the fixture's original `Energy Sankey` 
parameters, such
   as `test_export_chart_no_annotation_layers` at line 1002. A reused database 
exposes the
   committed formula annotation, and any failure before the assertion completes 
also skips
   cleanup.
   ```
   </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=77afe1efaf3049549ae0237c2eea1276&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=77afe1efaf3049549ae0237c2eea1276&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:** tests/integration_tests/charts/commands_tests.py
   **Line:** 905:915
   **Comment:**
        *State Inconsistency: The formula test commits a modified `Energy 
Sankey` chart but never restores its original parameters. This contaminates the 
shared fixture for later tests and leaves the database in a different state 
even when the test passes.
   
   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=b471ded7cc3837a4d96f37630f9c76fe09cf549ac2cc3815c46463f885e8148b&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=b471ded7cc3837a4d96f37630f9c76fe09cf549ac2cc3815c46463f885e8148b&reaction=dislike'>👎</a>



##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -761,3 +764,737 @@ def test_fave_unfave_chart_command_non_owner(self, 
mock_find_by_id):
             finally:
                 if example_chart.datasource:
                     self.revoke_role_access_to_table("Gamma", 
example_chart.datasource)
+
+
+class TestExportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling in chart export."""
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotation_uuid_replacement(self, mock_g):
+        """Test that NATIVE annotation layer IDs are replaced with UUIDs on 
export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Create an annotation layer
+        ann_layer = AnnotationLayer(name="Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        # Get a chart and set params with a NATIVE annotation referencing the 
layer
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Layer 1",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ann_layer.uuid)
+        assert layers[0]["sourceType"] == "NATIVE"
+
+        # Verify annotation layer YAML is included in export
+        ann_layer_key = [k for k in contents if 
k.startswith("annotation_layers/")]
+        assert len(ann_layer_key) == 1
+
+        # Clean up
+        db.session.query(Annotation).filter_by(layer_id=ann_layer.id).delete()
+        db.session.delete(ann_layer)
+        db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_table_annotation_uuid_replacement(self, mock_g):
+        """Test that table/line annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Get two charts: one main, one referenced as annotation source
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        # Set params with a table annotation referencing the other chart
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Table Annotation",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "table"
+
+        # The referenced chart should also be exported
+        ref_chart_key = f"charts/Heatmap_{ref_chart.id}.yaml"
+        assert ref_chart_key in contents
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_line_annotation_uuid_replacement(self, mock_g):
+        """Test that line sourceType annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Line Annotation",
+                "sourceType": "line",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "dashed",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()

Review Comment:
   **Suggestion:** This test persists a synthetic line annotation on the shared 
`Energy Sankey` chart and does not restore the previous `params`. A failed or 
subsequently reused test can therefore see stale annotation state and produce 
misleading results. [state inconsistency]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Later chart-export tests can inherit line annotations.
   - ⚠️ Results can vary with test execution order.
   - ⚠️ Failed tests leave committed chart mutations.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run
   
`TestExportChartsAnnotationLayers.test_export_chart_line_annotation_uuid_replacement`
 at
   `tests/integration_tests/charts/commands_tests.py:862`; it selects `Energy 
Sankey` and
   `Heatmap` at lines 867-872.
   
   2. The test assigns a synthetic `line` annotation to `Energy Sankey.params` 
at lines
   875-884 and commits it at line 885.
   
   3. The test verifies the export at lines 887-895 but does not restore the 
original
   parameters afterward.
   
   4. Run a later test that reads or exports `Energy Sankey`, such as
   `test_export_chart_query_context_annotation_uuid_replacement` at line 1030. 
With a reused
   integration-test database, it can see the stale line annotation; if the 
earlier assertion
   fails, cleanup is skipped entirely.
   ```
   </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=1dee501c549743c286d83e14745f1b61&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=1dee501c549743c286d83e14745f1b61&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:** tests/integration_tests/charts/commands_tests.py
   **Line:** 875:885
   **Comment:**
        *State Inconsistency: This test persists a synthetic line annotation on 
the shared `Energy Sankey` chart and does not restore the previous `params`. A 
failed or subsequently reused test can therefore see stale annotation state and 
produce misleading results.
   
   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=c7b7e56bceecb6815d5a660186d31e7c40ea371a7e72e8e9cc233995913f27bf&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=c7b7e56bceecb6815d5a660186d31e7c40ea371a7e72e8e9cc233995913f27bf&reaction=dislike'>👎</a>



##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -761,3 +764,737 @@ def test_fave_unfave_chart_command_non_owner(self, 
mock_find_by_id):
             finally:
                 if example_chart.datasource:
                     self.revoke_role_access_to_table("Gamma", 
example_chart.datasource)
+
+
+class TestExportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling in chart export."""
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotation_uuid_replacement(self, mock_g):
+        """Test that NATIVE annotation layer IDs are replaced with UUIDs on 
export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Create an annotation layer
+        ann_layer = AnnotationLayer(name="Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        # Get a chart and set params with a NATIVE annotation referencing the 
layer
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Layer 1",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ann_layer.uuid)
+        assert layers[0]["sourceType"] == "NATIVE"
+
+        # Verify annotation layer YAML is included in export
+        ann_layer_key = [k for k in contents if 
k.startswith("annotation_layers/")]
+        assert len(ann_layer_key) == 1
+
+        # Clean up
+        db.session.query(Annotation).filter_by(layer_id=ann_layer.id).delete()
+        db.session.delete(ann_layer)
+        db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_table_annotation_uuid_replacement(self, mock_g):
+        """Test that table/line annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Get two charts: one main, one referenced as annotation source
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        # Set params with a table annotation referencing the other chart
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Table Annotation",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "table"
+
+        # The referenced chart should also be exported
+        ref_chart_key = f"charts/Heatmap_{ref_chart.id}.yaml"
+        assert ref_chart_key in contents
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_line_annotation_uuid_replacement(self, mock_g):
+        """Test that line sourceType annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Line Annotation",
+                "sourceType": "line",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "dashed",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "line"
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_formula_annotation_unchanged(self, mock_g):
+        """Test that FORMULA annotations are not modified during export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Formula",
+                "sourceType": "FORMULA",
+                "value": "sin(x)",
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == "sin(x)"
+        assert layers[0]["sourceType"] == "FORMULA"
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_mixed_annotation_types(self, mock_g):
+        """Test export with NATIVE, table, and FORMULA annotations together."""
+        mock_g.user = security_manager.find_user("admin")
+
+        ann_layer = AnnotationLayer(name="Mixed Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Native",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            },
+            {
+                "name": "Table",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            },
+            {
+                "name": "Formula",
+                "sourceType": "FORMULA",
+                "value": "cos(x)",
+                "show": True,
+                "style": "dashed",
+            },
+        ]
+        main_chart.params = json.dumps(original_params)

Review Comment:
   **Suggestion:** The mixed-annotation test commits changes to the shared 
chart's `params` and only deletes the newly created annotation layer; it never 
restores the chart parameters. This leaves the table, native, and formula 
annotations behind for subsequent tests and can also leave the database 
contaminated if an assertion fails before cleanup. [state inconsistency]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Later exports can reference a deleted annotation layer.
   - ⚠️ Shared chart parameters become contaminated.
   - ⚠️ Cleanup failures can leave multiple database records.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run 
`TestExportChartsAnnotationLayers.test_export_chart_mixed_annotation_types` at
   `tests/integration_tests/charts/commands_tests.py:929`; it selects the 
shared `Energy
   Sankey` and `Heatmap` charts at lines 937-943 and creates an annotation 
layer at lines
   933-935.
   
   2. The test replaces `Energy Sankey.params` with native, table, and formula 
annotations at
   lines 945-969 and commits the mutation at line 970.
   
   3. The export assertions run at lines 972-993, then cleanup only deletes the 
newly created
   `AnnotationLayer` at lines 995-998; it does not restore `main_chart.params`.
   
   4. Run a later test using `Energy Sankey`, such as
   `test_export_chart_no_annotation_layers` at line 1002. The chart can still 
reference the
   deleted annotation-layer ID and retain the table/formula entries, while 
assertion failures
   before cleanup leave both the chart and annotation layer persisted.
   ```
   </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=5cea2bfdebd74d1886ad2d969db082ef&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=5cea2bfdebd74d1886ad2d969db082ef&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:** tests/integration_tests/charts/commands_tests.py
   **Line:** 945:969
   **Comment:**
        *State Inconsistency: The mixed-annotation test commits changes to the 
shared chart's `params` and only deletes the newly created annotation layer; it 
never restores the chart parameters. This leaves the table, native, and formula 
annotations behind for subsequent tests and can also leave the database 
contaminated if an assertion fails before cleanup.
   
   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=dcdc179d3b7859302b10a6f399d7f24ca6bdd19d15717a359b7ff7b53c833778&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=dcdc179d3b7859302b10a6f399d7f24ca6bdd19d15717a359b7ff7b53c833778&reaction=dislike'>👎</a>



##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -761,3 +764,737 @@ def test_fave_unfave_chart_command_non_owner(self, 
mock_find_by_id):
             finally:
                 if example_chart.datasource:
                     self.revoke_role_access_to_table("Gamma", 
example_chart.datasource)
+
+
+class TestExportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling in chart export."""
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotation_uuid_replacement(self, mock_g):
+        """Test that NATIVE annotation layer IDs are replaced with UUIDs on 
export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Create an annotation layer
+        ann_layer = AnnotationLayer(name="Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        # Get a chart and set params with a NATIVE annotation referencing the 
layer
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Layer 1",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ann_layer.uuid)
+        assert layers[0]["sourceType"] == "NATIVE"
+
+        # Verify annotation layer YAML is included in export
+        ann_layer_key = [k for k in contents if 
k.startswith("annotation_layers/")]
+        assert len(ann_layer_key) == 1
+
+        # Clean up
+        db.session.query(Annotation).filter_by(layer_id=ann_layer.id).delete()
+        db.session.delete(ann_layer)
+        db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_table_annotation_uuid_replacement(self, mock_g):
+        """Test that table/line annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        # Get two charts: one main, one referenced as annotation source
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        # Set params with a table annotation referencing the other chart
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Table Annotation",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "table"
+
+        # The referenced chart should also be exported
+        ref_chart_key = f"charts/Heatmap_{ref_chart.id}.yaml"
+        assert ref_chart_key in contents
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_line_annotation_uuid_replacement(self, mock_g):
+        """Test that line sourceType annotation chart IDs are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Line Annotation",
+                "sourceType": "line",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "dashed",
+            }
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == str(ref_chart.uuid)
+        assert layers[0]["sourceType"] == "line"
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_formula_annotation_unchanged(self, mock_g):
+        """Test that FORMULA annotations are not modified during export."""
+        mock_g.user = security_manager.find_user("admin")
+
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Formula",
+                "sourceType": "FORMULA",
+                "value": "sin(x)",
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 1
+        assert layers[0]["value"] == "sin(x)"
+        assert layers[0]["sourceType"] == "FORMULA"
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_mixed_annotation_types(self, mock_g):
+        """Test export with NATIVE, table, and FORMULA annotations together."""
+        mock_g.user = security_manager.find_user("admin")
+
+        ann_layer = AnnotationLayer(name="Mixed Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = [
+            {
+                "name": "Native",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+                "style": "solid",
+            },
+            {
+                "name": "Table",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+                "style": "solid",
+            },
+            {
+                "name": "Formula",
+                "sourceType": "FORMULA",
+                "value": "cos(x)",
+                "show": True,
+                "style": "dashed",
+            },
+        ]
+        main_chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([main_chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{main_chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        layers = chart_yaml["params"]["annotation_layers"]
+        assert len(layers) == 3
+
+        # NATIVE → UUID of annotation layer
+        assert layers[0]["value"] == str(ann_layer.uuid)
+        # table → UUID of referenced chart
+        assert layers[1]["value"] == str(ref_chart.uuid)
+        # FORMULA → unchanged string
+        assert layers[2]["value"] == "cos(x)"
+
+        # Annotation layer YAML should be in the export
+        ann_keys = [k for k in contents if k.startswith("annotation_layers/")]
+        assert len(ann_keys) == 1
+
+        # Referenced chart should be in the export
+        ref_chart_key = f"charts/Heatmap_{ref_chart.id}.yaml"
+        assert ref_chart_key in contents
+
+        # Clean up
+        db.session.query(Annotation).filter_by(layer_id=ann_layer.id).delete()
+        db.session.delete(ann_layer)
+        db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_no_annotation_layers(self, mock_g):
+        """Test that charts without annotation_layers export normally."""
+        mock_g.user = security_manager.find_user("admin")
+
+        chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        # Ensure no annotation_layers in params
+        original_params = json.loads(chart.params or "{}")
+        original_params.pop("annotation_layers", None)
+        chart.params = json.dumps(original_params)
+        db.session.commit()
+
+        command = ExportChartsCommand([chart.id])
+        contents = dict(command.run())
+
+        chart_key = f"charts/Energy_Sankey_{chart.id}.yaml"
+        chart_yaml = yaml.safe_load(contents[chart_key]())
+        # No annotation_layers key or empty list — either is fine
+        ann_layers = chart_yaml.get("params", {}).get("annotation_layers", [])
+        assert ann_layers == [] or "annotation_layers" not in chart_yaml.get(
+            "params", {}
+        )
+
+        # No annotation_layers/ files in export
+        ann_keys = [k for k in contents if k.startswith("annotation_layers/")]
+        assert len(ann_keys) == 0
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_query_context_annotation_uuid_replacement(self, 
mock_g):
+        """Test that annotation IDs in query_context are replaced with 
UUIDs."""
+        mock_g.user = security_manager.find_user("admin")
+
+        ann_layer = AnnotationLayer(name="QC Test Layer")
+        db.session.add(ann_layer)
+        db.session.commit()
+
+        charts = (
+            db.session.query(Slice)
+            .filter(Slice.slice_name.in_(["Energy Sankey", "Heatmap"]))
+            .all()
+        )
+        main_chart = next(c for c in charts if c.slice_name == "Energy Sankey")
+        ref_chart = next(c for c in charts if c.slice_name == "Heatmap")
+
+        annotations = [
+            {
+                "name": "Native",
+                "sourceType": "NATIVE",
+                "value": ann_layer.id,
+                "show": True,
+            },
+            {
+                "name": "Table",
+                "sourceType": "table",
+                "value": ref_chart.id,
+                "show": True,
+            },
+        ]
+
+        original_params = json.loads(main_chart.params or "{}")
+        original_params["annotation_layers"] = deepcopy(annotations)
+        main_chart.params = json.dumps(original_params)
+
+        query_context = {
+            "datasource": {"id": 1, "type": "table"},
+            "queries": [{"annotation_layers": deepcopy(annotations)}],
+            "form_data": {"annotation_layers": deepcopy(annotations)},
+        }
+        main_chart.query_context = json.dumps(query_context)
+        db.session.commit()

Review Comment:
   **Suggestion:** The query-context test persists both modified `params` and 
`query_context` on the shared `Energy Sankey` chart, but its cleanup only 
removes the newly created annotation layer. Neither field is restored, so later 
tests can consume stale query-context annotation IDs and chart parameters. 
[state inconsistency]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Later chart exports can contain stale query-context annotations.
   - ⚠️ Query execution tests may use contaminated chart metadata.
   - ⚠️ Shared integration-test state becomes order-dependent.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run
   
`TestExportChartsAnnotationLayers.test_export_chart_query_context_annotation_uuid_replacement`
   at `tests/integration_tests/charts/commands_tests.py:1030`; it loads `Energy 
Sankey` and
   `Heatmap` at lines 1038-1044.
   
   2. The test modifies `main_chart.params` at lines 1061-1063 and assigns 
annotation layers
   under both `queries` and `form_data` in `query_context` at lines 1065-1069.
   
   3. Both fields are committed at line 1071, and the export assertions inspect 
them at lines
   1073-1088.
   
   4. Cleanup at lines 1090-1093 deletes only the newly created annotation 
layer. It does not
   restore either field, so later tests reading or exporting `Energy Sankey` 
can consume
   stale query-context IDs; failures before cleanup leave the changes committed.
   ```
   </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=55fb52e5ee7e46f2aa04fc0c13576e0f&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=55fb52e5ee7e46f2aa04fc0c13576e0f&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:** tests/integration_tests/charts/commands_tests.py
   **Line:** 1061:1071
   **Comment:**
        *State Inconsistency: The query-context test persists both modified 
`params` and `query_context` on the shared `Energy Sankey` chart, but its 
cleanup only removes the newly created annotation layer. Neither field is 
restored, so later tests can consume stale query-context annotation IDs and 
chart parameters.
   
   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=151d901ea5ab9b00cff28919bc356ba4eb1cc17ca0bb56d52aeef84b5a2419dd&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=151d901ea5ab9b00cff28919bc356ba4eb1cc17ca0bb56d52aeef84b5a2419dd&reaction=dislike'>👎</a>



##########
superset/commands/chart/importers/v1/__init__.py:
##########
@@ -44,12 +51,33 @@ class ImportChartsCommand(ImportModelsCommand):
     model_name = "chart"
     prefix = "charts/"
     schemas: dict[str, Schema] = {
+        "annotation_layers/": ImportV1AnnotationLayerSchema(),
         "charts/": ImportV1ChartSchema(),
         "datasets/": ImportV1DatasetSchema(),
         "databases/": ImportV1DatabaseSchema(),
     }
     import_error = ChartImportError
 
+    @staticmethod
+    def _import_chart_with_tags(
+        config: dict[str, Any],
+        overwrite: bool,
+        contents: dict[str, Any],
+        imported_chart_ids: dict[str, int],
+        annotation_layer_ids: dict[str, int] | None = None,
+    ) -> None:
+        chart = import_chart(
+            config,
+            overwrite=overwrite,
+            annotation_layer_ids=annotation_layer_ids,
+            chart_ids=imported_chart_ids,
+        )
+        imported_chart_ids[str(chart.uuid)] = chart.id
+
+        if feature_flag_manager.is_feature_enabled("TAGGING_SYSTEM"):
+            if "tags" in config:
+                import_tag(config["tags"], contents, chart.id, "chart", 
db.session)

Review Comment:
   **Suggestion:** `feature_flag_manager` is referenced without being imported 
or defined in this module. Any chart import that reaches the tagging block will 
raise `NameError` after the chart is imported, causing the import command to 
fail. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Chart and dashboard imports can fail after chart creation.
   - ❌ Import completion is blocked when the tagging check executes.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start a Superset instance with the PR code and invoke the existing chart 
import flow
   through `ImportChartsCommand._import()` at
   `superset/commands/chart/importers/v1/__init__.py:83`, such as dashboard 
import through
   `superset/dashboards/api.py:2219`.
   
   2. Include a valid exported chart configuration so 
`_import_chart_with_tags()` at
   `superset/commands/chart/importers/v1/__init__.py:62` is called.
   
   3. After `import_chart()` returns at line 69, execution reaches
   `feature_flag_manager.is_feature_enabled("TAGGING_SYSTEM")` at line 77.
   
   4. `feature_flag_manager` is not imported or defined in
   `superset/commands/chart/importers/v1/__init__.py`, so Python raises 
`NameError` and the
   chart import command fails before completing its tagging handling.
   ```
   </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=2f3c02176d454f23817f4cd26f4e2d4b&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=2f3c02176d454f23817f4cd26f4e2d4b&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/importers/v1/__init__.py
   **Line:** 77:79
   **Comment:**
        *Possible Bug: `feature_flag_manager` is referenced without being 
imported or defined in this module. Any chart import that reaches the tagging 
block will raise `NameError` after the chart is imported, causing the import 
command to fail.
   
   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=cfd5cff734ae07203ee792e9b361f544b2c69a85fbe5231615d66714c7e1f597&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=cfd5cff734ae07203ee792e9b361f544b2c69a85fbe5231615d66714c7e1f597&reaction=dislike'>👎</a>



##########
superset/commands/chart/importers/v1/__init__.py:
##########
@@ -58,11 +86,18 @@ def _import(
         contents: dict[str, Any] | None = None,
     ) -> None:
         contents = {} if contents is None else contents
-        # discover datasets associated with charts
+        # discover datasets and referenced chart UUIDs from chart configs
         dataset_uuids: set[str] = set()
+        referenced_chart_uuids: set[str] = set()
         for file_name, config in configs.items():
             if file_name.startswith("charts/"):
                 dataset_uuids.add(config["dataset_uuid"])
+                for annotation in config.get("params", 
{}).get("annotation_layers", []):
+                    if annotation.get("sourceType") in (
+                        "table",
+                        "line",
+                    ) and isinstance(annotation.get("value"), str):
+                        referenced_chart_uuids.add(annotation["value"])

Review Comment:
   **Suggestion:** Dependency discovery only scans `params.annotation_layers`, 
while `_resolve_query_context_annotations` also resolves annotation references 
inside `query_context`. Charts referenced only from query context will not be 
placed in the dependency-first order, so newly imported charts may be processed 
before their referenced charts and their query-context references can remain 
unresolved. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Query-context chart annotations can remain unresolved after import.
   - ⚠️ Charts using query-context annotations may lose annotation behavior.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Export charts through `ExportChartsCommand` at 
`superset/commands/chart/export.py:46`
   where a chart's annotation configuration includes a chart reference inside 
serialized
   `query_context.annotation_layers`, but not inside `params.annotation_layers`.
   
   2. Import the export through `ImportChartsCommand._import()` at
   `superset/commands/chart/importers/v1/__init__.py:83` with the dependent 
chart listed
   before its referenced chart in `configs`.
   
   3. The dependency scan at lines 92-100 examines only `config.get("params",
   {}).get("annotation_layers", [])`, so it does not add the query-context 
chart UUID to
   `referenced_chart_uuids`.
   
   4. The dependent chart is therefore placed in `dependent_chart_configs` and 
can be passed
   to `_import_chart_with_tags()` at line 163 before the referenced chart has 
been added to
   `imported_chart_ids`; `_resolve_query_context_annotations()` at
   `superset/commands/chart/importers/v1/utils.py:145` cannot resolve that UUID 
at this point
   and leaves the reference unresolved.
   ```
   </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=ee0305cfc12f41e9bb8e3602d94b58e1&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=ee0305cfc12f41e9bb8e3602d94b58e1&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/importers/v1/__init__.py
   **Line:** 92:100
   **Comment:**
        *Logic Error: Dependency discovery only scans 
`params.annotation_layers`, while `_resolve_query_context_annotations` also 
resolves annotation references inside `query_context`. Charts referenced only 
from query context will not be placed in the dependency-first order, so newly 
imported charts may be processed before their referenced charts and their 
query-context references can remain unresolved.
   
   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=96cf58f3cb98898f60944e23d9a5758db71bf60c708e384fd415ffb2df4e3b7c&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=96cf58f3cb98898f60944e23d9a5758db71bf60c708e384fd415ffb2df4e3b7c&reaction=dislike'>👎</a>



##########
superset/commands/chart/importers/v1/__init__.py:
##########
@@ -88,6 +123,16 @@ def _import(
                 dataset = import_dataset(config, overwrite=False)
                 datasets[str(dataset.uuid)] = dataset
 
+        # import annotation layers before charts so UUID→ID maps are ready
+        annotation_layer_ids: dict[str, int] = {}
+        for file_name, config in configs.items():
+            if file_name.startswith("annotation_layers/"):
+                layer = import_annotation_layer(config, overwrite=False)

Review Comment:
   **Suggestion:** The annotation-layer import ignores the command's 
`overwrite` value by always passing `overwrite=False`. Re-importing an export 
with overwrite enabled will therefore retain stale annotation layers and 
annotations instead of updating them. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Re-imported dashboards retain stale annotation-layer configuration.
   - ⚠️ Chart contextual annotations may differ from exported source state.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Export a chart or dashboard containing an annotation layer through
   `ExportChartsCommand`, which is called by `superset/dashboards/api.py:1449` 
and includes
   an `annotation_layers/` configuration.
   
   2. Change the annotation layer's event, interval, or SQL configuration in 
the source
   system, then import the export through the existing dashboard import entry 
point
   `superset/dashboards/api.py:2219` with `overwrite=True`.
   
   3. `ImportChartsCommand._import()` at
   `superset/commands/chart/importers/v1/__init__.py:83` reaches the 
annotation-layer loop at
   lines 128-131 and calls `import_annotation_layer(config, overwrite=False)`.
   
   4. The annotation-layer importer at
   `superset/commands/annotation_layer/importers/v1/utils.py:25` therefore uses 
non-overwrite
   semantics, so an existing layer with the same UUID is retained instead of 
receiving the
   updated exported configuration; the chart's resolved annotation reference 
points to stale
   data.
   ```
   </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=f27ec413eefc4a62bc2411ead118e0f8&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=f27ec413eefc4a62bc2411ead118e0f8&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/importers/v1/__init__.py
   **Line:** 128:130
   **Comment:**
        *Logic Error: The annotation-layer import ignores the command's 
`overwrite` value by always passing `overwrite=False`. Re-importing an export 
with overwrite enabled will therefore retain stale annotation layers and 
annotations instead of updating them.
   
   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=b9e5bbb6faef2f7b58758e8f1f2181c207f56abb9c724f4b8b9ec5ba5414b183&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=b9e5bbb6faef2f7b58758e8f1f2181c207f56abb9c724f4b8b9ec5ba5414b183&reaction=dislike'>👎</a>



##########
superset/commands/chart/importers/v1/utils.py:
##########
@@ -24,24 +25,148 @@
 from superset.commands.importers.v1.utils import find_existing_for_import
 from superset.migrations.shared.migrate_viz import processors
 from superset.migrations.shared.migrate_viz.base import MigrateViz
+from superset.models.annotations import AnnotationLayer
 from superset.models.slice import Slice
 from superset.utils import json
 from superset.utils.core import AnnotationType, get_user
 
+logger = logging.getLogger(__name__)
 
-def filter_chart_annotations(chart_config: dict[str, Any]) -> None:
+
+def topological_sort_charts(
+    chart_configs: list[dict[str, Any]],
+) -> list[dict[str, Any]]:
+    """Sort charts so that annotation dependencies are imported first.
+
+    Handles multi-level dependencies (A→B→C) by iteratively resolving
+    charts whose in-batch dependencies are already satisfied.
     """
-    Mutating the chart's config params to keep only the annotations of
-    type FORMULA.
-    TODO:
-      handle annotation dependencies on either other charts or
-      annotation layers objects.
+    if len(chart_configs) <= 1:
+        return chart_configs
+
+    batch_uuids = {c["uuid"] for c in chart_configs}
+    sorted_refs: list[dict[str, Any]] = []
+    remaining = list(chart_configs)
+    resolved: set[str] = set()
+    while remaining:
+        next_remaining = []
+        for c in remaining:
+            unmet = {
+                ann["value"]
+                for ann in c.get("params", {}).get("annotation_layers", [])
+                if ann.get("sourceType") in ("table", "line")
+                and isinstance(ann.get("value"), str)
+                and ann["value"] in batch_uuids - resolved
+            }
+            if not unmet:
+                sorted_refs.append(c)
+                resolved.add(c["uuid"])
+            else:
+                next_remaining.append(c)
+        if len(next_remaining) == len(remaining):
+            logger.warning(
+                "Circular annotation dependency detected for charts: %s — "
+                "these charts may have unresolved annotation references after 
import.",
+                [c["uuid"] for c in next_remaining],
+            )
+            sorted_refs.extend(next_remaining)
+            break
+        remaining = next_remaining
+    return sorted_refs
+
+
+def _resolve_uuid_to_id(
+    uuid_value: str,
+    id_map: dict[str, int] | None,
+    model: type,
+) -> int | None:
+    """Resolve a UUID to a local integer ID using a map or DB fallback."""
+    if id_map and uuid_value in id_map:
+        return id_map[uuid_value]
+    obj = db.session.query(model).filter_by(uuid=uuid_value).first()
+    return obj.id if obj else None
+
+
+def filter_chart_annotations(
+    chart_config: dict[str, Any],
+    annotation_layer_ids: dict[str, int] | None = None,
+    chart_ids: dict[str, int] | None = None,
+) -> None:
+    """
+    Resolve annotation references from exported UUIDs to local integer IDs.
+    - FORMULA: kept unchanged (no DB reference)
+    - NATIVE: UUID resolved to AnnotationLayer.id
+    - table/line: UUID resolved to referenced Chart.id
+    Annotations whose references cannot be resolved are dropped.
     """
     params = chart_config.get("params", {})
-    als = params.get("annotation_layers", [])
-    params["annotation_layers"] = [
-        al for al in als if al.get("annotationType") == AnnotationType.FORMULA
-    ]
+    annotation_layers = params.get("annotation_layers", [])
+    resolved_annotations: list[dict[str, Any]] = []
+    for annotation in annotation_layers:
+        source_type = annotation.get("sourceType")
+        value = annotation.get("value")
+
+        if annotation.get("annotationType") == AnnotationType.FORMULA:
+            resolved_annotations.append(annotation)
+        elif source_type == "NATIVE" and isinstance(value, str):
+            layer_id = _resolve_uuid_to_id(value, annotation_layer_ids, 
AnnotationLayer)
+            if layer_id is not None:
+                annotation["value"] = layer_id
+                resolved_annotations.append(annotation)
+        elif source_type in ("table", "line") and isinstance(value, str):
+            ref_chart_id = _resolve_uuid_to_id(value, chart_ids, Slice)
+            if ref_chart_id is not None:
+                annotation["value"] = ref_chart_id
+                resolved_annotations.append(annotation)
+    params["annotation_layers"] = resolved_annotations
+
+
+def _resolve_annotation_list(
+    annotations: list[dict[str, Any]],
+    annotation_layer_ids: dict[str, int] | None,
+    chart_ids: dict[str, int] | None,
+) -> None:
+    """Resolve UUID values to integer IDs in-place for an annotation list."""
+    for annotation in annotations:
+        source_type = annotation.get("sourceType")
+        value = annotation.get("value")
+        if not isinstance(value, str):
+            continue
+        if source_type == "NATIVE":
+            layer_id = _resolve_uuid_to_id(value, annotation_layer_ids, 
AnnotationLayer)
+            if layer_id is not None:
+                annotation["value"] = layer_id
+        elif source_type in ("table", "line"):
+            ref_chart_id = _resolve_uuid_to_id(value, chart_ids, Slice)
+            if ref_chart_id is not None:
+                annotation["value"] = ref_chart_id

Review Comment:
   **Suggestion:** Unresolved query-context annotation references are left 
unchanged. When a referenced layer or chart is not present in the destination, 
its exported UUID remains in a field that expects a local integer ID, causing 
the imported chart to use an invalid annotation reference instead of dropping 
or otherwise handling it consistently with `filter_chart_annotations`. 
[possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Imported query contexts retain unusable annotation references.
   - ⚠️ Queries using missing chart or layer annotations may fail or render 
incorrectly.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Import a chart export through `ImportChartsCommand._import()` at
   `superset/commands/chart/importers/v1/__init__.py:83` where `query_context` 
contains an
   annotation-layer reference and the corresponding annotation layer or chart 
is absent from
   the import package and destination database.
   
   2. `import_chart()` at `superset/commands/chart/importers/v1/utils.py:261` 
calls
   `_resolve_query_context_annotations()` at line 325.
   
   3. `_resolve_annotation_list()` at lines 131-142 attempts to resolve the 
UUID through
   `_resolve_uuid_to_id()` at line 140; that lookup returns `None` when the 
referenced
   `Slice` is unavailable.
   
   4. Because there is no `else` branch removing the annotation, its exported 
UUID remains in
   `query_context`, which is serialized at line 167 and persisted with the 
chart even though
   downstream annotation handling expects a local integer ID.
   ```
   </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=8fff8cb8224c4e40be6bddffa84a2f86&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=8fff8cb8224c4e40be6bddffa84a2f86&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/importers/v1/utils.py
   **Line:** 131:142
   **Comment:**
        *Possible Bug: Unresolved query-context annotation references are left 
unchanged. When a referenced layer or chart is not present in the destination, 
its exported UUID remains in a field that expects a local integer ID, causing 
the imported chart to use an invalid annotation reference instead of dropping 
or otherwise handling it consistently with `filter_chart_annotations`.
   
   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=1b3b8d5791aa36eba5a47a6dad0381de7d691312eb98c6ad05234ee2e743fbb6&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39857&comment_hash=1b3b8d5791aa36eba5a47a6dad0381de7d691312eb98c6ad05234ee2e743fbb6&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