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
30 changes: 24 additions & 6 deletions packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/aac.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,32 @@ def declarative_metric_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
return declarative_metric_to_yaml(declarative)


def declarative_visualization_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
"""Convert a declarative visualization dict to AAC format."""
return declarative_visualisation_to_yaml(declarative)
def declarative_visualization_to_aac(
declarative: dict[str, Any],
entities: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
"""Convert a declarative visualization dict to AAC format.

Args:
declarative: The declarative visualization dict.
entities: Optional entities list for cross-reference resolution.
"""
ent = entities if entities is not None else []
return declarative_visualisation_to_yaml(ent, declarative)


def declarative_dashboard_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
"""Convert a declarative dashboard dict to AAC format."""
return declarative_dashboard_to_yaml(declarative)
def declarative_dashboard_to_aac(
declarative: dict[str, Any],
entities: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
"""Convert a declarative dashboard dict to AAC format.

Args:
declarative: The declarative dashboard dict.
entities: Optional entities list for cross-reference resolution.
"""
ent = entities if entities is not None else []
return declarative_dashboard_to_yaml(ent, declarative)


def declarative_plugin_to_aac(declarative: dict[str, Any]) -> dict[str, Any]:
Expand Down
95 changes: 95 additions & 0 deletions packages/gooddata-sdk/tests/catalog/test_aac.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
aac_visualization_to_declarative,
declarative_dataset_to_aac,
declarative_metric_to_aac,
declarative_visualization_to_aac,
detect_yaml_format,
load_aac_workspace_from_disk,
store_aac_workspace_to_disk,
Expand Down Expand Up @@ -176,6 +177,38 @@ def test_dataset_declarative_to_aac(self) -> None:
assert result["json"]["id"] == "orders"
assert isinstance(result["content"], str)

def test_visualization_declarative_to_aac(self) -> None:
"""Test declarative → AAC for visualizations (round-trip from fixture)."""
content = yaml.safe_load((_FIXTURES_DIR / "visualisations" / "ratings.yaml").read_text())
declarative = aac_visualization_to_declarative(content)
result = declarative_visualization_to_aac(declarative)
assert result["json"]["id"] == "71bdc379-384a-4eac-9627-364ea847d977"
assert isinstance(result["content"], str)
assert "Ratings" in result["content"]

def test_visualization_declarative_to_aac_inline(self) -> None:
"""Test declarative → AAC for a visualization built from inline data."""
aac_input = {
"type": "table",
"id": "my_table",
"title": "My Table",
"query": {
"fields": {
"m1": {
"title": "Sum of Amount",
"aggregation": "SUM",
"using": "fact/amount",
},
},
},
"metrics": [{"field": "m1", "format": "#,##0"}],
}
declarative = aac_visualization_to_declarative(aac_input)
result = declarative_visualization_to_aac(declarative)
assert result["json"]["id"] == "my_table"
assert isinstance(result["content"], str)
assert "my_table" in result["content"]


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

def test_store_and_reload_visualizations(self, tmp_path: Path) -> None:
"""Store visualization AAC files via store_aac_workspace_to_disk, then reload."""
aac_vis = {
"type": "table",
"id": "test_vis",
"title": "Test Visualization",
"query": {
"fields": {
"m1": {
"title": "Sum of Amount",
"aggregation": "SUM",
"using": "fact/amount",
},
},
},
"metrics": [{"field": "m1", "format": "#,##0"}],
}
vis_declarative = [aac_visualization_to_declarative(aac_vis)]

model_dict = {"analytics": {"visualizationObjects": vis_declarative}}
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import (
CatalogDeclarativeWorkspaceModel,
)

model = CatalogDeclarativeWorkspaceModel.from_dict(model_dict)
store_aac_workspace_to_disk(model, tmp_path)

# Verify files were created
vis_files = list((tmp_path / "visualisations").glob("*.yaml"))
assert len(vis_files) == 1

# Reload
reloaded = load_aac_workspace_from_disk(tmp_path)
reloaded_dict = reloaded.to_dict(camel_case=True)
assert len(reloaded_dict.get("analytics", {}).get("visualizationObjects", [])) == 1

def test_store_and_reload_from_fixtures(self, tmp_path: Path) -> None:
"""Load fixtures, store to disk, reload — full round-trip."""
import shutil
import tempfile

with tempfile.TemporaryDirectory() as tmp:
fixture_path = Path(tmp)
for subdir in ("datasets", "metrics", "visualisations"):
src = _FIXTURES_DIR / subdir
if src.exists():
shutil.copytree(src, fixture_path / subdir)

model = load_aac_workspace_from_disk(fixture_path)

store_aac_workspace_to_disk(model, tmp_path)

# Verify visualization files exist
vis_files = list((tmp_path / "visualisations").glob("*.yaml"))
assert len(vis_files) == 2

# Reload and verify counts match
reloaded = load_aac_workspace_from_disk(tmp_path)
reloaded_dict = reloaded.to_dict(camel_case=True)
assert len(reloaded_dict.get("analytics", {}).get("visualizationObjects", [])) == 2
assert len(reloaded_dict.get("analytics", {}).get("metrics", [])) == 1

def test_load_ignores_non_workspace_dirs(self, tmp_path: Path) -> None:
"""Ensure load skips declarative non-workspace directories."""
# Create AAC file
Expand Down
Loading