diff --git a/superset/commands/dashboard/importers/v1/utils.py b/superset/commands/dashboard/importers/v1/utils.py index 53904890c14..9d1f510a5dd 100644 --- a/superset/commands/dashboard/importers/v1/utils.py +++ b/superset/commands/dashboard/importers/v1/utils.py @@ -139,7 +139,55 @@ def update_id_refs( # pylint: disable=too-many-locals # noqa: C901 native_filter["scope"]["excluded"] = [ id_map[old_id] for old_id in scope_excluded if old_id in id_map ] + fixed = update_cross_filter_scoping(fixed, id_map) + return fixed + +def update_cross_filter_scoping( + config: dict[str, Any], id_map: dict[int, int] +) -> dict[str, Any]: + # fix cross filter references + fixed = config.copy() + + cross_filter_global_config = fixed.get("metadata", {}).get( + "global_chart_configuration", {} + ) + scope_excluded = cross_filter_global_config.get("scope", {}).get("excluded", []) + if scope_excluded: + cross_filter_global_config["scope"]["excluded"] = [ + id_map[old_id] for old_id in scope_excluded if old_id in id_map + ] + + if "chart_configuration" in (metadata := fixed.get("metadata", {})): + # Build remapped configuration in a single pass for clarity/readability. + new_chart_configuration: dict[str, Any] = {} + for old_id_str, chart_config in metadata["chart_configuration"].items(): + try: + old_id_int = int(old_id_str) + except (TypeError, ValueError): + continue + + new_id = id_map.get(old_id_int) + if new_id is None: + continue + + if isinstance(chart_config, dict): + chart_config["id"] = new_id + + # Update cross filter scope excluded ids + scope = chart_config.get("crossFilters", {}).get("scope", {}) + if isinstance(scope, dict): + excluded_scope = scope.get("excluded", []) + if excluded_scope: + chart_config["crossFilters"]["scope"]["excluded"] = [ + id_map[old_id] + for old_id in excluded_scope + if old_id in id_map + ] + + new_chart_configuration[str(new_id)] = chart_config + + metadata["chart_configuration"] = new_chart_configuration return fixed diff --git a/tests/unit_tests/dashboards/commands/importers/v1/utils_test.py b/tests/unit_tests/dashboards/commands/importers/v1/utils_test.py index 0e843629577..72bf490a5b0 100644 --- a/tests/unit_tests/dashboards/commands/importers/v1/utils_test.py +++ b/tests/unit_tests/dashboards/commands/importers/v1/utils_test.py @@ -121,3 +121,81 @@ def test_update_native_filter_config_scope_excluded(): }, "metadata": {"native_filter_configuration": [{"scope": {"excluded": [1, 2]}}]}, } + + +def test_update_id_refs_cross_filter_chart_configuration_key_and_excluded_mapping(): + from superset.commands.dashboard.importers.v1.utils import update_id_refs + + # Build a minimal dashboard position with uuids -> old ids + config: dict[str, Any] = { + "position": { + "CHART1": { + "id": "CHART1", + "meta": {"chartId": 101, "uuid": "uuid1"}, + "type": "CHART", + }, + "CHART2": { + "id": "CHART2", + "meta": {"chartId": 102, "uuid": "uuid2"}, + "type": "CHART", + }, + }, + "metadata": { + "chart_configuration": { + "101": { + "id": 101, + "crossFilters": {"scope": {"excluded": [102, 103]}}, + }, + "104": {"crossFilters": {"scope": {"excluded": [105]}}}, + }, + "global_chart_configuration": {"scope": {"excluded": [102, 999]}}, + }, + } + + chart_ids = {"uuid1": 1, "uuid2": 2} + dataset_info: dict[str, dict[str, Any]] = {} + + fixed = update_id_refs(config, chart_ids, dataset_info) + + metadata = fixed["metadata"] + # Expect top-level key remapped from "101" to "1" + assert "1" in metadata["chart_configuration"] + assert "101" not in metadata["chart_configuration"] + + chart_config = metadata["chart_configuration"]["1"] + # Expect inner id updated to new id + assert chart_config.get("id") == 1 + # Expect excluded list remapped and unknown ids dropped + assert chart_config["crossFilters"]["scope"]["excluded"] == [2] + + # Expect entries without id_map mapping to be dropped + assert "104" not in metadata["chart_configuration"] + + # Expect global scope excluded remapped too + assert metadata["global_chart_configuration"]["scope"]["excluded"] == [2] + + +def test_update_id_refs_cross_filter_handles_string_excluded(): + from superset.commands.dashboard.importers.v1.utils import update_id_refs + + config: dict[str, Any] = { + "position": { + "CHART1": { + "id": "CHART1", + "meta": {"chartId": 101, "uuid": "uuid1"}, + "type": "CHART", + }, + }, + "metadata": { + "chart_configuration": { + "101": {"crossFilters": {"scope": {"excluded": "all"}}} + } + }, + } + + chart_ids = {"uuid1": 1} + dataset_info: dict[str, dict[str, Any]] = {} + + fixed = update_id_refs(config, chart_ids, dataset_info) + # Should not raise and should remap key + assert "1" in fixed["metadata"]["chart_configuration"]