From 6d0775d244bce86adb57209d873ac9db5020499e Mon Sep 17 00:00:00 2001 From: Stu Tomlinson Date: Thu, 12 Jan 2023 10:23:55 +0000 Subject: [PATCH] Fix issues with connections publishing workbooks Allow publishing using connection credentials on ConnectionItem class without ConnectionCredentials instance, as documentated Accept empty string for username or password in connection credentials Avoid Tableau Server internal server error when publishing with empty connection list by setting connections to None --- tableauserverclient/server/request_factory.py | 14 +++-- test/test_workbook.py | 51 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 142297aa0..209626051 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -7,6 +7,7 @@ from tableauserverclient.models.metric_item import MetricItem +from ..models import ConnectionCredentials from ..models import ConnectionItem from ..models import DataAlertItem from ..models import FlowItem @@ -55,6 +56,13 @@ def _add_connections_element(connections_element, connection): connection_element.attrib["serverPort"] = connection.server_port if connection.connection_credentials: connection_credentials = connection.connection_credentials + elif connection.username is not None and connection.password is not None and connection.embed_password is not None: + connection_credentials = ConnectionCredentials( + connection.username, connection.password, embed=connection.embed_password + ) + else: + connection_credentials = None + if connection_credentials: _add_credentials_element(connection_element, connection_credentials) @@ -66,7 +74,7 @@ def _add_hiddenview_element(views_element, view_name): def _add_credentials_element(parent_element, connection_credentials): credentials_element = ET.SubElement(parent_element, "connectionCredentials") - if not connection_credentials.password or not connection_credentials.name: + if connection_credentials.password is None or connection_credentials.name is None: raise ValueError("Connection Credentials must have a name and password") credentials_element.attrib["name"] = connection_credentials.name credentials_element.attrib["password"] = connection_credentials.password @@ -177,7 +185,7 @@ def _generate_xml(self, datasource_item, connection_credentials=None, connection if connection_credentials is not None: _add_credentials_element(datasource_element, connection_credentials) - if connections is not None: + if connections is not None and len(connections) > 0: connections_element = ET.SubElement(datasource_element, "connections") for connection in connections: _add_connections_element(connections_element, connection) @@ -899,7 +907,7 @@ def _generate_xml( if connection_credentials is not None: _add_credentials_element(workbook_element, connection_credentials) - if connections is not None: + if connections is not None and len(connections) > 0: connections_element = ET.SubElement(workbook_element, "connections") for connection in connections: _add_connections_element(connections_element, connection) diff --git a/test/test_workbook.py b/test/test_workbook.py index db7f0723b..ba21dc195 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -748,6 +748,30 @@ def test_publish_multi_connection(self) -> None: self.assertEqual(connection_results[1].get("serverAddress", None), "pgsql.test.com") self.assertEqual(connection_results[1].find("connectionCredentials").get("password", None), "secret") # type: ignore[union-attr] + def test_publish_multi_connection_flat(self) -> None: + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) + connection1 = TSC.ConnectionItem() + connection1.server_address = "mysql.test.com" + connection1.username = "test" + connection1.password = "secret" + connection1.embed_password = True + connection2 = TSC.ConnectionItem() + connection2.server_address = "pgsql.test.com" + connection2.username = "test" + connection2.password = "secret" + connection2.embed_password = True + + response = RequestFactory.Workbook._generate_xml(new_workbook, connections=[connection1, connection2]) + # Can't use ConnectionItem parser due to xml namespace problems + connection_results = fromstring(response).findall(".//connection") + + self.assertEqual(connection_results[0].get("serverAddress", None), "mysql.test.com") + self.assertEqual(connection_results[0].find("connectionCredentials").get("name", None), "test") # type: ignore[union-attr] + self.assertEqual(connection_results[1].get("serverAddress", None), "pgsql.test.com") + self.assertEqual(connection_results[1].find("connectionCredentials").get("password", None), "secret") # type: ignore[union-attr] + def test_publish_single_connection(self) -> None: new_workbook = TSC.WorkbookItem( name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" @@ -762,6 +786,33 @@ def test_publish_single_connection(self) -> None: self.assertEqual(credentials[0].get("password", None), "secret") self.assertEqual(credentials[0].get("embed", None), "true") + def test_publish_single_connection_username_none(self) -> None: + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) + connection_creds = TSC.ConnectionCredentials(None, "secret", True) + + self.assertRaises( + ValueError, + RequestFactory.Workbook._generate_xml, + new_workbook, + connection_credentials=connection_creds, + ) + + def test_publish_single_connection_username_empty(self) -> None: + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) + connection_creds = TSC.ConnectionCredentials("", "secret", True) + + response = RequestFactory.Workbook._generate_xml(new_workbook, connection_credentials=connection_creds) + # Can't use ConnectionItem parser due to xml namespace problems + credentials = fromstring(response).findall(".//connectionCredentials") + self.assertEqual(len(credentials), 1) + self.assertEqual(credentials[0].get("name", None), "") + self.assertEqual(credentials[0].get("password", None), "secret") + self.assertEqual(credentials[0].get("embed", None), "true") + def test_credentials_and_multi_connect_raises_exception(self) -> None: new_workbook = TSC.WorkbookItem( name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"