Skip to content

Commit cc5a4dd

Browse files
Merge pull request #909 from gooddata/snapshot-master-eda1f3c6-to-rel/dev
[bot] Merge master/eda1f3c6 into rel/dev
2 parents ef9a81b + eda1f3c commit cc5a4dd

36 files changed

+1005
-18
lines changed

gooddata-sdk/gooddata_sdk/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
from gooddata_sdk.catalog.identifier import (
7272
CatalogAssigneeIdentifier,
7373
CatalogDatasetWorkspaceDataFilterIdentifier,
74+
CatalogDeclarativeAnalyticalDashboardIdentifier,
7475
CatalogExportDefinitionIdentifier,
7576
CatalogNotificationChannelIdentifier,
7677
CatalogUserIdentifier,
@@ -175,6 +176,7 @@
175176
CatalogDeclarativeModel,
176177
)
177178
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import (
179+
CatalogDeclarativeFilterView,
178180
CatalogDeclarativeUserDataFilter,
179181
CatalogDeclarativeUserDataFilters,
180182
CatalogDeclarativeWorkspace,

gooddata-sdk/gooddata_sdk/catalog/identifier.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from attrs import define
88
from gooddata_api_client.model.assignee_identifier import AssigneeIdentifier
99
from gooddata_api_client.model.dataset_workspace_data_filter_identifier import DatasetWorkspaceDataFilterIdentifier
10+
from gooddata_api_client.model.declarative_analytical_dashboard_identifier import (
11+
DeclarativeAnalyticalDashboardIdentifier,
12+
)
1013
from gooddata_api_client.model.declarative_export_definition_identifier import DeclarativeExportDefinitionIdentifier
1114
from gooddata_api_client.model.declarative_notification_channel_identifier import (
1215
DeclarativeNotificationChannelIdentifier,
@@ -114,3 +117,13 @@ class CatalogNotificationChannelIdentifier(Base):
114117
@staticmethod
115118
def client_class() -> builtins.type[DeclarativeNotificationChannelIdentifier]:
116119
return DeclarativeNotificationChannelIdentifier
120+
121+
122+
@attr.s(auto_attribs=True, kw_only=True)
123+
class CatalogDeclarativeAnalyticalDashboardIdentifier(Base):
124+
id: str
125+
type: str = attr.field(validator=value_in_allowed)
126+
127+
@staticmethod
128+
def client_class() -> builtins.type[DeclarativeAnalyticalDashboardIdentifier]:
129+
return DeclarativeAnalyticalDashboardIdentifier

gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, Optional
77

88
import attr
9+
from gooddata_api_client.model.declarative_filter_view import DeclarativeFilterView
910
from gooddata_api_client.model.declarative_user_data_filter import DeclarativeUserDataFilter
1011
from gooddata_api_client.model.declarative_user_data_filters import DeclarativeUserDataFilters
1112
from gooddata_api_client.model.declarative_workspace import DeclarativeWorkspace
@@ -17,6 +18,7 @@
1718

1819
from gooddata_sdk.catalog.base import Base
1920
from gooddata_sdk.catalog.identifier import (
21+
CatalogDeclarativeAnalyticalDashboardIdentifier,
2022
CatalogDeclarativeUserGroupIdentifier,
2123
CatalogUserIdentifier,
2224
CatalogWorkspaceIdentifier,
@@ -36,6 +38,7 @@
3638
LAYOUT_WORKSPACES_DIR = "workspaces"
3739
LAYOUT_WORKSPACES_DATA_FILTERS_DIR = "workspaces_data_filters"
3840
LAYOUT_USER_DATA_FILTERS_DIR = "user_data_filters"
41+
LAYOUT_FILTER_VIEWS_DIR = "filter_views"
3942

4043

4144
def get_workspace_folder(workspace_id: str, layout_organization_folder: Path) -> Path:
@@ -85,6 +88,7 @@ class CatalogDeclarativeWorkspace(Base):
8588
user_data_filters: list[CatalogDeclarativeUserDataFilter] = attr.field(factory=list)
8689
custom_application_settings: list[CatalogDeclarativeCustomApplicationSetting] = attr.field(factory=list)
8790
automations: list[CatalogDeclarativeAutomation] = attr.field(factory=list)
91+
filter_views: list[CatalogDeclarativeFilterView] = attr.field(factory=list)
8892

8993
@staticmethod
9094
def client_class() -> type[DeclarativeWorkspace]:
@@ -211,12 +215,13 @@ def load_from_disk(cls, workspaces_data_filter_file: Path) -> CatalogDeclarative
211215
@classmethod
212216
def from_dict(cls, data: dict[str, Any], camel_case: bool = True) -> CatalogDeclarativeWorkspaceDataFilter:
213217
"""
214-
:param data: Data loaded for example from the file.
215-
:param camel_case: True if the variable names in the input
216-
data are serialized names as specified in the OpenAPI document.
217-
False if the variables names in the input data are python
218-
variable names in PEP-8 snake case.
219-
:return: CatalogDeclarativeWorkspaceDataFilter object.
218+
Args:
219+
data (dict[str, Any]): Data loaded, for example, from a file.
220+
camel_case (bool): True if the variable names in the input data are serialized names as specified in the OpenAPI document.
221+
False if the variable names in the input data are Python variable names in PEP-8 snake case.
222+
223+
Returns:
224+
CatalogDeclarativeWorkspaceDataFilter: CatalogDeclarativeWorkspaceDataFilter object.
220225
"""
221226
declarative_workspace_data_filter = DeclarativeWorkspaceDataFilter.from_dict(data, camel_case)
222227
return cls.from_api(declarative_workspace_data_filter)
@@ -273,17 +278,61 @@ def load_from_disk(cls, user_data_filter_file: Path) -> CatalogDeclarativeUserDa
273278
@classmethod
274279
def from_dict(cls, data: dict[str, Any], camel_case: bool = True) -> CatalogDeclarativeUserDataFilter:
275280
"""
276-
:param data: Data loaded for example from the file.
277-
:param camel_case: True if the variable names in the input
278-
data are serialized names as specified in the OpenAPI document.
279-
False if the variables names in the input data are python
280-
variable names in PEP-8 snake case.
281-
:return: CatalogDeclarativeUserDataFilter object.
281+
Args:
282+
data (dict[str, Any]): Data loaded, for example, from a file.
283+
camel_case (bool): True if the variable names in the input data are serialized names as specified in the OpenAPI document.
284+
False if the variable names in the input data are Python variable names in PEP-8 snake case.
285+
286+
Returns:
287+
CatalogDeclarativeUserDataFilter: CatalogDeclarativeUserDataFilter object.
282288
"""
283289
declarative_user_data_filter = DeclarativeUserDataFilter.from_dict(data, camel_case)
284290
return cls.from_api(declarative_user_data_filter)
285291

286292

293+
@attr.s(auto_attribs=True, kw_only=True)
294+
class CatalogDeclarativeFilterView(Base):
295+
id: str
296+
title: str
297+
analytical_dashboard: Optional[CatalogDeclarativeAnalyticalDashboardIdentifier] = None
298+
content: Optional[dict[str, Any]] = None
299+
description: Optional[str] = None
300+
is_default: Optional[bool] = None
301+
tags: Optional[list[str]] = None
302+
user: Optional[CatalogUserIdentifier] = None
303+
304+
@staticmethod
305+
def client_class() -> type[DeclarativeFilterView]:
306+
return DeclarativeFilterView
307+
308+
def store_to_disk(self, filter_views_folder: Path) -> None:
309+
filter_view_file = filter_views_folder / f"{self.id}.yaml"
310+
write_layout_to_file(filter_view_file, self.to_api().to_dict(camel_case=True))
311+
312+
@classmethod
313+
def load_from_disk(cls, filter_view_file: Path) -> CatalogDeclarativeFilterView:
314+
filter_view = read_layout_from_file(filter_view_file)
315+
return CatalogDeclarativeFilterView.from_dict(filter_view, camel_case=True)
316+
317+
@classmethod
318+
def store_filter_views_to_disk(
319+
cls, filter_views: list[CatalogDeclarativeFilterView], layout_organization_folder: Path
320+
) -> None:
321+
filter_views_folder = CatalogDeclarativeWorkspaces.filter_views_folder(layout_organization_folder)
322+
create_directory(filter_views_folder)
323+
for filter_view in filter_views:
324+
filter_view.store_to_disk(filter_views_folder)
325+
326+
@classmethod
327+
def load_filter_views_from_disk(cls, layout_organization_folder: Path) -> list[CatalogDeclarativeFilterView]:
328+
filter_views_files = get_sorted_yaml_files(
329+
CatalogDeclarativeWorkspaces.filter_views_folder(layout_organization_folder)
330+
)
331+
return [
332+
CatalogDeclarativeFilterView.load_from_disk(filter_views_file) for filter_views_file in filter_views_files
333+
]
334+
335+
287336
@attr.s(auto_attribs=True, kw_only=True)
288337
class CatalogDeclarativeWorkspaces(Base):
289338
workspaces: list[CatalogDeclarativeWorkspace]
@@ -305,6 +354,10 @@ def workspace_data_filters_folder(layout_organization_folder: Path) -> Path:
305354
def user_data_filters_folder(layout_organization_folder: Path) -> Path:
306355
return layout_organization_folder / LAYOUT_USER_DATA_FILTERS_DIR
307356

357+
@staticmethod
358+
def filter_views_folder(layout_organization_folder: Path) -> Path:
359+
return layout_organization_folder / LAYOUT_FILTER_VIEWS_DIR
360+
308361
def store_to_disk(self, layout_organization_folder: Path) -> None:
309362
workspaces_folder = self.workspaces_folder(layout_organization_folder)
310363
workspaces_data_filters_folder = self.workspace_data_filters_folder(layout_organization_folder)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# (C) 2024 GoodData Corporation
2+
from __future__ import annotations
3+
4+
from typing import Any, Optional, Union
5+
6+
import attr
7+
from gooddata_api_client.model.json_api_filter_view_in import JsonApiFilterViewIn
8+
from gooddata_api_client.model.json_api_filter_view_in_attributes import JsonApiFilterViewInAttributes
9+
from gooddata_api_client.model.json_api_filter_view_in_document import JsonApiFilterViewInDocument
10+
from gooddata_api_client.model.json_api_filter_view_in_relationships import JsonApiFilterViewInRelationships
11+
12+
from gooddata_sdk.catalog.base import Base
13+
from gooddata_sdk.catalog.identifier import CatalogDeclarativeAnalyticalDashboardIdentifier, CatalogUserIdentifier
14+
15+
16+
@attr.s(auto_attribs=True, kw_only=True)
17+
class CatalogFilterViewDocument(Base):
18+
data: CatalogFilterView
19+
20+
@staticmethod
21+
def client_class() -> type[JsonApiFilterViewInDocument]:
22+
return JsonApiFilterViewInDocument
23+
24+
def to_api(self) -> JsonApiFilterViewInDocument:
25+
return JsonApiFilterViewInDocument(data=self.data.to_api())
26+
27+
28+
def _data_entity(value: Any) -> dict[str, Any]:
29+
return {"data": value}
30+
31+
32+
@attr.s(auto_attribs=True, kw_only=True)
33+
class CatalogFilterView(Base):
34+
id: Optional[str] = None
35+
attributes: CatalogFilterViewAttributes
36+
relationships: Optional[CatalogFilterViewRelationships] = None
37+
38+
@staticmethod
39+
def client_class() -> type[JsonApiFilterViewIn]:
40+
return JsonApiFilterViewIn
41+
42+
@classmethod
43+
def init(
44+
cls,
45+
filter_view_id: str,
46+
content: dict[str, Any],
47+
title: str,
48+
are_relations_valid: Optional[bool] = None,
49+
description: Optional[str] = None,
50+
is_default: Optional[bool] = None,
51+
tags: Optional[list[str]] = None,
52+
user_id: Optional[str] = None,
53+
analytical_dashboard_id: Optional[str] = None,
54+
) -> CatalogFilterView:
55+
attributes = CatalogFilterViewAttributes(
56+
content=content,
57+
title=title,
58+
are_relations_valid=are_relations_valid,
59+
description=description,
60+
is_default=is_default,
61+
tags=tags,
62+
)
63+
relationships = CatalogFilterViewRelationships.create_user_analytical_dashboard_relationship(
64+
user_id=user_id, analytical_dashboard_id=analytical_dashboard_id
65+
)
66+
return cls(id=filter_view_id, attributes=attributes, relationships=relationships)
67+
68+
def to_api(self) -> JsonApiFilterViewIn:
69+
attributes = self.attributes.to_api()
70+
relationships = self.relationships.to_api() if self.relationships is not None else None
71+
return JsonApiFilterViewIn(id=self.id, attributes=attributes, relationships=relationships)
72+
73+
@property
74+
def user_id(self) -> Union[str, None]:
75+
if self.relationships and self.relationships.user:
76+
return self.relationships.user["data"].id
77+
return None
78+
79+
@property
80+
def analytical_dashboard_id(self) -> Union[str, None]:
81+
if self.relationships and self.relationships.analytical_dashboard:
82+
return self.relationships.analytical_dashboard["data"].id
83+
return None
84+
85+
def assign_user(self, user_id: str) -> None:
86+
if self.relationships is None:
87+
self.relationships = CatalogFilterViewRelationships.create_user_analytical_dashboard_relationship(
88+
user_id=user_id
89+
)
90+
else:
91+
self.relationships.user = _data_entity(CatalogUserIdentifier(id=user_id, type="user"))
92+
93+
def assign_analytical_dashboard(self, analytical_dashboard_id: str) -> None:
94+
if self.relationships is None:
95+
self.relationships = CatalogFilterViewRelationships.create_user_analytical_dashboard_relationship(
96+
analytical_dashboard_id=analytical_dashboard_id
97+
)
98+
else:
99+
self.relationships.analytical_dashboard = _data_entity(
100+
CatalogDeclarativeAnalyticalDashboardIdentifier(id=analytical_dashboard_id, type="analyticalDashboard")
101+
)
102+
103+
def clean_relationships(self) -> None:
104+
if self.relationships is not None:
105+
self.relationships.user = None
106+
self.relationships.analytical_dashboard = None
107+
108+
109+
@attr.s(auto_attribs=True, kw_only=True)
110+
class CatalogFilterViewAttributes(Base):
111+
content: dict[str, Any]
112+
title: str
113+
are_relations_valid: Optional[bool] = None
114+
description: Optional[str] = None
115+
is_default: Optional[bool] = None
116+
tags: Optional[list[str]] = None
117+
118+
@staticmethod
119+
def client_class() -> type[JsonApiFilterViewInAttributes]:
120+
return JsonApiFilterViewInAttributes
121+
122+
123+
@attr.s(auto_attribs=True, kw_only=True)
124+
class CatalogFilterViewRelationships(Base):
125+
user: Optional[dict[str, CatalogUserIdentifier]] = None
126+
analytical_dashboard: Optional[dict[str, CatalogDeclarativeAnalyticalDashboardIdentifier]] = None
127+
128+
@staticmethod
129+
def client_class() -> type[JsonApiFilterViewInRelationships]:
130+
return JsonApiFilterViewInRelationships
131+
132+
@classmethod
133+
def create_user_analytical_dashboard_relationship(
134+
cls, user_id: Optional[str] = None, analytical_dashboard_id: Optional[str] = None
135+
) -> CatalogFilterViewRelationships | None:
136+
if user_id is None and analytical_dashboard_id is None:
137+
return None
138+
assignee_user = _data_entity(CatalogUserIdentifier(id=user_id, type="user")) if user_id else None
139+
assignee_analytical_dashboard = (
140+
_data_entity(
141+
CatalogDeclarativeAnalyticalDashboardIdentifier(id=analytical_dashboard_id, type="analyticalDashboard")
142+
)
143+
if analytical_dashboard_id
144+
else None
145+
)
146+
return cls(user=assignee_user, analytical_dashboard=assignee_analytical_dashboard)

gooddata-sdk/gooddata_sdk/catalog/workspace/entity_model/user_data_filter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ def to_api(self, post: bool = False) -> Union[JsonApiUserDataFilterPostOptionalI
9191
return JsonApiUserDataFilterIn(id=self.id, attributes=attributes, relationships=relationships)
9292

9393
@property
94-
def user_id(self) -> str | None:
94+
def user_id(self) -> Union[str, None]:
9595
if self.relationships and self.relationships.user:
9696
return self.relationships.user["data"].id
9797
return None
9898

9999
@property
100-
def user_group_id(self) -> str | None:
100+
def user_group_id(self) -> Union[str, None]:
101101
if self.relationships and self.relationships.user_group:
102102
return self.relationships.user_group["data"].id
103103
return None

0 commit comments

Comments
 (0)