aminghadersohi commented on code in PR #43232:
URL: https://github.com/apache/superset/pull/43232#discussion_r4150392725


##########
tests/integration_tests/charts/commands_tests.py:
##########
@@ -942,3 +950,1509 @@ 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)
+
+
+def _create_chart_annotation_layer(name, descr=None):
+    layer = AnnotationLayer(name=name, descr=descr)
+    db.session.add(layer)
+    db.session.commit()
+    return layer
+
+
+def _create_chart_annotation(
+    layer,
+    short_descr,
+    long_descr=None,
+    json_metadata=None,
+):
+    annotation = Annotation(
+        layer=layer,
+        short_descr=short_descr,
+        long_descr=long_descr,
+        json_metadata=json_metadata,
+    )
+    db.session.add(annotation)
+    db.session.commit()
+    return annotation
+
+
+def _delete_chart_annotation_layer(layer):
+    db.session.query(Annotation).filter(Annotation.layer_id == 
layer.id).delete()
+    db.session.delete(layer)
+    db.session.commit()
+
+
+def _create_chart_dependency(source_chart, slice_name):
+    chart = Slice(
+        slice_name=slice_name,
+        viz_type=source_chart.viz_type,
+        datasource_id=source_chart.datasource_id,
+        datasource_type=source_chart.datasource_type,
+        params=source_chart.params,
+        query_context=source_chart.query_context,
+        cache_timeout=source_chart.cache_timeout,
+    )
+    db.session.add(chart)
+    db.session.commit()
+    return chart
+
+
+def _delete_chart_dependency(chart):
+    db.session.delete(chart)
+    db.session.commit()
+
+
+def _annotation_layer_import_config(layer_uuid, name, annotations, descr=None):
+    return {
+        "name": name,
+        "descr": descr,
+        "uuid": layer_uuid,
+        "version": "1.0.0",
+        "annotation": annotations,
+    }
+
+
+def _chart_import_config(chart_uuid, slice_name):
+    config = deepcopy(chart_config)
+    config["uuid"] = chart_uuid
+    config["slice_name"] = slice_name
+    return config
+
+
+def _chart_using_source(chart_uuid: str, source_chart_uuid: str) -> dict[str, 
Any]:
+    """Chart config with a table annotation sourced from another chart."""
+    config = _chart_import_config(chart_uuid, "Uses Source")
+    config["params"]["annotation_layers"] = [
+        {
+            "name": "Source",
+            "annotationType": "EVENT",
+            "sourceType": "table",
+            "value": source_chart_uuid,
+        }
+    ]
+    return config
+
+
+def _source_chart_bundle(
+    source_chart: dict[str, Any],
+    main_chart: dict[str, Any] | None = None,
+    selected: list[str] | None = None,
+) -> dict[str, str]:
+    """Chart bundle with a source chart, an optional user of it and 
selection."""
+    contents = {
+        "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+        "databases/imported_database.yaml": yaml.safe_dump(database_config),
+        "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+        "charts/source_chart.yaml": yaml.safe_dump(source_chart),
+    }
+    if main_chart is not None:
+        contents["charts/main_chart.yaml"] = yaml.safe_dump(main_chart)
+    if selected is not None:
+        contents[SELECTED_CHARTS_FILE_NAME] = yaml.safe_dump({"chart_uuids": 
selected})
+    return contents
+
+
+def _cleanup_imported_chart_bundle(chart_uuids, layer_uuids):
+    for chart_uuid in chart_uuids:
+        chart = 
db.session.query(Slice).filter_by(uuid=chart_uuid).one_or_none()
+        if chart:
+            db.session.delete(chart)
+    for layer_uuid in layer_uuids:
+        layer = (
+            
db.session.query(AnnotationLayer).filter_by(uuid=layer_uuid).one_or_none()
+        )
+        if layer:
+            db.session.query(Annotation).filter(
+                Annotation.layer_id == layer.id
+            ).delete()
+            db.session.delete(layer)
+    dataset = (
+        
db.session.query(SqlaTable).filter_by(uuid=dataset_config["uuid"]).one_or_none()
+    )
+    if dataset:
+        db.session.delete(dataset)
+    database = (
+        
db.session.query(Database).filter_by(uuid=database_config["uuid"]).one_or_none()
+    )
+    if database:
+        db.session.delete(database)
+    db.session.commit()
+
+
+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_multiple_native_annotation_layers_with_children(self, mock_g):
+        """Export each referenced native layer once and preserve child 
annotations."""
+        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 "{}")
+        layer_one = _create_chart_annotation_layer(
+            name=f"Layer One {uuid4()}", descr="first layer"
+        )
+        layer_two = _create_chart_annotation_layer(
+            name=f"Layer Two {uuid4()}", descr="second layer"
+        )
+        unrelated_layer = _create_chart_annotation_layer(name=f"Unrelated 
{uuid4()}")
+        try:
+            _create_chart_annotation(
+                layer_one,
+                short_descr="layer-one-annotation",
+                long_descr="layer-one-long",
+                json_metadata='{"scope": "one"}',
+            )
+            _create_chart_annotation(
+                layer_two,
+                short_descr="layer-two-annotation",
+                long_descr="layer-two-long",
+                json_metadata='{"scope": "two"}',
+            )
+            chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Native One",
+                            "annotationType": "EVENT",
+                            "sourceType": "NATIVE",
+                            "value": layer_one.id,
+                            "show": True,
+                            "style": "solid",
+                        },
+                        {
+                            "name": "Native Two",
+                            "annotationType": "EVENT",
+                            "sourceType": "NATIVE",
+                            "value": layer_two.id,
+                            "show": False,
+                            "style": "dashed",
+                        },
+                    ],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+            )
+            exported_layers = chart_yaml["params"]["annotation_layers"]
+
+            assert [layer["value"] for layer in exported_layers] == [
+                str(layer_one.uuid),
+                str(layer_two.uuid),
+            ]
+            assert [layer["sourceType"] for layer in exported_layers] == [
+                "NATIVE",
+                "NATIVE",
+            ]
+            assert [layer["style"] for layer in exported_layers] == ["solid", 
"dashed"]
+            assert [layer["show"] for layer in exported_layers] == [True, 
False]
+
+            layer_payloads = {
+                payload["uuid"]: payload
+                for path, factory in contents.items()
+                if path.startswith("annotation_layers/")
+                for payload in [yaml.safe_load(factory())]
+            }
+            assert set(layer_payloads) == {str(layer_one.uuid), 
str(layer_two.uuid)}
+            assert layer_payloads[str(layer_one.uuid)]["annotation"][0][
+                "short_descr"
+            ] == ("layer-one-annotation")
+            assert layer_payloads[str(layer_two.uuid)]["annotation"][0][
+                "short_descr"
+            ] == ("layer-two-annotation")
+            assert str(unrelated_layer.uuid) not in layer_payloads
+        finally:
+            chart.params = json.dumps(original_params)
+            db.session.commit()
+            _delete_chart_annotation_layer(unrelated_layer)
+            _delete_chart_annotation_layer(layer_two)
+            _delete_chart_annotation_layer(layer_one)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def 
test_export_chart_duplicate_native_annotation_reference_deduplicates_files(
+        self, mock_g
+    ):
+        """Export a native layer referenced twice in one chart exactly once."""
+        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 "{}")
+        layer = _create_chart_annotation_layer(name=f"Duplicate {uuid4()}")
+        try:
+            chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Native One",
+                            "annotationType": "EVENT",
+                            "sourceType": "NATIVE",
+                            "value": layer.id,
+                        },
+                        {
+                            "name": "Native Two",
+                            "annotationType": "EVENT",
+                            "sourceType": "NATIVE",
+                            "value": layer.id,
+                        },
+                    ],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+            )
+            assert [
+                layer["value"] for layer in 
chart_yaml["params"]["annotation_layers"]
+            ] == [str(layer.uuid), str(layer.uuid)]
+            layer_paths = [
+                path for path in contents if 
path.startswith("annotation_layers/")
+            ]
+            assert len(layer_paths) == 1
+        finally:
+            chart.params = json.dumps(original_params)
+            db.session.commit()
+            _delete_chart_annotation_layer(layer)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_missing_native_annotation_reference_drops_reference(
+        self, mock_g
+    ):
+        """Drop a native annotation whose layer was deleted instead of 
failing."""
+        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 "{}")
+        missing_layer_id = 987654321
+        try:
+            chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Missing Native",
+                            "annotationType": "EVENT",
+                            "sourceType": "NATIVE",
+                            "value": missing_layer_id,
+                        }
+                    ],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+            )
+            assert chart_yaml["params"]["annotation_layers"] == []
+            assert not [
+                path for path in contents if 
path.startswith("annotation_layers/")
+            ]
+        finally:
+            chart.params = json.dumps(original_params)
+            db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_multiple_chart_annotation_references(self, mock_g):
+        """Export each referenced table or line annotation chart exactly 
once."""
+        mock_g.user = security_manager.find_user("admin")
+        main_chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(main_chart.params or "{}")
+        ref_table_chart = _create_chart_dependency(
+            main_chart,
+            slice_name=f"Reference Table {uuid4()}",
+        )
+        ref_line_chart = _create_chart_dependency(
+            main_chart,
+            slice_name=f"Reference Line {uuid4()}",
+        )
+        try:
+            main_chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Table Ref",
+                            "annotationType": "EVENT",
+                            "sourceType": "table",
+                            "value": ref_table_chart.id,
+                            "show": True,
+                        },
+                        {
+                            "name": "Line Ref",
+                            "annotationType": "TIME_SERIES",
+                            "sourceType": "line",
+                            "value": ref_line_chart.id,
+                            "show": False,
+                        },
+                    ],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([main_chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{main_chart.id}.yaml"]()
+            )
+            exported_layers = chart_yaml["params"]["annotation_layers"]
+            assert exported_layers[0]["sourceType"] == "table"
+            assert exported_layers[0]["value"] == str(ref_table_chart.uuid)
+            assert exported_layers[1]["sourceType"] == "line"
+            assert exported_layers[1]["value"] == str(ref_line_chart.uuid)
+            ref_table_chart_path = (
+                "charts/"
+                f"{ref_table_chart.slice_name.replace(' ', '_')}"
+                f"_{ref_table_chart.id}.yaml"
+            )
+            assert ref_table_chart_path in contents
+            ref_line_chart_path = (
+                "charts/"
+                f"{ref_line_chart.slice_name.replace(' ', '_')}"
+                f"_{ref_line_chart.id}.yaml"
+            )
+            assert ref_line_chart_path in contents
+            assert not [
+                path for path in contents if 
path.startswith("annotation_layers/")
+            ]
+        finally:
+            main_chart.params = json.dumps(original_params)
+            db.session.commit()
+            _delete_chart_dependency(ref_line_chart)
+            _delete_chart_dependency(ref_table_chart)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_missing_chart_annotation_reference_drops_reference(
+        self, mock_g
+    ):
+        """Drop a table/line annotation whose source chart is missing."""
+        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 "{}")
+        missing_chart_id = 987654321
+        try:
+            chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Missing Table Ref",
+                            "annotationType": "EVENT",
+                            "sourceType": "table",
+                            "value": missing_chart_id,
+                        }
+                    ],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+            )
+            assert chart_yaml["params"]["annotation_layers"] == []
+        finally:
+            chart.params = json.dumps(original_params)
+            db.session.commit()
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def 
test_export_chart_annotation_references_consistent_in_params_and_query_context(
+        self, mock_g
+    ):
+        """Resolve same annotation refs in params and query context."""
+        mock_g.user = security_manager.find_user("admin")
+        main_chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = json.loads(main_chart.params or "{}")
+        original_query_context = main_chart.query_context
+        native_layer = _create_chart_annotation_layer(
+            name=f"Native Query {uuid4()}", descr="query layer"
+        )
+        ref_chart = _create_chart_dependency(
+            main_chart, slice_name=f"Query Ref {uuid4()}"
+        )
+        try:
+            _create_chart_annotation(
+                native_layer,
+                short_descr="query-child",
+                long_descr="query-child-long",
+                json_metadata='{"color": "blue"}',
+            )
+            annotations = [
+                {
+                    "name": "Native",
+                    "annotationType": "EVENT",
+                    "sourceType": "NATIVE",
+                    "value": native_layer.id,
+                    "show": True,
+                    "style": "solid",
+                },
+                {
+                    "name": "Table",
+                    "annotationType": "EVENT",
+                    "sourceType": "table",
+                    "value": ref_chart.id,
+                    "show": True,
+                    "style": "solid",
+                },
+                {
+                    "name": "Formula",
+                    "annotationType": "FORMULA",
+                    "sourceType": "FORMULA",
+                    "value": "cos(x)",
+                    "show": False,
+                    "style": "dashed",
+                },
+            ]
+            main_chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": deepcopy(annotations),
+                }
+            )
+            main_chart.query_context = json.dumps(
+                {
+                    "datasource": {"id": main_chart.datasource_id, "type": 
"table"},
+                    "queries": [{"annotation_layers": deepcopy(annotations)}],
+                    "form_data": {"annotation_layers": deepcopy(annotations)},
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([main_chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{main_chart.id}.yaml"]()
+            )
+            params_layers = chart_yaml["params"]["annotation_layers"]
+            query_context = json.loads(chart_yaml["query_context"])
+            query_layers = query_context["queries"][0]["annotation_layers"]
+            form_layers = query_context["form_data"]["annotation_layers"]
+
+            expected_values = [str(native_layer.uuid), str(ref_chart.uuid), 
"cos(x)"]
+            assert [layer["value"] for layer in params_layers] == 
expected_values
+            assert [layer["value"] for layer in query_layers] == 
expected_values
+            assert [layer["value"] for layer in form_layers] == expected_values
+
+            layer_payloads = [
+                yaml.safe_load(factory())
+                for path, factory in contents.items()
+                if path.startswith("annotation_layers/")
+            ]
+            assert len(layer_payloads) == 1
+            assert layer_payloads[0]["uuid"] == str(native_layer.uuid)
+            assert layer_payloads[0]["annotation"][0]["short_descr"] == 
"query-child"
+            assert layer_payloads[0]["annotation"][0]["json_metadata"] == {
+                "color": "blue"
+            }
+        finally:
+            main_chart.params = json.dumps(original_params)
+            main_chart.query_context = original_query_context
+            db.session.commit()
+            _delete_chart_dependency(ref_chart)
+            _delete_chart_annotation_layer(native_layer)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def 
test_export_chart_soft_deleted_chart_annotation_reference_drops_reference(
+        self, mock_g
+    ) -> None:
+        """Drop a table/line annotation whose source chart is not visible."""
+        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 "{}")
+        ref_chart = _create_chart_dependency(chart, slice_name=f"Deleted Ref 
{uuid4()}")
+        try:
+            chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Deleted Table Ref",
+                            "annotationType": "EVENT",
+                            "sourceType": "table",
+                            "value": ref_chart.id,
+                        }
+                    ],
+                }
+            )
+            ref_chart.soft_delete()
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+            )
+            assert chart_yaml["params"]["annotation_layers"] == []
+            assert [path for path in contents if path.startswith("charts/")] 
== [
+                f"charts/Energy_Sankey_{chart.id}.yaml"
+            ]
+        finally:
+            chart.params = json.dumps(original_params)
+            ref_chart.restore()
+            db.session.commit()
+            _delete_chart_dependency(ref_chart)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_query_context_only_annotation_references_are_bundled(
+        self, mock_g
+    ) -> None:
+        """Bundle dependencies referenced only from query_context."""
+        mock_g.user = security_manager.find_user("admin")
+        main_chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = main_chart.params
+        original_query_context = main_chart.query_context
+        native_layer = _create_chart_annotation_layer(name=f"QC Only 
{uuid4()}")
+        ref_chart = _create_chart_dependency(
+            main_chart, slice_name=f"QC Only Ref {uuid4()}"
+        )
+        try:
+            annotations = [
+                {
+                    "name": "Native",
+                    "annotationType": "EVENT",
+                    "sourceType": "NATIVE",
+                    "value": native_layer.id,
+                },
+                {
+                    "name": "Table",
+                    "annotationType": "EVENT",
+                    "sourceType": "table",
+                    "value": ref_chart.id,
+                },
+            ]
+            main_chart.params = json.dumps(
+                {**json.loads(original_params or "{}"), "annotation_layers": 
[]}
+            )
+            main_chart.query_context = json.dumps(
+                {
+                    "datasource": {"id": main_chart.datasource_id, "type": 
"table"},
+                    "queries": [{"annotation_layers": deepcopy(annotations)}],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([main_chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{main_chart.id}.yaml"]()
+            )
+            query_layers = 
json.loads(chart_yaml["query_context"])["queries"][0][
+                "annotation_layers"
+            ]
+            assert [layer["value"] for layer in query_layers] == [
+                str(native_layer.uuid),
+                str(ref_chart.uuid),
+            ]
+            assert (
+                len(
+                    [path for path in contents if 
path.startswith("annotation_layers/")]
+                )
+                == 1
+            )
+            ref_chart_path = (
+                f"charts/{ref_chart.slice_name.replace(' ', 
'_')}_{ref_chart.id}.yaml"
+            )
+            assert ref_chart_path in contents
+        finally:
+            main_chart.params = original_params
+            main_chart.query_context = original_query_context
+            db.session.commit()
+            _delete_chart_dependency(ref_chart)
+            _delete_chart_annotation_layer(native_layer)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_native_annotations_require_annotation_read(
+        self, mock_g
+    ) -> None:
+        """Leave native layers out of the export without can_read on 
Annotation."""
+        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 "{}")
+        layer = _create_chart_annotation_layer(name=f"Unreadable {uuid4()}")
+        _create_chart_annotation(layer, short_descr="secret")
+        can_access = security_manager.can_access
+
+        def can_access_without_annotation_read(permission: str, view: str) -> 
bool:
+            if (permission, view) == ("can_read", "Annotation"):
+                return False
+            return can_access(permission, view)
+
+        try:
+            chart.params = json.dumps(
+                {
+                    **original_params,
+                    "annotation_layers": [
+                        {
+                            "name": "Native",
+                            "annotationType": "EVENT",
+                            "sourceType": "NATIVE",
+                            "value": layer.id,
+                        },
+                        {
+                            "name": "Formula",
+                            "annotationType": "FORMULA",
+                            "sourceType": "",
+                            "value": "x",
+                        },
+                    ],
+                }
+            )
+            db.session.commit()
+
+            with patch.object(
+                security_manager,
+                "can_access",
+                side_effect=can_access_without_annotation_read,
+            ):
+                contents = dict(ExportChartsCommand([chart.id]).run())
+                chart_yaml = yaml.safe_load(
+                    contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+                )
+                with pytest.raises(AnnotationLayerNotFoundError):
+                    list(ExportAnnotationLayersCommand([layer.id]).run())
+
+            assert [
+                annotation["name"]
+                for annotation in chart_yaml["params"]["annotation_layers"]
+            ] == ["Formula"]
+            assert not [
+                path for path in contents if 
path.startswith("annotation_layers/")
+            ]
+        finally:
+            chart.params = json.dumps(original_params)
+            db.session.commit()
+            _delete_chart_annotation_layer(layer)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def test_export_chart_tags_include_annotation_source_charts(self, mock_g) 
-> None:
+        """tags.yaml carries the tags of charts exported as annotation 
sources."""
+        mock_g.user = security_manager.find_user("admin")
+        main_chart = db.session.query(Slice).filter_by(slice_name="Energy 
Sankey").one()
+        original_params = main_chart.params
+        ref_chart = _create_chart_dependency(
+            main_chart, slice_name=f"Tagged Ref {uuid4()}"
+        )
+        tag = Tag(
+            name=f"source-tag-{uuid4().hex[:8]}",
+            type=TagType.custom,
+            description="tag on the source chart",
+        )
+        db.session.add(tag)
+        db.session.flush()
+        db.session.add(
+            TaggedObject(
+                tag_id=tag.id, object_id=ref_chart.id, 
object_type=ObjectType.chart
+            )
+        )
+        try:
+            main_chart.params = json.dumps(
+                {
+                    **json.loads(original_params or "{}"),
+                    "annotation_layers": [
+                        {
+                            "name": "Table",
+                            "annotationType": "EVENT",
+                            "sourceType": "table",
+                            "value": ref_chart.id,
+                        }
+                    ],
+                }
+            )
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([main_chart.id]).run())
+            tags = yaml.safe_load(contents["tags.yaml"]())["tags"]
+            assert {"tag_name": tag.name, "description": tag.description} in 
tags
+            assert yaml.safe_load(contents[SELECTED_CHARTS_FILE_NAME]()) == {
+                "chart_uuids": [str(main_chart.uuid)]
+            }
+        finally:
+            main_chart.params = original_params
+            db.session.query(TaggedObject).filter_by(tag_id=tag.id).delete()
+            db.session.delete(tag)
+            db.session.commit()
+            _delete_chart_dependency(ref_chart)
+
+    @patch("superset.security.manager.g")
+    @pytest.mark.usefixtures("load_energy_table_with_slice")
+    def 
test_export_chart_without_annotation_layers_adds_no_annotation_dependencies(
+        self, mock_g
+    ):
+        """Export charts without annotation layers without adding layer 
artifacts."""
+        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_query_context = chart.query_context
+        try:
+            params_without_annotations = deepcopy(original_params)
+            params_without_annotations.pop("annotation_layers", None)
+            chart.params = json.dumps(params_without_annotations)
+            query_context = json.loads(chart.query_context or "{}")
+            for query in query_context.get("queries", []):
+                query["annotation_layers"] = []
+            query_context.setdefault("form_data", {})["annotation_layers"] = []
+            chart.query_context = json.dumps(query_context)
+            db.session.commit()
+
+            contents = dict(ExportChartsCommand([chart.id]).run())
+            chart_yaml = yaml.safe_load(
+                contents[f"charts/Energy_Sankey_{chart.id}.yaml"]()
+            )
+            assert chart_yaml.get("params", {}).get("annotation_layers", []) 
== []
+            assert not [
+                path for path in contents if 
path.startswith("annotation_layers/")
+            ]
+        finally:
+            chart.params = json.dumps(original_params)
+            chart.query_context = original_query_context
+            db.session.commit()
+
+
+class TestImportChartsAnnotationLayers(SupersetTestCase):
+    """Tests for annotation layer handling during chart import."""
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_multiple_native_annotation_layers_with_children(
+        self, mock_add_permissions, sm_g, utils_g
+    ):
+        """Import multiple native layers with child annotation linkage."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        main_chart_uuid = str(uuid4())
+        layer_one_uuid = str(uuid4())
+        layer_two_uuid = str(uuid4())
+        main_chart_config = _chart_import_config(
+            main_chart_uuid, "Chart With Native Layers"
+        )
+        main_chart_config["params"]["annotation_layers"] = [
+            {
+                "name": "Native One",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": layer_one_uuid,
+                "show": True,
+                "style": "solid",
+            },
+            {
+                "name": "Native Two",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": layer_two_uuid,
+                "show": False,
+                "style": "dashed",
+            },
+        ]
+
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/main_chart.yaml": yaml.safe_dump(main_chart_config),
+            "annotation_layers/layer_one.yaml": yaml.safe_dump(
+                _annotation_layer_import_config(
+                    layer_one_uuid,
+                    "Layer One",
+                    [
+                        {
+                            "uuid": str(uuid4()),
+                            "short_descr": "one-a",
+                            "long_descr": "layer one annotation",
+                            "json_metadata": {"layer": 1},
+                        },
+                        {
+                            "uuid": str(uuid4()),
+                            "short_descr": "one-b",
+                            "long_descr": "layer one annotation two",
+                            "json_metadata": {"layer": 1, "rank": 2},
+                        },
+                    ],
+                    descr="layer one descr",
+                )
+            ),
+            "annotation_layers/layer_two.yaml": yaml.safe_dump(
+                _annotation_layer_import_config(
+                    layer_two_uuid,
+                    "Layer Two",
+                    [
+                        {
+                            "uuid": str(uuid4()),
+                            "short_descr": "two-a",
+                            "long_descr": "layer two annotation",
+                            "json_metadata": {"layer": 2},
+                        }
+                    ],
+                    descr="layer two descr",
+                )
+            ),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            chart = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            layer_one = (
+                
db.session.query(AnnotationLayer).filter_by(uuid=layer_one_uuid).one()
+            )
+            layer_two = (
+                
db.session.query(AnnotationLayer).filter_by(uuid=layer_two_uuid).one()
+            )
+            params_layers = json.loads(chart.params)["annotation_layers"]
+
+            assert [layer["value"] for layer in params_layers] == [
+                layer_one.id,
+                layer_two.id,
+            ]
+            assert [layer["style"] for layer in params_layers] == ["solid", 
"dashed"]
+            assert [layer["show"] for layer in params_layers] == [True, False]
+
+            layer_one_annotations = (
+                
db.session.query(Annotation).filter_by(layer_id=layer_one.id).all()
+            )
+            layer_two_annotations = (
+                
db.session.query(Annotation).filter_by(layer_id=layer_two.id).all()
+            )
+            assert {annotation.short_descr for annotation in 
layer_one_annotations} == {
+                "one-a",
+                "one-b",
+            }
+            assert {annotation.short_descr for annotation in 
layer_two_annotations} == {
+                "two-a"
+            }
+            assert all(
+                annotation.layer_id == layer_one.id
+                for annotation in layer_one_annotations
+            )
+            assert all(
+                annotation.layer_id == layer_two.id
+                for annotation in layer_two_annotations
+            )
+        finally:
+            _cleanup_imported_chart_bundle(
+                [main_chart_uuid], [layer_one_uuid, layer_two_uuid]
+            )
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_mixed_annotation_dependency_graph(
+        self, mock_add_permissions, sm_g, utils_g
+    ):
+        """Resolve mixed annotation source types across params and query 
context."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        native_layer_uuid = str(uuid4())
+        ref_table_uuid = str(uuid4())
+        ref_line_uuid = str(uuid4())
+        main_chart_uuid = str(uuid4())
+        ref_table_chart = _chart_import_config(ref_table_uuid, "Ref Table 
Chart")
+        ref_line_chart = _chart_import_config(ref_line_uuid, "Ref Line Chart")
+        main_chart = _chart_import_config(main_chart_uuid, "Main Mixed Chart")
+
+        annotations = [
+            {
+                "name": "Native",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": native_layer_uuid,
+                "show": True,
+                "style": "solid",
+            },
+            {
+                "name": "Table",
+                "annotationType": "EVENT",
+                "sourceType": "table",
+                "value": ref_table_uuid,
+                "show": False,
+                "style": "solid",
+            },
+            {
+                "name": "Line",
+                "annotationType": "TIME_SERIES",
+                "sourceType": "line",
+                "value": ref_line_uuid,
+                "show": True,
+                "style": "dashed",
+            },
+            {
+                "name": "Formula",
+                "annotationType": "FORMULA",
+                "sourceType": "FORMULA",
+                "value": "cos(x)",
+                "show": True,
+                "style": "solid",
+            },
+        ]
+        main_chart["params"]["annotation_layers"] = deepcopy(annotations)
+        main_chart["query_context"] = json.dumps(
+            {
+                "datasource": {"id": 12, "type": "table"},
+                "queries": [{"annotation_layers": deepcopy(annotations)}],
+                "form_data": {"annotation_layers": deepcopy(annotations)},
+            }
+        )
+
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/ref_table.yaml": yaml.safe_dump(ref_table_chart),
+            "charts/ref_line.yaml": yaml.safe_dump(ref_line_chart),
+            "charts/main_chart.yaml": yaml.safe_dump(main_chart),
+            "annotation_layers/native_layer.yaml": yaml.safe_dump(
+                _annotation_layer_import_config(
+                    native_layer_uuid,
+                    "Imported Native Layer",
+                    [
+                        {
+                            "uuid": str(uuid4()),
+                            "short_descr": "native-child",
+                            "long_descr": "native child annotation",
+                            "json_metadata": {"kind": "native"},
+                        }
+                    ],
+                )
+            ),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            imported_main_chart = (
+                db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            )
+            imported_table_chart = (
+                db.session.query(Slice).filter_by(uuid=ref_table_uuid).one()
+            )
+            imported_line_chart = (
+                db.session.query(Slice).filter_by(uuid=ref_line_uuid).one()
+            )
+            imported_native_layer = (
+                db.session.query(AnnotationLayer)
+                .filter_by(uuid=native_layer_uuid)
+                .one()
+            )
+
+            params_layers = 
json.loads(imported_main_chart.params)["annotation_layers"]
+            assert [layer["sourceType"] for layer in params_layers] == [
+                "NATIVE",
+                "table",
+                "line",
+                "FORMULA",
+            ]
+            assert [layer["value"] for layer in params_layers] == [
+                imported_native_layer.id,
+                imported_table_chart.id,
+                imported_line_chart.id,
+                "cos(x)",
+            ]
+            assert [layer["show"] for layer in params_layers] == [
+                True,
+                False,
+                True,
+                True,
+            ]
+            assert [layer["style"] for layer in params_layers] == [
+                "solid",
+                "solid",
+                "dashed",
+                "solid",
+            ]
+
+            query_context = json.loads(imported_main_chart.query_context)
+            assert [
+                layer["value"]
+                for layer in query_context["queries"][0]["annotation_layers"]
+            ] == [
+                imported_native_layer.id,
+                imported_table_chart.id,
+                imported_line_chart.id,
+                "cos(x)",
+            ]
+            assert [
+                layer["value"]
+                for layer in query_context["form_data"]["annotation_layers"]
+            ] == [
+                imported_native_layer.id,
+                imported_table_chart.id,
+                imported_line_chart.id,
+                "cos(x)",
+            ]
+        finally:
+            _cleanup_imported_chart_bundle(
+                [main_chart_uuid, ref_table_uuid, ref_line_uuid],
+                [native_layer_uuid],
+            )
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_missing_annotation_layer_dependency_drops_reference(
+        self, mock_add_permissions, sm_g, utils_g
+    ):
+        """Drop unresolved native layer UUID refs when dependency is 
missing."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        main_chart_uuid = str(uuid4())
+        missing_layer_uuid = str(uuid4())
+        main_chart = _chart_import_config(main_chart_uuid, "Missing Native 
Layer")
+        annotations = [
+            {
+                "name": "Missing Native",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": missing_layer_uuid,
+                "show": True,
+            }
+        ]
+        main_chart["params"]["annotation_layers"] = deepcopy(annotations)
+        main_chart["query_context"] = json.dumps(
+            {
+                "datasource": {"id": 12, "type": "table"},
+                "queries": [{"annotation_layers": deepcopy(annotations)}],
+                "form_data": {"annotation_layers": deepcopy(annotations)},
+            }
+        )
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/main_chart.yaml": yaml.safe_dump(main_chart),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            chart = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            assert json.loads(chart.params)["annotation_layers"] == []
+            query_context = json.loads(chart.query_context)
+            assert query_context["queries"][0]["annotation_layers"] == []
+            assert query_context["form_data"]["annotation_layers"] == []
+        finally:
+            _cleanup_imported_chart_bundle([main_chart_uuid], [])
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def 
test_import_chart_invalid_annotation_layer_uuid_drops_only_invalid_reference(
+        self, mock_add_permissions, sm_g, utils_g
+    ):
+        """Drop invalid native UUID refs and keep formula annotations."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        main_chart_uuid = str(uuid4())
+        main_chart = _chart_import_config(main_chart_uuid, "Invalid Native 
UUID")
+        annotations = [
+            {
+                "name": "Invalid Native",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": "not-a-uuid",
+                "show": True,
+            },
+            {
+                "name": "Formula",
+                "annotationType": "FORMULA",
+                "sourceType": "FORMULA",
+                "value": "sin(x)",
+                "show": False,
+            },
+        ]
+        main_chart["params"]["annotation_layers"] = [
+            *deepcopy(annotations),
+        ]
+        main_chart["query_context"] = json.dumps(
+            {
+                "datasource": {"id": 12, "type": "table"},
+                "queries": [{"annotation_layers": deepcopy(annotations)}],
+                "form_data": {"annotation_layers": deepcopy(annotations)},
+            }
+        )
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/main_chart.yaml": yaml.safe_dump(main_chart),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            chart = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            params_layers = json.loads(chart.params)["annotation_layers"]
+            assert len(params_layers) == 1
+            assert params_layers[0]["annotationType"] == "FORMULA"
+            assert params_layers[0]["value"] == "sin(x)"
+
+            query_context = json.loads(chart.query_context)
+            query_layers = query_context["queries"][0]["annotation_layers"]
+            form_layers = query_context["form_data"]["annotation_layers"]
+            assert [layer["value"] for layer in query_layers] == ["sin(x)"]
+            assert [layer["value"] for layer in form_layers] == ["sin(x)"]
+        finally:
+            _cleanup_imported_chart_bundle([main_chart_uuid], [])
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def 
test_import_chart_existing_annotation_layer_dependency_overwrite_reuses_layer(
+        self, mock_add_permissions, sm_g, utils_g
+    ):
+        """Reuse and overwrite existing annotation layer on chart import."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        existing_layer_uuid = str(uuid4())
+        main_chart_uuid = str(uuid4())
+        existing_layer = _create_chart_annotation_layer(
+            name="existing-layer",
+            descr="before overwrite",
+        )
+        existing_layer.uuid = existing_layer_uuid
+        db.session.commit()
+        existing_layer_id = existing_layer.id
+        _create_chart_annotation(existing_layer, short_descr="stale-child")
+
+        main_chart = _chart_import_config(
+            main_chart_uuid, "Overwrite Native Layer Chart"
+        )
+        main_chart["params"]["annotation_layers"] = [
+            {
+                "name": "Native",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": existing_layer_uuid,
+                "show": True,
+                "style": "solid",
+            }
+        ]
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/main_chart.yaml": yaml.safe_dump(main_chart),
+            "annotation_layers/native_layer.yaml": yaml.safe_dump(
+                _annotation_layer_import_config(
+                    existing_layer_uuid,
+                    "existing-layer-updated",
+                    [
+                        {
+                            "uuid": str(uuid4()),
+                            "short_descr": "fresh-child",
+                            "long_descr": "new annotation",
+                            "json_metadata": {"fresh": True},
+                        }
+                    ],
+                    descr="after overwrite",
+                )
+            ),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            chart = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            layer = (
+                db.session.query(AnnotationLayer)
+                .filter_by(uuid=existing_layer_uuid)
+                .one()
+            )
+            params_layers = json.loads(chart.params)["annotation_layers"]
+            annotations = (
+                db.session.query(Annotation).filter_by(layer_id=layer.id).all()
+            )
+
+            assert layer.id == existing_layer_id
+            assert layer.name == "existing-layer-updated"
+            assert layer.descr == "after overwrite"
+            assert params_layers[0]["value"] == layer.id
+            assert len(annotations) == 1
+            assert annotations[0].short_descr == "fresh-child"
+        finally:
+            _cleanup_imported_chart_bundle([main_chart_uuid], 
[existing_layer_uuid])
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_circular_chart_annotation_references_keep_both_sides(
+        self, mock_add_permissions, sm_g, utils_g
+    ) -> None:
+        """Charts referencing each other both keep their annotation 
references."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        chart_a_uuid = str(uuid4())
+        chart_b_uuid = str(uuid4())
+        chart_a = _chart_import_config(chart_a_uuid, "Cycle Chart A")
+        chart_b = _chart_import_config(chart_b_uuid, "Cycle Chart B")
+        for config, target_uuid in ((chart_a, chart_b_uuid), (chart_b, 
chart_a_uuid)):
+            annotations = [
+                {
+                    "name": "Cycle Ref",
+                    "annotationType": "TIME_SERIES",
+                    "sourceType": "line",
+                    "value": target_uuid,
+                }
+            ]
+            config["params"]["annotation_layers"] = deepcopy(annotations)
+            config["query_context"] = json.dumps(
+                {
+                    "datasource": {"id": 12, "type": "table"},
+                    "queries": [{"annotation_layers": deepcopy(annotations)}],
+                    "form_data": {"annotation_layers": deepcopy(annotations)},
+                }
+            )
+
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/chart_a.yaml": yaml.safe_dump(chart_a),
+            "charts/chart_b.yaml": yaml.safe_dump(chart_b),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            imported_a = 
db.session.query(Slice).filter_by(uuid=chart_a_uuid).one()
+            imported_b = 
db.session.query(Slice).filter_by(uuid=chart_b_uuid).one()
+            for chart, expected_id in (
+                (imported_a, imported_b.id),
+                (imported_b, imported_a.id),
+            ):
+                params_layers = json.loads(chart.params)["annotation_layers"]
+                query_context = json.loads(chart.query_context)
+                assert [layer["value"] for layer in params_layers] == 
[expected_id]
+                assert [
+                    layer["value"]
+                    for layer in 
query_context["queries"][0]["annotation_layers"]
+                ] == [expected_id]
+                assert [
+                    layer["value"]
+                    for layer in 
query_context["form_data"]["annotation_layers"]
+                ] == [expected_id]
+        finally:
+            _cleanup_imported_chart_bundle([chart_a_uuid, chart_b_uuid], [])
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_legacy_integer_annotation_references_are_dropped(
+        self, mock_add_permissions, sm_g, utils_g
+    ) -> None:
+        """Integer IDs from pre-UUID bundles are not bound to local rows."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        local_layer = _create_chart_annotation_layer(name=f"Local {uuid4()}")
+        main_chart_uuid = str(uuid4())
+        main_chart = _chart_import_config(main_chart_uuid, "Legacy Int Chart")
+        annotations = [
+            {
+                "name": "Legacy Native",
+                "annotationType": "EVENT",
+                "sourceType": "NATIVE",
+                "value": local_layer.id,
+            },
+            {
+                "name": "Legacy Table",
+                "annotationType": "EVENT",
+                "sourceType": "table",
+                "value": 1,
+            },
+            {
+                "name": "Formula",
+                "annotationType": "FORMULA",
+                "sourceType": "",
+                "value": "x",
+            },
+        ]
+        main_chart["params"]["annotation_layers"] = deepcopy(annotations)
+        main_chart["query_context"] = json.dumps(
+            {
+                "datasource": {"id": 12, "type": "table"},
+                "queries": [{"annotation_layers": deepcopy(annotations)}],
+                "form_data": {"annotation_layers": deepcopy(annotations)},
+            }
+        )
+        contents = {
+            "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+            "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+            "datasets/imported_dataset.yaml": yaml.safe_dump(dataset_config),
+            "charts/main_chart.yaml": yaml.safe_dump(main_chart),
+        }
+
+        try:
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            chart = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            query_context = json.loads(chart.query_context)
+            for annotation_layers in (
+                json.loads(chart.params)["annotation_layers"],
+                query_context["queries"][0]["annotation_layers"],
+                query_context["form_data"]["annotation_layers"],
+            ):
+                assert [layer["name"] for layer in annotation_layers] == 
["Formula"]
+        finally:
+            _cleanup_imported_chart_bundle([main_chart_uuid], [])
+            _delete_chart_annotation_layer(local_layer)
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_reuses_existing_annotation_source_chart(
+        self, mock_add_permissions, sm_g, utils_g
+    ) -> None:
+        """An existing source chart is reused unchanged, tags included."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        source_chart_uuid = str(uuid4())
+        main_chart_uuid = str(uuid4())
+        tag = None
+        try:
+            ImportChartsCommand(
+                _source_chart_bundle(
+                    _chart_import_config(source_chart_uuid, "Existing Source")
+                ),
+                overwrite=True,
+            ).run()
+            existing_source = (
+                db.session.query(Slice).filter_by(uuid=source_chart_uuid).one()
+            )
+            tag = Tag(name=f"local-tag-{uuid4().hex[:8]}", type=TagType.custom)
+            db.session.add(tag)
+            db.session.flush()
+            db.session.add(
+                TaggedObject(
+                    tag_id=tag.id,
+                    object_id=existing_source.id,
+                    object_type=ObjectType.chart,
+                )
+            )
+            db.session.commit()
+
+            changed_source = _chart_import_config(source_chart_uuid, "Changed 
Source")
+            changed_source["tags"] = []
+            main_chart = _chart_using_source(main_chart_uuid, 
source_chart_uuid)
+            contents = _source_chart_bundle(
+                changed_source, main_chart, selected=[main_chart_uuid]
+            )
+
+            # Alpha can write charts but isn't an editor of the admin's source.
+            sm_g.user = utils_g.user = security_manager.find_user("alpha")
+
+            # overwrite=False: the existing source isn't a conflict
+            ImportChartsCommand(contents, overwrite=False).run()
+
+            # overwrite=True: the source is reused instead of overwritten
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            db.session.expire_all()
+            source = 
db.session.query(Slice).filter_by(uuid=source_chart_uuid).one()
+            main = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            assert source.id == existing_source.id
+            assert source.slice_name == "Existing Source"
+            assert tag.name in {source_tag.name for source_tag in source.tags}
+            assert [
+                layer["value"] for layer in 
json.loads(main.params)["annotation_layers"]
+            ] == [source.id]
+        finally:
+            if tag is not None:
+                
db.session.query(TaggedObject).filter_by(tag_id=tag.id).delete()
+                db.session.delete(tag)
+                db.session.commit()
+            _cleanup_imported_chart_bundle([main_chart_uuid, 
source_chart_uuid], [])
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_overwrites_selected_annotation_source_chart(
+        self, mock_add_permissions, sm_g, utils_g
+    ) -> None:
+        """A source chart that was picked for the export is overwritten."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        source_chart_uuid = str(uuid4())
+        main_chart_uuid = str(uuid4())
+        try:
+            ImportChartsCommand(
+                _source_chart_bundle(
+                    _chart_import_config(source_chart_uuid, "Existing Source")
+                ),
+                overwrite=True,
+            ).run()
+
+            contents = _source_chart_bundle(
+                _chart_import_config(source_chart_uuid, "Changed Source"),
+                _chart_using_source(main_chart_uuid, source_chart_uuid),
+                selected=[main_chart_uuid, source_chart_uuid],
+            )
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            db.session.expire_all()
+            source = 
db.session.query(Slice).filter_by(uuid=source_chart_uuid).one()
+            assert source.slice_name == "Changed Source"
+        finally:
+            _cleanup_imported_chart_bundle([main_chart_uuid, 
source_chart_uuid], [])
+
+    @patch("superset.utils.core.g")
+    @patch("superset.security.manager.g")
+    @patch("superset.commands.database.importers.v1.utils.add_permissions")
+    def test_import_chart_gamma_overwrite_keeps_existing_annotation_layer(
+        self, mock_add_permissions, sm_g, utils_g
+    ) -> None:
+        """Gamma's overwrite chart import reuses a layer without changing 
it."""
+        sm_g.user = utils_g.user = security_manager.find_user("admin")
+        layer = _create_chart_annotation_layer(
+            name=f"Gamma Kept {uuid4()}", descr="original"
+        )
+        _create_chart_annotation(layer, short_descr="original-child")
+        layer_uuid = str(layer.uuid)
+        seed_chart_uuid = str(uuid4())
+        main_chart_uuid = str(uuid4())
+        try:
+            # the database and dataset have to exist: Gamma can't create them
+            ImportChartsCommand(
+                _source_chart_bundle(_chart_import_config(seed_chart_uuid, 
"Seed")),
+                overwrite=True,
+            ).run()
+
+            main_chart = _chart_import_config(main_chart_uuid, "Gamma Chart")
+            main_chart["params"]["annotation_layers"] = [
+                {
+                    "name": "Native",
+                    "annotationType": "EVENT",
+                    "sourceType": "NATIVE",
+                    "value": layer_uuid,
+                }
+            ]
+            contents = {
+                "metadata.yaml": yaml.safe_dump(chart_metadata_config),
+                "databases/imported_database.yaml": 
yaml.safe_dump(database_config),
+                "datasets/imported_dataset.yaml": 
yaml.safe_dump(dataset_config),
+                "charts/main_chart.yaml": yaml.safe_dump(main_chart),
+                "annotation_layers/layer.yaml": yaml.safe_dump(
+                    _annotation_layer_import_config(
+                        layer_uuid, "renamed-by-gamma", [], descr="changed"
+                    )
+                ),
+            }
+
+            sm_g.user = utils_g.user = security_manager.find_user("gamma")
+            ImportChartsCommand(contents, overwrite=True).run()
+
+            db.session.expire_all()
+            reloaded = (
+                
db.session.query(AnnotationLayer).filter_by(uuid=layer_uuid).one()
+            )
+            children = 
db.session.query(Annotation).filter_by(layer_id=reloaded.id)
+            chart = 
db.session.query(Slice).filter_by(uuid=main_chart_uuid).one()
+            assert reloaded.name == layer.name

Review Comment:
   Nit: `layer` and `reloaded` are the same identity-mapped row, so after 
`expire_all()` this compares the DB value with itself; it stayed green with the 
layer renamed under the `ignore_permissions=True` mutant.
   
   ```suggestion
               assert reloaded.name.startswith("Gamma Kept ")
   ```



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