-
Notifications
You must be signed in to change notification settings - Fork 17.3k
fix(dashboard-import): dashboard import fails with keyerror when referencing deleted charts in expanded_slices metadata #37042
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,8 +19,9 @@ | |||||||||||||||||||||||||||||||||||||||
| from typing import Optional | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| from flask_babel import lazy_gettext as _ | ||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy import func | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| from superset import security_manager | ||||||||||||||||||||||||||||||||||||||||
| from superset import db, security_manager | ||||||||||||||||||||||||||||||||||||||||
| from superset.commands.base import BaseCommand | ||||||||||||||||||||||||||||||||||||||||
| from superset.commands.chart.exceptions import ( | ||||||||||||||||||||||||||||||||||||||||
| ChartDeleteFailedError, | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -31,7 +32,9 @@ | |||||||||||||||||||||||||||||||||||||||
| from superset.daos.chart import ChartDAO | ||||||||||||||||||||||||||||||||||||||||
| from superset.daos.report import ReportScheduleDAO | ||||||||||||||||||||||||||||||||||||||||
| from superset.exceptions import SupersetSecurityException | ||||||||||||||||||||||||||||||||||||||||
| from superset.models.dashboard import Dashboard | ||||||||||||||||||||||||||||||||||||||||
| from superset.models.slice import Slice | ||||||||||||||||||||||||||||||||||||||||
| from superset.utils import json | ||||||||||||||||||||||||||||||||||||||||
| from superset.utils.decorators import on_error, transaction | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -46,6 +49,9 @@ def __init__(self, model_ids: list[int]): | |||||||||||||||||||||||||||||||||||||||
| def run(self) -> None: | ||||||||||||||||||||||||||||||||||||||||
| self.validate() | ||||||||||||||||||||||||||||||||||||||||
| assert self._models | ||||||||||||||||||||||||||||||||||||||||
| # Clean up dashboard metadata before deleting charts | ||||||||||||||||||||||||||||||||||||||||
| for chart in self._models: | ||||||||||||||||||||||||||||||||||||||||
| self._cleanup_dashboard_metadata(chart.id) | ||||||||||||||||||||||||||||||||||||||||
| ChartDAO.delete(self._models) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def validate(self) -> None: | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -68,3 +74,140 @@ def validate(self) -> None: | |||||||||||||||||||||||||||||||||||||||
| security_manager.raise_for_ownership(model) | ||||||||||||||||||||||||||||||||||||||||
| except SupersetSecurityException as ex: | ||||||||||||||||||||||||||||||||||||||||
| raise ChartForbiddenError() from ex | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def _cleanup_dashboard_metadata( # noqa: C901 | ||||||||||||||||||||||||||||||||||||||||
| self, chart_id: int | ||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
| Remove references to this chart from all dashboard metadata. | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| When a chart is deleted, dashboards may still contain references to the | ||||||||||||||||||||||||||||||||||||||||
| chart ID in various metadata fields (expanded_slices, filter_scopes, etc.). | ||||||||||||||||||||||||||||||||||||||||
| This method cleans up those references to prevent issues during dashboard | ||||||||||||||||||||||||||||||||||||||||
| export/import. | ||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # First check how many dashboards contain this chart | ||||||||||||||||||||||||||||||||||||||||
| dashboard_count = ( | ||||||||||||||||||||||||||||||||||||||||
| db.session.query(func.count(Dashboard.id)) | ||||||||||||||||||||||||||||||||||||||||
| .filter(Dashboard.slices.any(id=chart_id)) # type: ignore[attr-defined] | ||||||||||||||||||||||||||||||||||||||||
| .scalar() | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if dashboard_count == 0: | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Log warning if cleaning up many dashboards | ||||||||||||||||||||||||||||||||||||||||
| if dashboard_count > 100: | ||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||
| "Chart %s is on %d dashboards. " | ||||||||||||||||||||||||||||||||||||||||
| "Cleaning up metadata may take some time.", | ||||||||||||||||||||||||||||||||||||||||
| chart_id, | ||||||||||||||||||||||||||||||||||||||||
| dashboard_count, | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Use a reasonable limit to prevent memory issues with extremely popular charts | ||||||||||||||||||||||||||||||||||||||||
| if dashboard_count > (safety_limit := 1000): | ||||||||||||||||||||||||||||||||||||||||
| logger.error( | ||||||||||||||||||||||||||||||||||||||||
| "Chart %s is on %d dashboards (exceeds safety limit of %d). " | ||||||||||||||||||||||||||||||||||||||||
| "Skipping metadata cleanup. " | ||||||||||||||||||||||||||||||||||||||||
| "Manual intervention may be required for export/import.", | ||||||||||||||||||||||||||||||||||||||||
| chart_id, | ||||||||||||||||||||||||||||||||||||||||
| dashboard_count, | ||||||||||||||||||||||||||||||||||||||||
| safety_limit, | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Query only ID and json_metadata (not full Dashboard objects) | ||||||||||||||||||||||||||||||||||||||||
| dashboards_to_update = ( | ||||||||||||||||||||||||||||||||||||||||
| db.session.query(Dashboard.id, Dashboard.json_metadata) | ||||||||||||||||||||||||||||||||||||||||
| .filter(Dashboard.slices.any(id=chart_id)) # type: ignore[attr-defined] | ||||||||||||||||||||||||||||||||||||||||
| .all() | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| for dashboard_id, json_metadata_str in dashboards_to_update: | ||||||||||||||||||||||||||||||||||||||||
| if not json_metadata_str: | ||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||
| metadata = json.loads(json_metadata_str) | ||||||||||||||||||||||||||||||||||||||||
| except (json.JSONDecodeError, TypeError): | ||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||
| "Could not parse metadata for dashboard %s", dashboard_id | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| modified = False | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Clean up expanded_slices | ||||||||||||||||||||||||||||||||||||||||
| if "expanded_slices" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| chart_id_str = str(chart_id) | ||||||||||||||||||||||||||||||||||||||||
| if chart_id_str in metadata["expanded_slices"]: | ||||||||||||||||||||||||||||||||||||||||
| del metadata["expanded_slices"][chart_id_str] | ||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Clean up timed_refresh_immune_slices | ||||||||||||||||||||||||||||||||||||||||
| if "timed_refresh_immune_slices" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| if chart_id in metadata["timed_refresh_immune_slices"]: | ||||||||||||||||||||||||||||||||||||||||
| metadata["timed_refresh_immune_slices"].remove(chart_id) | ||||||||||||||||||||||||||||||||||||||||
|
ramiroaquinoromero marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Clean up filter_scopes | ||||||||||||||||||||||||||||||||||||||||
| if "filter_scopes" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| chart_id_str = str(chart_id) | ||||||||||||||||||||||||||||||||||||||||
| if chart_id_str in metadata["filter_scopes"]: | ||||||||||||||||||||||||||||||||||||||||
| del metadata["filter_scopes"][chart_id_str] | ||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
| # Also clean from immune lists | ||||||||||||||||||||||||||||||||||||||||
| for filter_scope in metadata["filter_scopes"].values(): | ||||||||||||||||||||||||||||||||||||||||
| for attributes in filter_scope.values(): | ||||||||||||||||||||||||||||||||||||||||
| if chart_id in attributes.get("immune", []): | ||||||||||||||||||||||||||||||||||||||||
| attributes["immune"].remove(chart_id) | ||||||||||||||||||||||||||||||||||||||||
|
ramiroaquinoromero marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Clean up default_filters | ||||||||||||||||||||||||||||||||||||||||
| if "default_filters" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| default_filters = json.loads(metadata["default_filters"]) | ||||||||||||||||||||||||||||||||||||||||
|
ramiroaquinoromero marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||
| chart_id_str = str(chart_id) | ||||||||||||||||||||||||||||||||||||||||
| if chart_id_str in default_filters: | ||||||||||||||||||||||||||||||||||||||||
| del default_filters[chart_id_str] | ||||||||||||||||||||||||||||||||||||||||
| metadata["default_filters"] = json.dumps(default_filters) | ||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+168
to
+175
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error Handling Gap
The json.loads call for metadata["default_filters"] lacks error handling, which could raise an exception if the value is malformed. It looks like this should be wrapped in a try-except block similar to the main metadata parsing, to log a warning and continue instead of failing the entire chart deletion. Code suggestionCheck the AI-generated fix before applying
Suggested change
Code Review Run #eacd8a Should Bito avoid suggestions like this for future reviews? (Manage Rules)
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Clean up native_filter_configuration scope exclusions | ||||||||||||||||||||||||||||||||||||||||
| if "native_filter_configuration" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| for native_filter in metadata["native_filter_configuration"]: | ||||||||||||||||||||||||||||||||||||||||
| scope_excluded = native_filter.get("scope", {}).get("excluded", []) | ||||||||||||||||||||||||||||||||||||||||
| if chart_id in scope_excluded: | ||||||||||||||||||||||||||||||||||||||||
| scope_excluded.remove(chart_id) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+180
to
+182
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The code compares and removes Severity Level: Critical 🚨- ❌ Dashboard export/importers/v1 may fail on orphaned references.
- ❌ Native filter exclusion lists remain stale.
- ⚠️ Chart deletion metadata cleanup incomplete.
Suggested change
Steps of Reproduction ✅1. Create a dashboard whose json_metadata["native_filter_configuration"] contains an entry
where scope.excluded is a list of string IDs (e.g., ["123"]) rather than ints. The cleanup
loop is at superset/commands/chart/delete.py:177-183 in
DeleteChartCommand._cleanup_dashboard_metadata (function start at
superset/commands/chart/delete.py:78).
2. Invoke DeleteChartCommand.run with the chart id 123 present on that dashboard
(superset/commands/chart/delete.py:49). The run method calls _cleanup_dashboard_metadata
at superset/commands/chart/delete.py:52-54.
3. The current code checks `if chart_id in scope_excluded` at
superset/commands/chart/delete.py:180. Because scope_excluded contains string "123" and
chart_id is the integer 123, the test is False; no removal occurs and modified remains
False for that dashboard.
4. The string-ID exclusion remains in the dashboard metadata and is not cleaned. This
leaves an orphaned reference that can later trigger import-time KeyError in the importer
(see PR description for superset/commands/dashboard/importers/v1/utils.py). The behavior
is concrete and reproducible using string-stored IDs.Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** superset/commands/chart/delete.py
**Line:** 180:182
**Comment:**
*Logic Error: The code compares and removes `chart_id` as an int from lists that may store IDs as strings (and vice versa), so removals can silently fail (logic bug). Normalize comparisons by checking and removing both the integer and string representations to ensure the chart ID is removed regardless of stored type.
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. |
||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
ramiroaquinoromero marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Clean up chart_configuration | ||||||||||||||||||||||||||||||||||||||||
| if "chart_configuration" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| chart_id_str = str(chart_id) | ||||||||||||||||||||||||||||||||||||||||
| if chart_id_str in metadata["chart_configuration"]: | ||||||||||||||||||||||||||||||||||||||||
| del metadata["chart_configuration"][chart_id_str] | ||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see a lot of repeating logic for the if statements I think a util function will clean this up quite a bit |
||||||||||||||||||||||||||||||||||||||||
| # Clean up global_chart_configuration scope exclusions | ||||||||||||||||||||||||||||||||||||||||
| if "global_chart_configuration" in metadata: | ||||||||||||||||||||||||||||||||||||||||
| scope_excluded = ( | ||||||||||||||||||||||||||||||||||||||||
| metadata["global_chart_configuration"] | ||||||||||||||||||||||||||||||||||||||||
| .get("scope", {}) | ||||||||||||||||||||||||||||||||||||||||
| .get("excluded", []) | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| if chart_id in scope_excluded: | ||||||||||||||||||||||||||||||||||||||||
| scope_excluded.remove(chart_id) | ||||||||||||||||||||||||||||||||||||||||
| modified = True | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if modified: | ||||||||||||||||||||||||||||||||||||||||
| # Update only the json_metadata field | ||||||||||||||||||||||||||||||||||||||||
| db.session.query(Dashboard).filter_by(id=dashboard_id).update( | ||||||||||||||||||||||||||||||||||||||||
| {"json_metadata": json.dumps(metadata)}, | ||||||||||||||||||||||||||||||||||||||||
| synchronize_session=False, | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||||||||||||||||||||
| "Cleaned up metadata for dashboard %s after deleting chart %s", | ||||||||||||||||||||||||||||||||||||||||
| dashboard_id, | ||||||||||||||||||||||||||||||||||||||||
| chart_id, | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -199,3 +199,131 @@ def test_update_id_refs_cross_filter_handles_string_excluded(): | |
| fixed = update_id_refs(config, chart_ids, dataset_info) | ||
| # Should not raise and should remap key | ||
| assert "1" in fixed["metadata"]["chart_configuration"] | ||
|
|
||
|
|
||
| def test_update_id_refs_expanded_slices_with_missing_chart(): | ||
| """ | ||
| Test that missing charts in expanded_slices are gracefully skipped. | ||
|
|
||
| When a chart is deleted from a workspace, dashboards may still contain | ||
| references to the deleted chart ID in expanded_slices metadata. This test | ||
| verifies that the import process skips missing chart references instead of | ||
| raising a KeyError. | ||
| """ | ||
| from superset.commands.dashboard.importers.v1.utils import update_id_refs | ||
|
|
||
| config = { | ||
| "position": { | ||
| "CHART1": { | ||
| "id": "CHART1", | ||
| "meta": {"chartId": 101, "uuid": "uuid1"}, | ||
| "type": "CHART", | ||
| }, | ||
| }, | ||
| "metadata": { | ||
| "expanded_slices": { | ||
| "101": True, # This chart exists in the import | ||
| "102": False, # This chart was deleted and doesn't exist | ||
| "103": True, # Another deleted chart | ||
| }, | ||
| }, | ||
| } | ||
| chart_ids = {"uuid1": 1} # Only uuid1 exists in the import | ||
| dataset_info: dict[str, dict[str, Any]] = {} | ||
|
|
||
| fixed = update_id_refs(config, chart_ids, dataset_info) | ||
|
|
||
| # Should only include the existing chart, missing charts are skipped | ||
| assert fixed["metadata"]["expanded_slices"] == {"1": True} | ||
| # Should not raise KeyError for missing charts 102 and 103 | ||
|
|
||
|
|
||
| def test_update_id_refs_timed_refresh_immune_slices_with_missing_chart(): | ||
| """ | ||
| Test that missing charts in timed_refresh_immune_slices are gracefully skipped. | ||
|
|
||
| When a chart is deleted from a workspace, dashboards may still contain | ||
| references to the deleted chart ID in timed_refresh_immune_slices metadata. | ||
| This test verifies that the import process skips missing chart references | ||
| instead of raising a KeyError. | ||
| """ | ||
| from superset.commands.dashboard.importers.v1.utils import update_id_refs | ||
|
|
||
| config = { | ||
| "position": { | ||
| "CHART1": { | ||
| "id": "CHART1", | ||
| "meta": {"chartId": 101, "uuid": "uuid1"}, | ||
| "type": "CHART", | ||
| }, | ||
| "CHART2": { | ||
| "id": "CHART2", | ||
| "meta": {"chartId": 102, "uuid": "uuid2"}, | ||
| "type": "CHART", | ||
| }, | ||
| }, | ||
| "metadata": { | ||
| "timed_refresh_immune_slices": [ | ||
| 101, # This chart exists | ||
| 102, # This chart exists | ||
| 103, # This chart was deleted and doesn't exist | ||
| 104, # Another deleted chart | ||
| ], | ||
| }, | ||
| } | ||
|
Comment on lines
+252
to
+273
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could add a general function that builds a new config for you, so you can modify as you wish for each test |
||
| chart_ids = {"uuid1": 1, "uuid2": 2} # Only uuid1 and uuid2 exist | ||
| dataset_info: dict[str, dict[str, Any]] = {} | ||
|
|
||
| fixed = update_id_refs(config, chart_ids, dataset_info) | ||
|
|
||
| # Should only include existing charts, missing charts are skipped | ||
| assert fixed["metadata"]["timed_refresh_immune_slices"] == [1, 2] | ||
| # Should not raise KeyError for missing charts 103 and 104 | ||
|
|
||
|
|
||
| def test_update_id_refs_multiple_missing_chart_references(): | ||
| """ | ||
| Test that multiple metadata fields with missing charts are all handled gracefully. | ||
|
|
||
| This comprehensive test verifies that all metadata fields properly skip | ||
| missing chart references during import. | ||
| """ | ||
| from superset.commands.dashboard.importers.v1.utils import update_id_refs | ||
|
|
||
| config = { | ||
| "position": { | ||
| "CHART1": { | ||
| "id": "CHART1", | ||
| "meta": {"chartId": 101, "uuid": "uuid1"}, | ||
| "type": "CHART", | ||
| }, | ||
| }, | ||
| "metadata": { | ||
| "expanded_slices": { | ||
| "101": True, | ||
| "999": False, # Missing chart | ||
| }, | ||
| "timed_refresh_immune_slices": [101, 999], # 999 is missing | ||
| "filter_scopes": { | ||
| "101": {"region": {"immune": [999]}}, # 999 is missing | ||
| "999": {"region": {"immune": [101]}}, # Key 999 is missing | ||
| }, | ||
| "default_filters": '{"101": {"col": "value"}, "999": {"col": "other"}}', | ||
| "native_filter_configuration": [ | ||
| {"scope": {"excluded": [101, 999]}} # 999 is missing | ||
| ], | ||
| }, | ||
| } | ||
| chart_ids = {"uuid1": 1} # Only uuid1 exists | ||
| dataset_info: dict[str, dict[str, Any]] = {} | ||
|
|
||
| fixed = update_id_refs(config, chart_ids, dataset_info) | ||
|
|
||
| # All missing chart references should be gracefully skipped | ||
| assert fixed["metadata"]["expanded_slices"] == {"1": True} | ||
| assert fixed["metadata"]["timed_refresh_immune_slices"] == [1] | ||
| assert fixed["metadata"]["filter_scopes"] == {"1": {"region": {"immune": []}}} | ||
| assert fixed["metadata"]["default_filters"] == '{"1": {"col": "value"}}' | ||
|
ramiroaquinoromero marked this conversation as resolved.
|
||
| assert fixed["metadata"]["native_filter_configuration"] == [ | ||
| {"scope": {"excluded": [1]}} | ||
| ] | ||
|
ramiroaquinoromero marked this conversation as resolved.
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Personal concern, since I don't have context]
It doesn't feel really safe to do this since the I don't know how many models we could get here and also
_cleanup_dashboard_metadatacould be time consuming. I would suggest to investigate if this won't cause any performance issues, and if not, I would write a comment denoting why this solution works and what scenario will cover, so if at some point in the future it fails, devs would know what to consider to fix it.