mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-18 14:06:59 +00:00
Chore: Tableau Improvements (#21620)
* Chore: Tableau Improvements * Added apiVersion * linting * Addressed Comments
This commit is contained in:
parent
407eb24baf
commit
161b4a8b2a
@ -13,7 +13,7 @@ Wrapper module of TableauServerConnection client
|
|||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Callable, Dict, List, Optional, Tuple, Union
|
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import validators
|
import validators
|
||||||
from cached_property import cached_property
|
from cached_property import cached_property
|
||||||
@ -77,16 +77,18 @@ class TableauClient:
|
|||||||
self,
|
self,
|
||||||
tableau_server_auth: Union[PersonalAccessTokenAuth, TableauAuth],
|
tableau_server_auth: Union[PersonalAccessTokenAuth, TableauAuth],
|
||||||
config,
|
config,
|
||||||
verify_ssl,
|
verify_ssl: Union[bool, str],
|
||||||
pagination_limit: int,
|
pagination_limit: int,
|
||||||
):
|
):
|
||||||
self.tableau_server = Server(str(config.hostPort), use_server_version=True)
|
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.add_http_options({"verify": verify_ssl})
|
||||||
self.tableau_server.auth.sign_in(tableau_server_auth)
|
self.tableau_server.auth.sign_in(tableau_server_auth)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.pagination_limit = pagination_limit
|
self.pagination_limit = pagination_limit
|
||||||
self.custom_sql_table_queries: Dict[str, List[str]] = {}
|
self.custom_sql_table_queries: Dict[str, List[str]] = {}
|
||||||
self.usage_metrics: Dict[str, int] = {}
|
self.owner_cache: Dict[str, TableauOwner] = {}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def server_info(self) -> Callable:
|
def server_info(self) -> Callable:
|
||||||
@ -101,9 +103,15 @@ class TableauClient:
|
|||||||
|
|
||||||
def get_tableau_owner(self, owner_id: str) -> Optional[TableauOwner]:
|
def get_tableau_owner(self, owner_id: str) -> Optional[TableauOwner]:
|
||||||
try:
|
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
|
owner = self.tableau_server.users.get_by_id(owner_id) if owner_id else None
|
||||||
if owner and owner.email:
|
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:
|
except Exception as err:
|
||||||
logger.debug(f"Failed to fetch owner details for ID {owner_id}: {str(err)}")
|
logger.debug(f"Failed to fetch owner details for ID {owner_id}: {str(err)}")
|
||||||
return None
|
return None
|
||||||
@ -130,21 +138,20 @@ class TableauClient:
|
|||||||
)
|
)
|
||||||
view_count += view.total_views
|
view_count += view.total_views
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.warning(
|
logger.debug(
|
||||||
f"Failed to process view due to missing attribute: {str(e)}"
|
f"Failed to process view due to missing attribute: {str(e)}"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to process view: {str(e)}")
|
logger.debug(f"Failed to process view: {str(e)}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return charts, view_count
|
return charts, view_count
|
||||||
|
|
||||||
def get_workbooks(self) -> List[TableauDashboard]:
|
def get_workbooks(self) -> Iterable[TableauDashboard]:
|
||||||
"""
|
"""
|
||||||
Fetch all tableau workbooks
|
Fetch all tableau workbooks
|
||||||
"""
|
"""
|
||||||
workbooks: Optional[List[TableauDashboard]] = []
|
|
||||||
self.cache_custom_sql_tables()
|
self.cache_custom_sql_tables()
|
||||||
for workbook in Pager(self.tableau_server.workbooks):
|
for workbook in Pager(self.tableau_server.workbooks):
|
||||||
try:
|
try:
|
||||||
@ -152,22 +159,20 @@ class TableauClient:
|
|||||||
charts, user_views = self.get_workbook_charts_and_user_count(
|
charts, user_views = self.get_workbook_charts_and_user_count(
|
||||||
workbook.views
|
workbook.views
|
||||||
)
|
)
|
||||||
workbooks.append(
|
workbook = TableauDashboard(
|
||||||
TableauDashboard(
|
id=workbook.id,
|
||||||
id=workbook.id,
|
name=workbook.name,
|
||||||
name=workbook.name,
|
project=TableauBaseModel(
|
||||||
project=TableauBaseModel(
|
id=workbook.project_id, name=workbook.project_name
|
||||||
id=workbook.project_id, name=workbook.project_name
|
),
|
||||||
),
|
owner=self.get_tableau_owner(workbook.owner_id),
|
||||||
description=workbook.description,
|
description=workbook.description,
|
||||||
owner=self.get_tableau_owner(workbook.owner_id),
|
tags=workbook.tags,
|
||||||
tags=workbook.tags,
|
webpageUrl=workbook.webpage_url,
|
||||||
webpageUrl=workbook.webpage_url,
|
charts=charts,
|
||||||
charts=charts,
|
user_views=user_views,
|
||||||
dataModels=self.get_datasources(dashboard_id=workbook.id),
|
|
||||||
user_views=user_views,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
yield workbook
|
||||||
except AttributeError as err:
|
except AttributeError as err:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Failed to process workbook due to missing attribute: {str(err)}"
|
f"Failed to process workbook due to missing attribute: {str(err)}"
|
||||||
@ -176,7 +181,6 @@ class TableauClient:
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.warning(f"Failed to process workbook: {str(err)}")
|
logger.warning(f"Failed to process workbook: {str(err)}")
|
||||||
continue
|
continue
|
||||||
return workbooks
|
|
||||||
|
|
||||||
def test_get_workbooks(self):
|
def test_get_workbooks(self):
|
||||||
for workbook in Pager(self.tableau_server.workbooks):
|
for workbook in Pager(self.tableau_server.workbooks):
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
Source connection handler
|
Source connection handler
|
||||||
"""
|
"""
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
import tableauserverclient as TSC
|
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.ingestion.source.dashboard.tableau.client import TableauClient
|
||||||
from metadata.utils.constants import THREE_MIN
|
from metadata.utils.constants import THREE_MIN
|
||||||
from metadata.utils.logger import ingestion_logger
|
from metadata.utils.logger import ingestion_logger
|
||||||
from metadata.utils.ssl_registry import get_verify_ssl_fn
|
|
||||||
|
|
||||||
logger = ingestion_logger()
|
logger = ingestion_logger()
|
||||||
|
|
||||||
@ -48,12 +47,12 @@ def get_connection(connection: TableauConnection) -> TableauClient:
|
|||||||
Create connection
|
Create connection
|
||||||
"""
|
"""
|
||||||
tableau_server_auth = build_server_config(connection)
|
tableau_server_auth = build_server_config(connection)
|
||||||
get_verify_ssl = get_verify_ssl_fn(connection.verifySSL)
|
verify_ssl = set_verify_ssl(connection)
|
||||||
try:
|
try:
|
||||||
return TableauClient(
|
return TableauClient(
|
||||||
tableau_server_auth=tableau_server_auth,
|
tableau_server_auth=tableau_server_auth,
|
||||||
config=connection,
|
config=connection,
|
||||||
verify_ssl=get_verify_ssl(connection.sslConfig),
|
verify_ssl=verify_ssl,
|
||||||
pagination_limit=connection.paginationLimit,
|
pagination_limit=connection.paginationLimit,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
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(
|
def test_connection(
|
||||||
metadata: OpenMetadata,
|
metadata: OpenMetadata,
|
||||||
client: TableauClient,
|
client: TableauClient,
|
||||||
|
@ -127,8 +127,8 @@ class TableauSource(DashboardServiceSource):
|
|||||||
)
|
)
|
||||||
return cls(config, metadata)
|
return cls(config, metadata)
|
||||||
|
|
||||||
def get_dashboards_list(self) -> Optional[List[TableauDashboard]]:
|
def get_dashboards_list(self) -> Iterable[TableauDashboard]:
|
||||||
return self.client.get_workbooks()
|
yield from self.client.get_workbooks()
|
||||||
|
|
||||||
def get_dashboard_name(self, dashboard: TableauDashboard) -> str:
|
def get_dashboard_name(self, dashboard: TableauDashboard) -> str:
|
||||||
return dashboard.name
|
return dashboard.name
|
||||||
@ -137,6 +137,7 @@ class TableauSource(DashboardServiceSource):
|
|||||||
"""
|
"""
|
||||||
Get Dashboard Details including the dashboard charts and datamodels
|
Get Dashboard Details including the dashboard charts and datamodels
|
||||||
"""
|
"""
|
||||||
|
dashboard.dataModels = self.client.get_datasources(dashboard.id)
|
||||||
return dashboard
|
return dashboard
|
||||||
|
|
||||||
def get_owner_ref(
|
def get_owner_ref(
|
||||||
|
@ -52,6 +52,12 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"default": 10
|
"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": {
|
"verifySSL": {
|
||||||
"$ref": "../../../../security/ssl/verifySSLConfig.json#/definitions/verifySSL",
|
"$ref": "../../../../security/ssl/verifySSLConfig.json#/definitions/verifySSL",
|
||||||
"default": "no-ssl"
|
"default": "no-ssl"
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
* Tableau Connection Config
|
* Tableau Connection Config
|
||||||
*/
|
*/
|
||||||
export interface TableauConnection {
|
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
|
* Types of methods used to authenticate to the tableau instance
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user