Chore: Tableau Improvements (#21620)

* Chore: Tableau Improvements

* Added apiVersion

* linting

* Addressed Comments
This commit is contained in:
Suman Maharana 2025-06-07 21:38:48 +05:30 committed by GitHub
parent 407eb24baf
commit 161b4a8b2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 59 additions and 30 deletions

View File

@ -13,7 +13,7 @@ Wrapper module of TableauServerConnection client
"""
import math
import traceback
from typing import Callable, Dict, List, Optional, Tuple, Union
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union
import validators
from cached_property import cached_property
@ -77,16 +77,18 @@ class TableauClient:
self,
tableau_server_auth: Union[PersonalAccessTokenAuth, TableauAuth],
config,
verify_ssl,
verify_ssl: Union[bool, str],
pagination_limit: int,
):
self.tableau_server = Server(str(config.hostPort), use_server_version=True)
if config.apiVersion:
self.tableau_server.version = config.apiVersion
self.tableau_server.add_http_options({"verify": verify_ssl})
self.tableau_server.auth.sign_in(tableau_server_auth)
self.config = config
self.pagination_limit = pagination_limit
self.custom_sql_table_queries: Dict[str, List[str]] = {}
self.usage_metrics: Dict[str, int] = {}
self.owner_cache: Dict[str, TableauOwner] = {}
@cached_property
def server_info(self) -> Callable:
@ -101,9 +103,15 @@ class TableauClient:
def get_tableau_owner(self, owner_id: str) -> Optional[TableauOwner]:
try:
if owner_id in self.owner_cache:
return self.owner_cache[owner_id]
owner = self.tableau_server.users.get_by_id(owner_id) if owner_id else None
if owner and owner.email:
return TableauOwner(id=owner.id, name=owner.name, email=owner.email)
owner_obj = TableauOwner(
id=owner.id, name=owner.name, email=owner.email
)
self.owner_cache[owner_id] = owner_obj
return owner_obj
except Exception as err:
logger.debug(f"Failed to fetch owner details for ID {owner_id}: {str(err)}")
return None
@ -130,21 +138,20 @@ class TableauClient:
)
view_count += view.total_views
except AttributeError as e:
logger.warning(
logger.debug(
f"Failed to process view due to missing attribute: {str(e)}"
)
continue
except Exception as e:
logger.warning(f"Failed to process view: {str(e)}")
logger.debug(f"Failed to process view: {str(e)}")
continue
return charts, view_count
def get_workbooks(self) -> List[TableauDashboard]:
def get_workbooks(self) -> Iterable[TableauDashboard]:
"""
Fetch all tableau workbooks
"""
workbooks: Optional[List[TableauDashboard]] = []
self.cache_custom_sql_tables()
for workbook in Pager(self.tableau_server.workbooks):
try:
@ -152,22 +159,20 @@ class TableauClient:
charts, user_views = self.get_workbook_charts_and_user_count(
workbook.views
)
workbooks.append(
TableauDashboard(
workbook = TableauDashboard(
id=workbook.id,
name=workbook.name,
project=TableauBaseModel(
id=workbook.project_id, name=workbook.project_name
),
description=workbook.description,
owner=self.get_tableau_owner(workbook.owner_id),
description=workbook.description,
tags=workbook.tags,
webpageUrl=workbook.webpage_url,
charts=charts,
dataModels=self.get_datasources(dashboard_id=workbook.id),
user_views=user_views,
)
)
yield workbook
except AttributeError as err:
logger.warning(
f"Failed to process workbook due to missing attribute: {str(err)}"
@ -176,7 +181,6 @@ class TableauClient:
except Exception as err:
logger.warning(f"Failed to process workbook: {str(err)}")
continue
return workbooks
def test_get_workbooks(self):
for workbook in Pager(self.tableau_server.workbooks):

View File

@ -13,7 +13,7 @@
Source connection handler
"""
import traceback
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Union
import tableauserverclient as TSC
@ -38,7 +38,6 @@ from metadata.ingestion.ometa.ometa_api import OpenMetadata
from metadata.ingestion.source.dashboard.tableau.client import TableauClient
from metadata.utils.constants import THREE_MIN
from metadata.utils.logger import ingestion_logger
from metadata.utils.ssl_registry import get_verify_ssl_fn
logger = ingestion_logger()
@ -48,12 +47,12 @@ def get_connection(connection: TableauConnection) -> TableauClient:
Create connection
"""
tableau_server_auth = build_server_config(connection)
get_verify_ssl = get_verify_ssl_fn(connection.verifySSL)
verify_ssl = set_verify_ssl(connection)
try:
return TableauClient(
tableau_server_auth=tableau_server_auth,
config=connection,
verify_ssl=get_verify_ssl(connection.sslConfig),
verify_ssl=verify_ssl,
pagination_limit=connection.paginationLimit,
)
except Exception as exc:
@ -63,6 +62,21 @@ def get_connection(connection: TableauConnection) -> TableauClient:
)
def set_verify_ssl(connection: TableauConnection) -> Union[bool, str]:
"""
Set verify ssl based on connection configuration
ref: https://tableau.github.io/server-client-python/docs/sign-in-out#handling-ssl-certificates-for-tableau-server
"""
if connection.verifySSL.value == "validate":
if connection.sslConfig.root.caCertificate:
return connection.sslConfig.root.caCertificate.get_secret_value()
if connection.sslConfig.root.sslCertificate:
return connection.sslConfig.root.sslCertificate.get_secret_value()
if connection.verifySSL.value == "ignore":
return False
return None
def test_connection(
metadata: OpenMetadata,
client: TableauClient,

View File

@ -127,8 +127,8 @@ class TableauSource(DashboardServiceSource):
)
return cls(config, metadata)
def get_dashboards_list(self) -> Optional[List[TableauDashboard]]:
return self.client.get_workbooks()
def get_dashboards_list(self) -> Iterable[TableauDashboard]:
yield from self.client.get_workbooks()
def get_dashboard_name(self, dashboard: TableauDashboard) -> str:
return dashboard.name
@ -137,6 +137,7 @@ class TableauSource(DashboardServiceSource):
"""
Get Dashboard Details including the dashboard charts and datamodels
"""
dashboard.dataModels = self.client.get_datasources(dashboard.id)
return dashboard
def get_owner_ref(

View File

@ -52,6 +52,12 @@
"type": "integer",
"default": 10
},
"apiVersion": {
"title": "API Version",
"description": "Tableau API version. If not provided, the version will be used from the tableau server.",
"type": "string",
"default": null
},
"verifySSL": {
"$ref": "../../../../security/ssl/verifySSLConfig.json#/definitions/verifySSL",
"default": "no-ssl"

View File

@ -14,6 +14,10 @@
* Tableau Connection Config
*/
export interface TableauConnection {
/**
* Tableau API version. If not provided, the version will be used from the tableau server.
*/
apiVersion?: string;
/**
* Types of methods used to authenticate to the tableau instance
*/