Skip to content

Commit a128879

Browse files
authored
Merge pull request #1360 from DalgoT4D/cleanup/remove-dashboard-root-layout-fields
cleanup: remove root-level layout_config and components from Dashboar…
2 parents a358a4c + 82984ea commit a128879

22 files changed

Lines changed: 257 additions & 486 deletions

ddpui/api/dashboard_native_api.py

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Native Dashboard API endpoints"""
22

3-
import copy
43
from typing import Optional, List
54
from datetime import timedelta
65

@@ -174,15 +173,12 @@ def duplicate_dashboard(request, dashboard_id: int):
174173

175174
# Create a copy of the dashboard
176175
with transaction.atomic():
177-
# First create new dashboard WITHOUT layout_config and components (we'll update them later)
178176
new_dashboard = Dashboard.objects.create(
179177
title=f"Copy of {original_dashboard.title}",
180178
description=original_dashboard.description,
181179
dashboard_type=original_dashboard.dashboard_type,
182180
grid_columns=original_dashboard.grid_columns,
183181
target_screen_size=original_dashboard.target_screen_size,
184-
layout_config=[], # Will be updated after filter duplication
185-
components={}, # Will be updated after filter duplication
186182
created_by=orguser,
187183
org=orguser.org,
188184
last_modified_by=orguser,
@@ -204,47 +200,6 @@ def duplicate_dashboard(request, dashboard_id: int):
204200
)
205201
filter_id_mapping[str(original_filter.id)] = str(new_filter.id)
206202

207-
# Now update layout_config and components with new filter IDs
208-
209-
# Deep copy the original data to avoid modifying it
210-
new_layout_config = copy.deepcopy(original_dashboard.layout_config or [])
211-
new_components = copy.deepcopy(original_dashboard.components or {})
212-
213-
# Update layout_config: change component IDs from "filter-{old_id}" to "filter-{new_id}"
214-
for layout_item in new_layout_config:
215-
item_id = layout_item.get("i", "")
216-
if item_id.startswith("filter-"):
217-
# Extract old filter ID and replace with new one
218-
old_filter_id = item_id.replace("filter-", "")
219-
if old_filter_id in filter_id_mapping:
220-
new_filter_id = filter_id_mapping[old_filter_id]
221-
layout_item["i"] = f"filter-{new_filter_id}"
222-
223-
# Update components: change component keys and filterId references
224-
updated_components = {}
225-
for component_id, component_data in new_components.items():
226-
new_component_id = component_id
227-
new_component_data = copy.deepcopy(component_data)
228-
229-
# If this is a filter component
230-
if component_id.startswith("filter-"):
231-
old_filter_id = component_id.replace("filter-", "")
232-
if old_filter_id in filter_id_mapping:
233-
new_filter_id = filter_id_mapping[old_filter_id]
234-
new_component_id = f"filter-{new_filter_id}"
235-
236-
# Update the filterId reference in the component config
237-
if (
238-
"config" in new_component_data
239-
and "filterId" in new_component_data["config"]
240-
):
241-
new_component_data["config"]["filterId"] = int(new_filter_id)
242-
243-
updated_components[new_component_id] = new_component_data
244-
245-
# Update the dashboard with the corrected layout_config, components, and tabs
246-
new_dashboard.layout_config = new_layout_config
247-
new_dashboard.components = updated_components
248203
new_dashboard.tabs = DashboardService.copy_tabs_with_filter_remapping(
249204
original_dashboard.tabs or [], filter_id_mapping
250205
)
@@ -422,8 +377,6 @@ def get_filter_options(
422377
raise HttpError(400, "No warehouse configured for organization")
423378

424379
# Get filter options from service
425-
from ddpui.services.dashboard_service import DashboardService
426-
427380
options = DashboardService.generate_filter_options(
428381
schema=schema_name,
429382
table=table_name,

ddpui/core/reports/report_service.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,14 @@ def _freeze_dashboard(dashboard: Dashboard) -> Dict[str, Any]:
6262
"description": dashboard.description,
6363
"grid_columns": dashboard.grid_columns,
6464
"target_screen_size": dashboard.target_screen_size,
65-
"layout_config": dashboard.layout_config,
66-
"components": dashboard.components,
6765
"tabs": dashboard.tabs,
6866
"filter_layout": dashboard.filter_layout,
6967
"filters": [f.to_json() for f in filters],
7068
}
7169

7270
@staticmethod
7371
def _extract_chart_ids(dashboard: Dashboard) -> List[int]:
74-
"""Extract chart IDs from tabs (new structure) and root components (backward compat)."""
72+
"""Extract chart IDs from tabs."""
7573
chart_ids = []
7674

7775
for tab in dashboard.tabs or []:
@@ -81,12 +79,6 @@ def _extract_chart_ids(dashboard: Dashboard) -> List[int]:
8179
if chart_id:
8280
chart_ids.append(chart_id)
8381

84-
for component in (dashboard.components or {}).values():
85-
if component.get("type") == "chart":
86-
chart_id = component.get("config", {}).get("chartId")
87-
if chart_id:
88-
chart_ids.append(chart_id)
89-
9082
return list(set(chart_ids))
9183

9284
@staticmethod
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from django.core.management.base import BaseCommand
2+
from ddpui.models.report import ReportSnapshot
3+
4+
5+
class Command(BaseCommand):
6+
"""
7+
Remove stale root-level layout_config and components keys from
8+
ReportSnapshot.frozen_dashboard JSON blobs.
9+
10+
These keys were left in place after the initial tabs migration for rollback
11+
safety. All data now lives inside frozen_dashboard.tabs — the root-level
12+
keys are no longer read and can be removed.
13+
"""
14+
15+
help = "Remove root-level layout_config and components from frozen_dashboard JSON"
16+
17+
def add_arguments(self, parser):
18+
"""Add --dry-run argument to preview changes without applying them."""
19+
parser.add_argument(
20+
"--dry-run",
21+
action="store_true",
22+
help="Preview changes without applying them",
23+
)
24+
25+
def handle(self, *args, **options):
26+
"""Execute the cleanup, iterating all ReportSnapshot records in batches."""
27+
dry_run = options["dry_run"]
28+
29+
if dry_run:
30+
self.stdout.write("\n=== DRY RUN MODE: No changes will be made ===\n")
31+
32+
cleaned_count = 0
33+
skipped_count = 0
34+
35+
snapshots_to_update = []
36+
37+
for snapshot in ReportSnapshot.objects.only("id", "frozen_dashboard").iterator(
38+
chunk_size=1000
39+
):
40+
frozen = snapshot.frozen_dashboard
41+
if not isinstance(frozen, dict):
42+
skipped_count += 1
43+
continue
44+
45+
has_layout = "layout_config" in frozen
46+
has_components = "components" in frozen
47+
48+
if not has_layout and not has_components:
49+
skipped_count += 1
50+
continue
51+
52+
cleaned_count += 1
53+
54+
if dry_run:
55+
self.stdout.write(
56+
f"[DRY RUN] Would clean - Snapshot ID: {snapshot.id}, "
57+
f"keys to remove: {[k for k in ('layout_config', 'components') if k in frozen]}"
58+
)
59+
else:
60+
frozen.pop("layout_config", None)
61+
frozen.pop("components", None)
62+
snapshot.frozen_dashboard = frozen
63+
snapshots_to_update.append(snapshot)
64+
65+
if not dry_run and snapshots_to_update:
66+
ReportSnapshot.objects.bulk_update(
67+
snapshots_to_update, ["frozen_dashboard"], batch_size=500
68+
)
69+
for snapshot in snapshots_to_update:
70+
self.stdout.write(f"Cleaned - Snapshot ID: {snapshot.id}")
71+
72+
self.stdout.write("")
73+
if dry_run:
74+
self.stdout.write(
75+
self.style.WARNING(
76+
f"=== DRY RUN COMPLETE: {cleaned_count} snapshots would be cleaned, "
77+
f"{skipped_count} skipped ==="
78+
)
79+
)
80+
else:
81+
self.stdout.write(
82+
self.style.SUCCESS(
83+
f"=== CLEANUP COMPLETE: {cleaned_count} snapshots cleaned, "
84+
f"{skipped_count} skipped ==="
85+
)
86+
)

ddpui/management/commands/migrate_dashboards_to_tabs.py

Lines changed: 0 additions & 95 deletions
This file was deleted.

0 commit comments

Comments
 (0)