Skip to content

Commit a2f761d

Browse files
vondravlclaude
andcommitted
fix(gooddata-sdk): support tabbed dashboard layouts in translation methods
Dashboards can now use a tabbed layout where sections live under content.tabs[*].layout.sections instead of content.layout.sections. get_texts_to_translate and set_translated_texts crashed with KeyError on dashboards that only use tabs (no top-level layout key). - Add _iter_dashboard_sections() to yield sections from both structures - Extract date filter titles from tab-level configs - Extract and translate tab titles - Add unit tests covering all layout variants Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0ddff14 commit a2f761d

File tree

2 files changed

+524
-5
lines changed

2 files changed

+524
-5
lines changed

packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -755,19 +755,57 @@ def set_title_description_tags(self, workspace_object: Any, translated: dict[str
755755
if workspace_object.tags:
756756
workspace_object.tags = [translated[x] for x in workspace_object.tags]
757757

758+
@staticmethod
759+
def _iter_dashboard_sections(content: dict):
760+
"""Yield all sections from a dashboard content dict.
761+
762+
Handles both the legacy flat layout (content.layout.sections) and the
763+
newer tabbed layout (content.tabs[*].layout.sections), including
764+
dashboards that contain both structures.
765+
766+
Args:
767+
content: Dashboard content dictionary
768+
"""
769+
# Top-level layout sections (legacy / single-tab dashboards)
770+
layout = content.get("layout")
771+
if layout:
772+
yield from layout.get("sections", [])
773+
774+
# Tab-level layout sections (tabbed dashboards)
775+
for tab in content.get("tabs", []):
776+
tab_layout = tab.get("layout")
777+
if tab_layout:
778+
yield from tab_layout.get("sections", [])
779+
758780
def _extract_dashboard_date_filter_titles(self, to_translate: set[str], dashboard_content: dict) -> None:
759781
"""Extract date filter titles from dashboard content for translation.
760782
783+
Handles both top-level and tab-level date filter configurations.
784+
761785
Args:
762786
to_translate: Set to collect translatable strings
763787
dashboard_content: Dashboard content dictionary containing date filter configurations
764788
"""
789+
# Extract date filter titles from top-level content
790+
self._extract_date_filter_titles_from_dict(to_translate, dashboard_content)
791+
792+
# Extract date filter titles from each tab
793+
for tab in dashboard_content.get("tabs", []):
794+
self._extract_date_filter_titles_from_dict(to_translate, tab)
795+
796+
def _extract_date_filter_titles_from_dict(self, to_translate: set[str], content_dict: dict) -> None:
797+
"""Extract date filter titles from a single content dictionary.
798+
799+
Args:
800+
to_translate: Set to collect translatable strings
801+
content_dict: Dictionary that may contain dateFilterConfig and dateFilterConfigs
802+
"""
765803
# Extract implicit date filter title
766-
implicit_date_filter_title = dashboard_content.get("dateFilterConfig", {}).get("filterName")
804+
implicit_date_filter_title = content_dict.get("dateFilterConfig", {}).get("filterName")
767805
self.add_title_description(to_translate, implicit_date_filter_title, None)
768806

769807
# Extract explicit date filter titles
770-
for date_filter_config in dashboard_content.get("dateFilterConfigs", []):
808+
for date_filter_config in content_dict.get("dateFilterConfigs", []):
771809
explicit_date_filter_title = date_filter_config.get("config", {}).get("filterName")
772810
self.add_title_description(to_translate, explicit_date_filter_title, None)
773811

@@ -808,9 +846,15 @@ def get_texts_to_translate(
808846
to_translate.add(item["measure"]["alias"])
809847
for dashboard in workspace_content.analytics.analytical_dashboards or []:
810848
self.add_title_description(to_translate, dashboard.title, dashboard.description)
811-
# Extract date filter titles for translation
849+
# Extract date filter titles for translation (top-level + tabs)
812850
self._extract_dashboard_date_filter_titles(to_translate, dashboard.content)
813-
for section in dashboard.content["layout"]["sections"]:
851+
# Extract tab titles for translation
852+
for tab in dashboard.content.get("tabs", []):
853+
tab_title = tab.get("title")
854+
if tab_title:
855+
to_translate.add(tab_title)
856+
# Iterate sections from both top-level layout and tabs
857+
for section in self._iter_dashboard_sections(dashboard.content):
814858
for item in section["items"]:
815859
widget = item["widget"]
816860
title = widget.get("title")
@@ -877,8 +921,12 @@ def set_translated_texts(
877921
item["measure"]["alias"] = translated[item["measure"]["alias"]]
878922
for dashboard in new_workspace_content.analytics.analytical_dashboards or []:
879923
self.set_title_description(dashboard, translated)
924+
# Translate tab titles
925+
for tab in dashboard.content.get("tabs", []):
926+
if "title" in tab and tab["title"]:
927+
tab["title"] = translated.get(tab["title"], tab["title"])
880928
# Hack: translate titles in free-form, which is not processed intentionally by this SDK
881-
for section in dashboard.content["layout"]["sections"]:
929+
for section in self._iter_dashboard_sections(dashboard.content):
882930
for item in section["items"]:
883931
if "title" in item["widget"]:
884932
item["widget"]["title"] = translated.get(item["widget"]["title"])

0 commit comments

Comments
 (0)