Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for groupsets #1405

Merged
merged 2 commits into from
Aug 2, 2024
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
2 changes: 2 additions & 0 deletions tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
FlowRunItem,
FileuploadItem,
GroupItem,
GroupSetItem,
HourlyInterval,
IntervalItem,
JobItem,
Expand Down Expand Up @@ -79,6 +80,7 @@
"FlowRunItem",
"FileuploadItem",
"GroupItem",
"GroupSetItem",
"HourlyInterval",
"IntervalItem",
"JobItem",
Expand Down
2 changes: 2 additions & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from tableauserverclient.models.flow_item import FlowItem
from tableauserverclient.models.flow_run_item import FlowRunItem
from tableauserverclient.models.group_item import GroupItem
from tableauserverclient.models.groupset_item import GroupSetItem
from tableauserverclient.models.interval_item import (
IntervalItem,
DailyInterval,
Expand Down Expand Up @@ -60,6 +61,7 @@
"FlowItem",
"FlowRunItem",
"GroupItem",
"GroupSetItem",
"IntervalItem",
"JobItem",
"DailyInterval",
Expand Down
48 changes: 48 additions & 0 deletions tableauserverclient/models/groupset_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Dict, List, Optional
import xml.etree.ElementTree as ET

from defusedxml.ElementTree import fromstring

from tableauserverclient.models.group_item import GroupItem


class GroupSetItem:
tag_name: str = "groupSet"

def __init__(self, name: Optional[str] = None) -> None:
self.name = name
self.id: Optional[str] = None
self.groups: List["GroupItem"] = []
self.group_count: int = 0

def __str__(self) -> str:
name = self.name
id = self.id
return f"<{self.__class__.__qualname__}({name=}, {id=})>"

def __repr__(self) -> str:
return self.__str__()

@classmethod
def from_response(cls, response: bytes, ns: Dict[str, str]) -> List["GroupSetItem"]:
parsed_response = fromstring(response)
all_groupset_xml = parsed_response.findall(".//t:groupSet", namespaces=ns)
return [cls.from_xml(xml, ns) for xml in all_groupset_xml]

@classmethod
def from_xml(cls, groupset_xml: ET.Element, ns: Dict[str, str]) -> "GroupSetItem":
def get_group(group_xml: ET.Element) -> GroupItem:
group_item = GroupItem()
group_item._id = group_xml.get("id")
group_item.name = group_xml.get("name")
return group_item

group_set_item = cls()
group_set_item.name = groupset_xml.get("name")
group_set_item.id = groupset_xml.get("id")
group_set_item.group_count = int(count) if (count := groupset_xml.get("groupCount")) else 0
group_set_item.groups = [
get_group(group_xml) for group_xml in groupset_xml.findall(".//t:group", namespaces=ns)
]

return group_set_item
2 changes: 2 additions & 0 deletions tableauserverclient/server/endpoint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from tableauserverclient.server.endpoint.flows_endpoint import Flows
from tableauserverclient.server.endpoint.flow_task_endpoint import FlowTasks
from tableauserverclient.server.endpoint.groups_endpoint import Groups
from tableauserverclient.server.endpoint.groupsets_endpoint import GroupSets
from tableauserverclient.server.endpoint.jobs_endpoint import Jobs
from tableauserverclient.server.endpoint.metadata_endpoint import Metadata
from tableauserverclient.server.endpoint.metrics_endpoint import Metrics
Expand Down Expand Up @@ -43,6 +44,7 @@
"Flows",
"FlowTasks",
"Groups",
"GroupSets",
"Jobs",
"Metadata",
"Metrics",
Expand Down
87 changes: 87 additions & 0 deletions tableauserverclient/server/endpoint/groupsets_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from typing import List, Literal, Optional, Tuple, TYPE_CHECKING, Union

from tableauserverclient.helpers.logging import logger
from tableauserverclient.models.group_item import GroupItem
from tableauserverclient.models.groupset_item import GroupSetItem
from tableauserverclient.models.pagination_item import PaginationItem
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint
from tableauserverclient.server.request_options import RequestOptions
from tableauserverclient.server.request_factory import RequestFactory
from tableauserverclient.server.endpoint.endpoint import api

if TYPE_CHECKING:
from tableauserverclient.server import Server


class GroupSets(QuerysetEndpoint[GroupSetItem]):
def __init__(self, parent_srv: "Server") -> None:
super().__init__(parent_srv)

@property
def baseurl(self) -> str:
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/groupsets"

@api(version="3.22")
def get(
self,
request_options: Optional[RequestOptions] = None,
result_level: Optional[Literal["members", "local"]] = None,
) -> Tuple[List[GroupSetItem], PaginationItem]:
logger.info("Querying all group sets on site")
url = self.baseurl
if result_level:
url += f"?resultlevel={result_level}"
server_response = self.get_request(url, request_options)
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
all_group_set_items = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
return all_group_set_items, pagination_item

@api(version="3.22")
def get_by_id(self, groupset_id: str) -> GroupSetItem:
logger.info(f"Querying group set (ID: {groupset_id})")
url = f"{self.baseurl}/{groupset_id}"
server_response = self.get_request(url)
all_group_set_items = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
return all_group_set_items[0]

@api(version="3.22")
def create(self, groupset_item: GroupSetItem) -> GroupSetItem:
logger.info(f"Creating group set (name: {groupset_item.name})")
url = self.baseurl
request = RequestFactory.GroupSet.create_request(groupset_item)
server_response = self.post_request(url, request)
created_groupset = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
return created_groupset[0]

@api(version="3.22")
def add_group(self, groupset_item: GroupSetItem, group: Union[GroupItem, str]) -> None:
group_id = group.id if isinstance(group, GroupItem) else group
logger.info(f"Adding group (ID: {group_id}) to group set (ID: {groupset_item.id})")
url = f"{self.baseurl}/{groupset_item.id}/groups/{group_id}"
_ = self.put_request(url)
return None

@api(version="3.22")
def remove_group(self, groupset_item: GroupSetItem, group: Union[GroupItem, str]) -> None:
group_id = group.id if isinstance(group, GroupItem) else group
logger.info(f"Removing group (ID: {group_id}) from group set (ID: {groupset_item.id})")
url = f"{self.baseurl}/{groupset_item.id}/groups/{group_id}"
_ = self.delete_request(url)
return None

@api(version="3.22")
def delete(self, groupset: Union[GroupSetItem, str]) -> None:
groupset_id = groupset.id if isinstance(groupset, GroupSetItem) else groupset
logger.info(f"Deleting group set (ID: {groupset_id})")
url = f"{self.baseurl}/{groupset_id}"
_ = self.delete_request(url)
return None

@api(version="3.22")
def update(self, groupset: GroupSetItem) -> GroupSetItem:
logger.info(f"Updating group set (ID: {groupset.id})")
url = f"{self.baseurl}/{groupset.id}"
request = RequestFactory.GroupSet.update_request(groupset)
server_response = self.put_request(url, request)
updated_groupset = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
return updated_groupset[0]
17 changes: 17 additions & 0 deletions tableauserverclient/server/request_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,22 @@ def update_req(self, xml_request: ET.Element, custom_view_item: CustomViewItem):
updating_element.attrib["name"] = custom_view_item.name


class GroupSetRequest:
@_tsrequest_wrapped
def create_request(self, xml_request: ET.Element, group_set_item: "GroupSetItem") -> bytes:
group_set_element = ET.SubElement(xml_request, "groupSet")
if group_set_item.name is not None:
group_set_element.attrib["name"] = group_set_item.name
return ET.tostring(xml_request)

@_tsrequest_wrapped
def update_request(self, xml_request: ET.Element, group_set_item: "GroupSetItem") -> bytes:
group_set_element = ET.SubElement(xml_request, "groupSet")
if group_set_item.name is not None:
group_set_element.attrib["name"] = group_set_item.name
return ET.tostring(xml_request)


class RequestFactory(object):
Auth = AuthRequest()
Connection = Connection()
Expand All @@ -1261,6 +1277,7 @@ class RequestFactory(object):
Flow = FlowRequest()
FlowTask = FlowTaskRequest()
Group = GroupRequest()
GroupSet = GroupSetRequest()
Metric = MetricRequest()
Permission = PermissionRequest()
Project = ProjectRequest()
Expand Down
2 changes: 2 additions & 0 deletions tableauserverclient/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Metrics,
Endpoint,
CustomViews,
GroupSets,
)
from tableauserverclient.server.exceptions import (
ServerInfoEndpointNotFoundError,
Expand Down Expand Up @@ -99,6 +100,7 @@ def __init__(self, server_address, use_server_version=False, http_options=None,
self.flow_runs = FlowRuns(self)
self.metrics = Metrics(self)
self.custom_views = CustomViews(self)
self.group_sets = GroupSets(self)

self._session = self._session_factory()
self._http_options = dict() # must set this before making a server call
Expand Down
4 changes: 4 additions & 0 deletions test/assets/groupsets_create.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
<groupSet id="1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" name="All Users" groupCount="0" />
</tsResponse>
15 changes: 15 additions & 0 deletions test/assets/groupsets_get.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
<pagination pageNumber="1" pageSize="100" totalAvailable="3"/>
<groupSets>
<groupSet id="1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" name="All Users" groupCount="1">
<group id="gs-1" name="group-one"/>
</groupSet>
<groupSet id="9a8a7b6b-5c4c-3d2d-1e0e-9a8a7b6b5b4b" name="active-directory-group-import" groupCount="1">
<group id="gs21" name="group-two"/>
</groupSet>
<groupSet id="7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6" name="local-group-license-on-login" groupCount="1">
<group id="gs-3" name="group-three"/>
</groupSet>
</groupSets>
</tsResponse>
9 changes: 9 additions & 0 deletions test/assets/groupsets_get_by_id.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
<pagination pageNumber="1" pageSize="100" totalAvailable="3"/>
<groupSet id="1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" name="All Users" groupCount="3">
<group id="gs-1" name="group-one"/>
<group id="gs21" name="group-two"/>
<group id="gs-3" name="group-three"/>
</groupSet>
</tsResponse>
9 changes: 9 additions & 0 deletions test/assets/groupsets_update.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
<pagination pageNumber="1" pageSize="100" totalAvailable="3"/>
<groupSet id="1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" name="All Users" groupCount="1">
<group id="gs-1" name="group-one"/>
<group id="gs21" name="group-two"/>
<group id="gs-3" name="group-three"/>
</groupSet>
</tsResponse>
Loading
Loading