Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/gooddata_legacy2cloud/dashboards/cloud_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def __init__(
self.cloud_filters = filter_context_class.cloud_filters
self.attribute_filter_configs = filter_context_class.attribute_filter_configs

self.layout = self._get_layout(self.dashboards_content.get("layout", []))
# Empty Legacy dashboards (no widgets) have no layout at all
self.layout = self._get_layout(self.dashboards_content.get("layout") or {})
self.title = self.meta["title"]

# Check for any warnings (filter values or drills) after layout processing
Expand Down Expand Up @@ -483,7 +484,7 @@ def _get_layout(self, layout_items: dict) -> dict:
"""
Returns the items.
"""
rows = layout_items["fluidLayout"]["rows"]
rows = layout_items.get("fluidLayout", {}).get("rows", [])
new_sections = []
for row in rows:
# Iterate over the columns to get the max row height
Expand Down
4 changes: 4 additions & 0 deletions src/gooddata_legacy2cloud/dashboards/filter_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ def _get_filters(self, filters: list) -> tuple[list, dict]:
pass
new_filters.append(filter)
elif "attributeFilter" in filter:
# Older Legacy KPI dashboards did not store localIdentifier
filter["attributeFilter"].setdefault(
"localIdentifier", f"{idx}_attributeFilter"
)
obj = self.ctx.legacy_client.get_object(
filter["attributeFilter"]["displayForm"]
)
Expand Down
2 changes: 1 addition & 1 deletion src/gooddata_legacy2cloud/workflows/migrate_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def migrate_dashboards(config: DashboardConfig):
prefetcher.create_metrics_for_unmapped_elements()
validation_required = True

if validation_required:
if validation_required and legacy_dashboards:
legacy_client.initialize_attribute_elements_cache()

if config.validation_element_lookup_with_metrics:
Expand Down
2 changes: 1 addition & 1 deletion src/gooddata_legacy2cloud/workflows/migrate_insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def migrate_insights(config: InsightConfig):
prefetcher.create_metrics_for_unmapped_elements()
validation_required = True

if validation_required:
if validation_required and legacy_insights:
legacy_client.initialize_attribute_elements_cache()

if config.validation_element_lookup_with_metrics:
Expand Down
2 changes: 1 addition & 1 deletion src/gooddata_legacy2cloud/workflows/migrate_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def migrate_metrics(config: MetricConfig):
prefetcher.collect_element_uris_from_objects(legacy_metrics)
prefetcher.prefetch_and_cache()

if config.validation_element_lookup:
if config.validation_element_lookup and legacy_metrics:
legacy_client.initialize_attribute_elements_cache()

if config.object_migration_config.dump_legacy:
Expand Down
20 changes: 20 additions & 0 deletions tests/data/dashboards/legacy_objects/objects_by_uri.json
Original file line number Diff line number Diff line change
Expand Up @@ -1278,5 +1278,25 @@
"filters": []
}
}
},
"/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/40030": {
"filterContext": {
"meta": {
"title": "filterContext",
"identifier": "aa6LcrTCksyT",
"uri": "/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/40030"
},
"content": {
"filters": [
{
"attributeFilter": {
"displayForm": "/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/990010",
"attributeElements": [],
"negativeSelection": false
}
}
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[
{
"data": {
"id": "dashboard_with_attribute_filter_with_multiple_labels_1_abrLcCYyiyXE",
"type": "analyticalDashboard",
"attributes": {
"title": "dashboard with attribute filter with multiple labels 1",
"description": "",
"tags": [],
"content": {
"attributeFilterConfigs": [
{
"localIdentifier": "0_attributeFilter",
"displayAsLabel": {
"identifier": {
"id": "product.productid.product",
"type": "label"
}
}
}
],
"filterContextRef": {
"identifier": {
"id": "filtercontext_aa6LcrTCksyT",
"type": "filterContext"
}
},
"layout": {
"type": "IDashboardLayout",
"sections": [
{
"type": "IDashboardLayoutSection",
"header": {},
"items": [
{
"type": "IDashboardLayoutItem",
"size": {
"xl": {
"gridHeight": 22,
"gridWidth": 12
}
},
"widget": {
"type": "insight",
"drills": [],
"description": "",
"ignoreDashboardFilters": [],
"insight": {
"identifier": {
"id": "visualization_with_attribute_filter_with_multiple_labels_2_aaqLka7tnKTs",
"type": "visualizationObject"
}
},
"title": "visualization with attribute filter with multiple labels 2",
"dateDataSet": {
"identifier": {
"id": "dt_order",
"type": "dataset"
}
},
"localIdentifier": "aavLkgndn8D2"
}
},
{
"type": "IDashboardLayoutItem",
"size": {
"xl": {
"gridHeight": 22,
"gridWidth": 12
}
},
"widget": {
"type": "insight",
"drills": [],
"description": "",
"ignoreDashboardFilters": [],
"insight": {
"identifier": {
"id": "visualization_with_attribute_filter_with_multiple_labels_1_abjLcJdtJ0d5",
"type": "visualizationObject"
}
},
"title": "visualization with attribute filter with multiple labels 1",
"dateDataSet": {
"identifier": {
"id": "dt_order",
"type": "dataset"
}
},
"localIdentifier": "abZLcDMSkhzN"
}
}
]
}
]
},
"version": "2"
}
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[
{
"analyticalDashboard": {
"content": {
"filterContext": "/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/40030",
"layout": {
"fluidLayout": {
"rows": [
{
"columns": [
{
"content": {
"widget": {
"qualifier": {
"uri": "/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/4002"
}
}
},
"size": {
"xl": {
"width": 12,
"height": 22
}
}
},
{
"content": {
"widget": {
"qualifier": {
"uri": "/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/4004"
}
}
},
"size": {
"xl": {
"height": 22,
"width": 12
}
}
}
]
}
]
}
},
"widgets": [
"/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/4002",
"/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/4004"
]
},
"meta": {
"created": "2025-04-06 16:43:20",
"uri": "/gdc/md/fkxyvp08rrrkfqss1ai656hvs0m77vl0/obj/4005",
"identifier": "abrLcCYyiyXE",
"deprecated": "0",
"summary": "",
"title": "dashboard with attribute filter with multiple labels 1",
"updated": "2025-04-06 16:43:27",
"contributor": "/gdc/account/profile/0102b54c3859150e0d75e52f1a3d034f",
"tags": "",
"category": "analyticalDashboard",
"author": "/gdc/account/profile/0102b54c3859150e0d75e52f1a3d034f",
"isProduction": 1
}
}
}
]
27 changes: 27 additions & 0 deletions tests/data/dashboards/test_cases/empty_dashboard_cloud.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"data": {
"id": "migration_sample_aarrBr48ObDC",
"type": "analyticalDashboard",
"attributes": {
"title": "Migration sample",
"description": "",
"tags": [],
"content": {
"attributeFilterConfigs": [],
"filterContextRef": {
"identifier": {
"id": "filtercontext_acBrpELxXJPQ",
"type": "filterContext"
}
},
"layout": {
"type": "IDashboardLayout",
"sections": []
},
"version": "2"
}
}
}
}
]
24 changes: 24 additions & 0 deletions tests/data/dashboards/test_cases/empty_dashboard_legacy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[
{
"analyticalDashboard": {
"content": {
"widgets": [],
"filterContext": "/gdc/md/dinu996plcf6ajcp6wygnqn952uyom9u/obj/4985"
},
"meta": {
"isProduction": 1,
"summary": "",
"identifier": "aarrBr48ObDC",
"contributor": "/gdc/account/profile/5cc081c981561ae1d3f81481b50f002c",
"updated": "2025-11-26 14:40:14",
"tags": "",
"uri": "/gdc/md/dinu996plcf6ajcp6wygnqn952uyom9u/obj/4988",
"created": "2025-09-09 08:51:00",
"title": "Migration sample",
"deprecated": "0",
"category": "analyticalDashboard",
"author": "/gdc/account/profile/5cc081c981561ae1d3f81481b50f002c"
}
}
}
]
2 changes: 2 additions & 0 deletions tests/test_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"dashboard_with_missing_kpi_metric",
"self_drill",
"dashboard_with_dependent_filters",
"empty_dashboard",
"dashboard_with_attribute_filter_without_local_identifier",
],
)
def test_dashboards_migration(
Expand Down
56 changes: 56 additions & 0 deletions tests/test_migrate_metrics_element_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# (C) 2026 GoodData Corporation
"""The Legacy attribute elements cache is only loaded when there are metrics to migrate.

Client workspaces migrated with --client-prefix usually have no custom metrics
left after filtering out the mapped (default) ones; the slow cache fetch must
be skipped for them.
"""

from typing import cast

import pytest
from pytest_mock import MockerFixture

from gooddata_legacy2cloud.config.configuration_objects import MetricConfig
from gooddata_legacy2cloud.workflows import migrate_metrics as workflow


@pytest.mark.parametrize(
("legacy_metrics", "cache_loaded"),
[([], False), ([{"metric": {"meta": {"identifier": "m1"}}}], True)],
)
def test_element_cache_only_loaded_with_metrics(
legacy_metrics: list[dict], cache_loaded: bool, mocker: MockerFixture
) -> None:
for name in (
"EnvVars",
"CloudClient",
"IdMappings",
"OutputWriter",
"FilterParameters",
"CloudMetricsBuilder",
"process_objects",
"set_output_files_prefix",
"format_mapping_files_info",
):
mocker.patch.object(workflow, name)
mocker.patch.object(
workflow, "get_mapping_files", return_value=(["metric_mappings.csv"], [])
)
mocker.patch.object(
workflow, "fetch_objects_with_filters", return_value=legacy_metrics
)
legacy_client = mocker.patch.object(workflow, "LegacyClient").return_value

config = mocker.MagicMock()
config.validation_element_lookup = True
config.element_values_prefetch = False
config.object_filter_config.without_mapped_objects = None
config.object_migration_config.dump_legacy = False
config.object_migration_config.dump_cloud = False
config.object_migration_config.cleanup_target_env = False
config.common_config.skip_deploy = True

workflow.migrate_metrics(cast(MetricConfig, config))

assert legacy_client.initialize_attribute_elements_cache.called is cache_loaded
Loading