Skip to content

Commit 12bef5e

Browse files
feat: add support for FilterView management
Enabling management of FilterViews via pythonSDK JIRA: LX-428 risk: low
1 parent d20db45 commit 12bef5e

File tree

5 files changed

+402
-1
lines changed

5 files changed

+402
-1
lines changed

gooddata-sdk/gooddata_sdk/__init__.py

Lines changed: 3 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,8 @@
175176
CatalogDeclarativeModel,
176177
)
177178
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import (
179+
CatalogDeclarativeFilterView,
180+
CatalogDeclarativeFilterViews,
178181
CatalogDeclarativeUserDataFilter,
179182
CatalogDeclarativeUserDataFilters,
180183
CatalogDeclarativeWorkspace,

gooddata-sdk/gooddata_sdk/catalog/identifier.py

Lines changed: 14 additions & 1 deletion
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,
@@ -82,7 +85,7 @@ def client_class() -> builtins.type[DeclarativeUserIdentifier]:
8285
@attr.s(auto_attribs=True, kw_only=True)
8386
class CatalogLabelIdentifier(Base):
8487
id: str
85-
type: str = attr.field(validator=value_in_allowed)
88+
type: str = attr.field(default="analyticalDashboard", validator=value_in_allowed)
8689

8790
@staticmethod
8891
def client_class() -> builtins.type[LabelIdentifier]:
@@ -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: 71 additions & 0 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: Optional[list[CatalogDeclarativeFilterView]] = None
8892

8993
@staticmethod
9094
def client_class() -> type[DeclarativeWorkspace]:
@@ -284,6 +288,69 @@ def from_dict(cls, data: dict[str, Any], camel_case: bool = True) -> CatalogDecl
284288
return cls.from_api(declarative_user_data_filter)
285289

286290

291+
@attr.s(auto_attribs=True, kw_only=True)
292+
class CatalogDeclarativeFilterViews(Base):
293+
filter_views: list[CatalogDeclarativeFilterView]
294+
295+
@staticmethod
296+
def client_class() -> type[list[DeclarativeFilterView]]:
297+
return list[DeclarativeFilterView]
298+
299+
def store_to_disk(self, layout_organization_folder: Path) -> None:
300+
filter_views_folder = CatalogDeclarativeWorkspaces.filter_views_folder(layout_organization_folder)
301+
create_directory(filter_views_folder)
302+
for filter_view in self.filter_views:
303+
filter_view.store_to_disk(filter_views_folder)
304+
305+
@classmethod
306+
def load_from_disk(cls, layout_organization_folder: Path) -> CatalogDeclarativeFilterViews:
307+
filter_views_files = get_sorted_yaml_files(
308+
CatalogDeclarativeWorkspaces.filter_views_folder(layout_organization_folder)
309+
)
310+
filter_views = [
311+
CatalogDeclarativeFilterView.load_from_disk(filter_views_file) for filter_views_file in filter_views_files
312+
]
313+
return cls(filter_views=filter_views)
314+
315+
316+
@attr.s(auto_attribs=True, kw_only=True)
317+
class CatalogDeclarativeFilterView(Base):
318+
id: str
319+
title: str
320+
analytical_dashboard: Optional[CatalogDeclarativeAnalyticalDashboardIdentifier] = None
321+
content: Optional[dict[str, Any]] = None
322+
description: Optional[str] = None
323+
is_default: Optional[bool] = None
324+
tags: Optional[list[str]] = None
325+
user: Optional[CatalogUserIdentifier] = None
326+
327+
@staticmethod
328+
def client_class() -> type[DeclarativeFilterView]:
329+
return DeclarativeFilterView
330+
331+
def store_to_disk(self, filter_views_folder: Path) -> None:
332+
filter_view_file = filter_views_folder / f"{self.id}.yaml"
333+
write_layout_to_file(filter_view_file, self.to_api().to_dict(camel_case=True))
334+
335+
@classmethod
336+
def load_from_disk(cls, filter_view_file: Path) -> CatalogDeclarativeFilterView:
337+
filter_view = read_layout_from_file(filter_view_file)
338+
return CatalogDeclarativeFilterView.from_dict(filter_view, camel_case=True)
339+
340+
@classmethod
341+
def from_dict(cls, data: dict[str, Any], camel_case: bool = True) -> CatalogDeclarativeFilterView:
342+
"""
343+
:param data: Data loaded for example from the file.
344+
:param camel_case: True if the variable names in the input
345+
data are serialized names as specified in the OpenAPI document.
346+
False if the variables names in the input data are python
347+
variable names in PEP-8 snake case.
348+
:return: CatalogDeclarativeFilterView object.
349+
"""
350+
declarative_filter_view = DeclarativeFilterView.from_dict(data, camel_case)
351+
return cls.from_api(declarative_filter_view)
352+
353+
287354
@attr.s(auto_attribs=True, kw_only=True)
288355
class CatalogDeclarativeWorkspaces(Base):
289356
workspaces: list[CatalogDeclarativeWorkspace]
@@ -305,6 +372,10 @@ def workspace_data_filters_folder(layout_organization_folder: Path) -> Path:
305372
def user_data_filters_folder(layout_organization_folder: Path) -> Path:
306373
return layout_organization_folder / LAYOUT_USER_DATA_FILTERS_DIR
307374

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

0 commit comments

Comments
 (0)