Skip to content

Commit 48dc249

Browse files
jaceksanclaude
andcommitted
fix: pass entities arg to WASM visualization/dashboard converters
The WASM converter functions declarativeVisualisationToYaml and declarativeDashboardToYaml require an entities array as their first argument. The Python wrappers were only passing the declarative dict, which the WASM received as the entities param, leaving the actual object undefined — causing "cannot read property 'content' of undefined" for every visualization and dashboard. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> JIRA: DX-326 risk: low
1 parent d6a7ef4 commit 48dc249

File tree

2 files changed

+119
-6
lines changed
  • packages/gooddata-sdk

2 files changed

+119
-6
lines changed

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,32 @@ def declarative_metric_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
188188
return declarative_metric_to_yaml(declarative)
189189

190190

191-
def declarative_visualization_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
192-
"""Convert a declarative visualization dict to AAC format."""
193-
return declarative_visualisation_to_yaml(declarative)
191+
def declarative_visualization_to_aac(
192+
declarative: dict[str, Any],
193+
entities: list[dict[str, Any]] | None = None,
194+
) -> dict[str, Any]:
195+
"""Convert a declarative visualization dict to AAC format.
196+
197+
Args:
198+
declarative: The declarative visualization dict.
199+
entities: Optional entities list for cross-reference resolution.
200+
"""
201+
ent = entities if entities is not None else []
202+
return declarative_visualisation_to_yaml(ent, declarative)
194203

195204

196-
def declarative_dashboard_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
197-
"""Convert a declarative dashboard dict to AAC format."""
198-
return declarative_dashboard_to_yaml(declarative)
205+
def declarative_dashboard_to_aac(
206+
declarative: dict[str, Any],
207+
entities: list[dict[str, Any]] | None = None,
208+
) -> dict[str, Any]:
209+
"""Convert a declarative dashboard dict to AAC format.
210+
211+
Args:
212+
declarative: The declarative dashboard dict.
213+
entities: Optional entities list for cross-reference resolution.
214+
"""
215+
ent = entities if entities is not None else []
216+
return declarative_dashboard_to_yaml(ent, declarative)
199217

200218

201219
def declarative_plugin_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:

packages/gooddata-sdk/tests/catalog/test_aac.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
aac_visualization_to_declarative,
1212
declarative_dataset_to_aac,
1313
declarative_metric_to_aac,
14+
declarative_visualization_to_aac,
1415
detect_yaml_format,
1516
load_aac_workspace_from_disk,
1617
store_aac_workspace_to_disk,
@@ -176,6 +177,38 @@ def test_dataset_declarative_to_aac(self) -> None:
176177
assert result["json"]["id"] == "orders"
177178
assert isinstance(result["content"], str)
178179

180+
def test_visualization_declarative_to_aac(self) -> None:
181+
"""Test declarative → AAC for visualizations (round-trip from fixture)."""
182+
content = yaml.safe_load((_FIXTURES_DIR / "visualisations" / "ratings.yaml").read_text())
183+
declarative = aac_visualization_to_declarative(content)
184+
result = declarative_visualization_to_aac(declarative)
185+
assert result["json"]["id"] == "71bdc379-384a-4eac-9627-364ea847d977"
186+
assert isinstance(result["content"], str)
187+
assert "Ratings" in result["content"]
188+
189+
def test_visualization_declarative_to_aac_inline(self) -> None:
190+
"""Test declarative → AAC for a visualization built from inline data."""
191+
aac_input = {
192+
"type": "table",
193+
"id": "my_table",
194+
"title": "My Table",
195+
"query": {
196+
"fields": {
197+
"m1": {
198+
"title": "Sum of Amount",
199+
"aggregation": "SUM",
200+
"using": "fact/amount",
201+
},
202+
},
203+
},
204+
"metrics": [{"field": "m1", "format": "#,##0"}],
205+
}
206+
declarative = aac_visualization_to_declarative(aac_input)
207+
result = declarative_visualization_to_aac(declarative)
208+
assert result["json"]["id"] == "my_table"
209+
assert isinstance(result["content"], str)
210+
assert "my_table" in result["content"]
211+
179212

180213
# ---------------------------------------------------------------------------
181214
# Format detection tests
@@ -263,6 +296,68 @@ def test_store_and_reload_metrics(self, tmp_path: Path) -> None:
263296
reloaded_dict = reloaded.to_dict(camel_case=True)
264297
assert len(reloaded_dict.get("analytics", {}).get("metrics", [])) == 2
265298

299+
def test_store_and_reload_visualizations(self, tmp_path: Path) -> None:
300+
"""Store visualization AAC files via store_aac_workspace_to_disk, then reload."""
301+
aac_vis = {
302+
"type": "table",
303+
"id": "test_vis",
304+
"title": "Test Visualization",
305+
"query": {
306+
"fields": {
307+
"m1": {
308+
"title": "Sum of Amount",
309+
"aggregation": "SUM",
310+
"using": "fact/amount",
311+
},
312+
},
313+
},
314+
"metrics": [{"field": "m1", "format": "#,##0"}],
315+
}
316+
vis_declarative = [aac_visualization_to_declarative(aac_vis)]
317+
318+
model_dict = {"analytics": {"visualizationObjects": vis_declarative}}
319+
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import (
320+
CatalogDeclarativeWorkspaceModel,
321+
)
322+
323+
model = CatalogDeclarativeWorkspaceModel.from_dict(model_dict)
324+
store_aac_workspace_to_disk(model, tmp_path)
325+
326+
# Verify files were created
327+
vis_files = list((tmp_path / "visualisations").glob("*.yaml"))
328+
assert len(vis_files) == 1
329+
330+
# Reload
331+
reloaded = load_aac_workspace_from_disk(tmp_path)
332+
reloaded_dict = reloaded.to_dict(camel_case=True)
333+
assert len(reloaded_dict.get("analytics", {}).get("visualizationObjects", [])) == 1
334+
335+
def test_store_and_reload_from_fixtures(self, tmp_path: Path) -> None:
336+
"""Load fixtures, store to disk, reload — full round-trip."""
337+
import shutil
338+
import tempfile
339+
340+
with tempfile.TemporaryDirectory() as tmp:
341+
fixture_path = Path(tmp)
342+
for subdir in ("datasets", "metrics", "visualisations"):
343+
src = _FIXTURES_DIR / subdir
344+
if src.exists():
345+
shutil.copytree(src, fixture_path / subdir)
346+
347+
model = load_aac_workspace_from_disk(fixture_path)
348+
349+
store_aac_workspace_to_disk(model, tmp_path)
350+
351+
# Verify visualization files exist
352+
vis_files = list((tmp_path / "visualisations").glob("*.yaml"))
353+
assert len(vis_files) == 2
354+
355+
# Reload and verify counts match
356+
reloaded = load_aac_workspace_from_disk(tmp_path)
357+
reloaded_dict = reloaded.to_dict(camel_case=True)
358+
assert len(reloaded_dict.get("analytics", {}).get("visualizationObjects", [])) == 2
359+
assert len(reloaded_dict.get("analytics", {}).get("metrics", [])) == 1
360+
266361
def test_load_ignores_non_workspace_dirs(self, tmp_path: Path) -> None:
267362
"""Ensure load skips declarative non-workspace directories."""
268363
# Create AAC file

0 commit comments

Comments
 (0)