Skip to content

Commit

Permalink
feat: add support for FilterView management
Browse files Browse the repository at this point in the history
Enabling management of FilterViews via pythonSDK

JIRA: LX-428
risk: low
  • Loading branch information
chrisbonilla95 committed Nov 12, 2024
1 parent d20db45 commit 27300fa
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 1 deletion.
3 changes: 3 additions & 0 deletions gooddata-sdk/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
from gooddata_sdk.catalog.identifier import (
CatalogAssigneeIdentifier,
CatalogDatasetWorkspaceDataFilterIdentifier,
CatalogDeclarativeAnalyticalDashboardIdentifier,
CatalogExportDefinitionIdentifier,
CatalogNotificationChannelIdentifier,
CatalogUserIdentifier,
Expand Down Expand Up @@ -175,6 +176,8 @@
CatalogDeclarativeModel,
)
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import (
CatalogDeclarativeFilterView,
CatalogDeclarativeFilterViews,
CatalogDeclarativeUserDataFilter,
CatalogDeclarativeUserDataFilters,
CatalogDeclarativeWorkspace,
Expand Down
15 changes: 14 additions & 1 deletion gooddata-sdk/gooddata_sdk/catalog/identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from attrs import define
from gooddata_api_client.model.assignee_identifier import AssigneeIdentifier
from gooddata_api_client.model.dataset_workspace_data_filter_identifier import DatasetWorkspaceDataFilterIdentifier
from gooddata_api_client.model.declarative_analytical_dashboard_identifier import (
DeclarativeAnalyticalDashboardIdentifier,
)
from gooddata_api_client.model.declarative_export_definition_identifier import DeclarativeExportDefinitionIdentifier
from gooddata_api_client.model.declarative_notification_channel_identifier import (
DeclarativeNotificationChannelIdentifier,
Expand Down Expand Up @@ -82,7 +85,7 @@ def client_class() -> builtins.type[DeclarativeUserIdentifier]:
@attr.s(auto_attribs=True, kw_only=True)
class CatalogLabelIdentifier(Base):
id: str
type: str = attr.field(validator=value_in_allowed)
type: str = attr.field(default="analyticalDashboard", validator=value_in_allowed)

@staticmethod
def client_class() -> builtins.type[LabelIdentifier]:
Expand Down Expand Up @@ -114,3 +117,13 @@ class CatalogNotificationChannelIdentifier(Base):
@staticmethod
def client_class() -> builtins.type[DeclarativeNotificationChannelIdentifier]:
return DeclarativeNotificationChannelIdentifier


@attr.s(auto_attribs=True, kw_only=True)
class CatalogDeclarativeAnalyticalDashboardIdentifier(Base):
id: str
type: str = attr.field(validator=value_in_allowed)

@staticmethod
def client_class() -> builtins.type[DeclarativeAnalyticalDashboardIdentifier]:
return DeclarativeAnalyticalDashboardIdentifier
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any, Optional

import attr
from gooddata_api_client.model.declarative_filter_view import DeclarativeFilterView
from gooddata_api_client.model.declarative_user_data_filter import DeclarativeUserDataFilter
from gooddata_api_client.model.declarative_user_data_filters import DeclarativeUserDataFilters
from gooddata_api_client.model.declarative_workspace import DeclarativeWorkspace
Expand All @@ -17,6 +18,7 @@

from gooddata_sdk.catalog.base import Base
from gooddata_sdk.catalog.identifier import (
CatalogDeclarativeAnalyticalDashboardIdentifier,
CatalogDeclarativeUserGroupIdentifier,
CatalogUserIdentifier,
CatalogWorkspaceIdentifier,
Expand All @@ -36,6 +38,7 @@
LAYOUT_WORKSPACES_DIR = "workspaces"
LAYOUT_WORKSPACES_DATA_FILTERS_DIR = "workspaces_data_filters"
LAYOUT_USER_DATA_FILTERS_DIR = "user_data_filters"
LAYOUT_FILTER_VIEWS_DIR = "filter_views"


def get_workspace_folder(workspace_id: str, layout_organization_folder: Path) -> Path:
Expand Down Expand Up @@ -85,6 +88,7 @@ class CatalogDeclarativeWorkspace(Base):
user_data_filters: list[CatalogDeclarativeUserDataFilter] = attr.field(factory=list)
custom_application_settings: list[CatalogDeclarativeCustomApplicationSetting] = attr.field(factory=list)
automations: list[CatalogDeclarativeAutomation] = attr.field(factory=list)
filter_views: Optional[list[CatalogDeclarativeFilterView]] = None

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


@attr.s(auto_attribs=True, kw_only=True)
class CatalogDeclarativeFilterViews(Base):
filter_views: list[CatalogDeclarativeFilterView]

@staticmethod
def client_class() -> type[list[DeclarativeFilterView]]:
return list[DeclarativeFilterView]

def store_to_disk(self, layout_organization_folder: Path) -> None:
filter_views_folder = CatalogDeclarativeWorkspaces.filter_views_folder(layout_organization_folder)
create_directory(filter_views_folder)
for filter_view in self.filter_views:
filter_view.store_to_disk(filter_views_folder)

@classmethod
def load_from_disk(cls, layout_organization_folder: Path) -> CatalogDeclarativeFilterViews:
filter_views_files = get_sorted_yaml_files(
CatalogDeclarativeWorkspaces.filter_views_folder(layout_organization_folder)
)
filter_views = [
CatalogDeclarativeFilterView.load_from_disk(filter_views_file) for filter_views_file in filter_views_files
]
return cls(filter_views=filter_views)


@attr.s(auto_attribs=True, kw_only=True)
class CatalogDeclarativeFilterView(Base):
id: str
title: str
analytical_dashboard: Optional[CatalogDeclarativeAnalyticalDashboardIdentifier] = None
content: Optional[dict[str, Any]] = None
description: Optional[str] = None
is_default: Optional[bool] = None
tags: Optional[list[str]] = None
user: Optional[CatalogUserIdentifier] = None

@staticmethod
def client_class() -> type[DeclarativeFilterView]:
return DeclarativeFilterView

def store_to_disk(self, filter_views_folder: Path) -> None:
filter_view_file = filter_views_folder / f"{self.id}.yaml"
write_layout_to_file(filter_view_file, self.to_api().to_dict(camel_case=True))

@classmethod
def load_from_disk(cls, filter_view_file: Path) -> CatalogDeclarativeFilterView:
filter_view = read_layout_from_file(filter_view_file)
return CatalogDeclarativeFilterView.from_dict(filter_view, camel_case=True)

@classmethod
def from_dict(cls, data: dict[str, Any], camel_case: bool = True) -> CatalogDeclarativeFilterView:
"""
:param data: Data loaded for example from the file.
:param camel_case: True if the variable names in the input
data are serialized names as specified in the OpenAPI document.
False if the variables names in the input data are python
variable names in PEP-8 snake case.
:return: CatalogDeclarativeFilterView object.
"""
declarative_filter_view = DeclarativeFilterView.from_dict(data, camel_case)
return cls.from_api(declarative_filter_view)


@attr.s(auto_attribs=True, kw_only=True)
class CatalogDeclarativeWorkspaces(Base):
workspaces: list[CatalogDeclarativeWorkspace]
Expand All @@ -305,6 +372,10 @@ def workspace_data_filters_folder(layout_organization_folder: Path) -> Path:
def user_data_filters_folder(layout_organization_folder: Path) -> Path:
return layout_organization_folder / LAYOUT_USER_DATA_FILTERS_DIR

@staticmethod
def filter_views_folder(layout_organization_folder: Path) -> Path:
return layout_organization_folder / LAYOUT_FILTER_VIEWS_DIR

def store_to_disk(self, layout_organization_folder: Path) -> None:
workspaces_folder = self.workspaces_folder(layout_organization_folder)
workspaces_data_filters_folder = self.workspace_data_filters_folder(layout_organization_folder)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# (C) 2024 GoodData Corporation
from __future__ import annotations

from typing import Any, Optional

import attr
from gooddata_api_client.model.json_api_filter_view_in import JsonApiFilterViewIn
from gooddata_api_client.model.json_api_filter_view_in_attributes import JsonApiFilterViewInAttributes
from gooddata_api_client.model.json_api_filter_view_in_document import JsonApiFilterViewInDocument
from gooddata_api_client.model.json_api_filter_view_in_relationships import JsonApiFilterViewInRelationships

from gooddata_sdk.catalog.base import Base


@attr.s(auto_attribs=True, kw_only=True)
class CatalogFilterViewDocument(Base):
data: CatalogFilterView

@staticmethod
def client_class() -> type[JsonApiFilterViewInDocument]:
return JsonApiFilterViewInDocument

def to_api(self) -> JsonApiFilterViewInDocument:
return JsonApiFilterViewInDocument(data=self.data.to_api())


def _data_entity(value: Any) -> dict[str, Any]:
return {"data": value}


@attr.s(auto_attribs=True, kw_only=True)
class CatalogFilterView(Base):
id: Optional[str] = None
attributes: CatalogFilterViewAttributes
relationships: Optional[CatalogFilterViewRelationships] = None

@staticmethod
def client_class() -> type[JsonApiFilterViewIn]:
return JsonApiFilterViewIn

@classmethod
def init(
cls,
filter_view_id: str,
content: dict[str, Any],
title: str,
are_relations_valid: Optional[bool] = None,
description: Optional[str] = None,
is_default: Optional[bool] = None,
tags: Optional[list[str]] = None,
user_id: Optional[str] = None,
analytical_dashboard_id: Optional[str] = None,
) -> CatalogFilterView:
attributes = CatalogFilterViewAttributes(
content=content,
title=title,
are_relations_valid=are_relations_valid,
description=description,
is_default=is_default,
tags=tags,
)
relationships = CatalogFilterViewRelationships.create_user_analytical_dashboard_relationship(
user_id=user_id, analytical_dashboard_id=analytical_dashboard_id
)
return cls(id=filter_view_id, attributes=attributes, relationships=relationships)

def to_api(self) -> JsonApiFilterViewIn:
attributes = self.attributes.to_api()
relationships = self.relationships.to_api() if self.relationships is not None else None
return JsonApiFilterViewIn(id=self.id, attributes=attributes, relationships=relationships)

@property
def user_id(self) -> str | None:
if self.relationships and self.relationships.user:
return self.relationships.user["data"].id
return None

@property
def analytical_dashboard_id(self) -> str | None:
if self.relationships and self.relationships.analytical_dashboard:
return self.relationships.analytical_dashboard["data"].id
return None

def assign_user(self, user_id: str) -> None:
if self.relationships is None:
self.relationships = CatalogFilterViewRelationships.create_user_analytical_dashboard_relationship(
user_id=user_id
)
else:
self.relationships.user = _data_entity(CatalogEntityIdentifier(id=user_id))

def assign_analytical_dashboard(self, analytical_dashboard_id: str) -> None:
if self.relationships is None:
self.relationships = CatalogFilterViewRelationships.create_user_analytical_dashboard_relationship(
analytical_dashboard_id=analytical_dashboard_id
)
else:
self.relationships.analytical_dashboard = _data_entity(CatalogEntityIdentifier(id=analytical_dashboard_id))

def clean_assignments(self) -> None:
if self.relationships is not None:
self.relationships.user = None
self.relationships.analytical_dashboard = None


@attr.s(auto_attribs=True, kw_only=True)
class CatalogFilterViewAttributes(Base):
content: dict[str, Any]
title: str
are_relations_valid: Optional[bool] = None
description: Optional[str] = None
is_default: Optional[bool] = None
tags: Optional[list[str]] = None

@staticmethod
def client_class() -> type[JsonApiFilterViewInAttributes]:
return JsonApiFilterViewInAttributes


@attr.s(auto_attribs=True, kw_only=True)
class CatalogFilterViewRelationships(Base):
user: Optional[dict[str, CatalogEntityIdentifier]] = None
analytical_dashboard: Optional[dict[str, CatalogEntityIdentifier]] = None

@staticmethod
def client_class() -> type[JsonApiFilterViewInRelationships]:
return JsonApiFilterViewInRelationships

@classmethod
def create_user_analytical_dashboard_relationship(
cls, user_id: Optional[str] = None, analytical_dashboard_id: Optional[str] = None
) -> CatalogFilterViewRelationships | None:
if user_id is None and analytical_dashboard_id is None:
return None
assignee_user = _data_entity(CatalogEntityIdentifier(id=user_id, type="user")) if user_id else None
assignee_analytical_dashboard = (
_data_entity(CatalogEntityIdentifier(id=analytical_dashboard_id, type="analyticalDashboard"))
if analytical_dashboard_id
else None
)
return cls(user=assignee_user, analytical_dashboard=assignee_analytical_dashboard)


@attr.s(auto_attribs=True, kw_only=True)
class CatalogEntityIdentifier(Base):
id: str
type: Optional[str] = None
Loading

0 comments on commit 27300fa

Please sign in to comment.