mirror of
https://github.com/datahub-project/datahub.git
synced 2026-01-06 06:46:41 +00:00
refactor(ingest): Tableau cleanup (#6131)
Co-authored-by: Shirshanka Das <shirshanka@apache.org>
This commit is contained in:
parent
879894d702
commit
d569734193
@ -7,7 +7,7 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
import dateutil.parser as dp
|
||||
import tableauserverclient as TSC
|
||||
from pydantic import root_validator, validator
|
||||
from pydantic import validator
|
||||
from pydantic.fields import Field
|
||||
from tableauserverclient import (
|
||||
PersonalAccessTokenAuth,
|
||||
@ -17,7 +17,7 @@ from tableauserverclient import (
|
||||
)
|
||||
|
||||
import datahub.emitter.mce_builder as builder
|
||||
from datahub.configuration.common import ConfigurationError
|
||||
from datahub.configuration.common import ConfigModel, ConfigurationError
|
||||
from datahub.configuration.source_common import DatasetLineageProviderConfigBase
|
||||
from datahub.emitter.mcp import MetadataChangeProposalWrapper
|
||||
from datahub.emitter.mcp_builder import (
|
||||
@ -96,7 +96,7 @@ logger: logging.Logger = logging.getLogger(__name__)
|
||||
REPLACE_SLASH_CHAR = "|"
|
||||
|
||||
|
||||
class TableauConfig(DatasetLineageProviderConfigBase):
|
||||
class TableauConnectionConfig(ConfigModel):
|
||||
connect_uri: str = Field(description="Tableau host URL.")
|
||||
username: Optional[str] = Field(
|
||||
default=None,
|
||||
@ -117,9 +117,44 @@ class TableauConfig(DatasetLineageProviderConfigBase):
|
||||
|
||||
site: str = Field(
|
||||
default="",
|
||||
description="Tableau Site. Always required for Tableau Online. Use emptystring "
|
||||
" to connect with Default site on Tableau Server.",
|
||||
description="Tableau Site. Always required for Tableau Online. Use emptystring to connect with Default site on Tableau Server.",
|
||||
)
|
||||
|
||||
@validator("connect_uri")
|
||||
def remove_trailing_slash(cls, v):
|
||||
return config_clean.remove_trailing_slashes(v)
|
||||
|
||||
def make_tableau_client(self) -> Server:
|
||||
# https://tableau.github.io/server-client-python/docs/api-ref#authentication
|
||||
authentication: Union[TableauAuth, PersonalAccessTokenAuth]
|
||||
if self.username and self.password:
|
||||
authentication = TableauAuth(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
site_id=self.site,
|
||||
)
|
||||
elif self.token_name and self.token_value:
|
||||
authentication = PersonalAccessTokenAuth(
|
||||
self.token_name, self.token_value, self.site
|
||||
)
|
||||
else:
|
||||
raise ConfigurationError(
|
||||
"Tableau Source: Either username/password or token_name/token_value must be set"
|
||||
)
|
||||
|
||||
try:
|
||||
server = Server(self.connect_uri, use_server_version=True)
|
||||
server.auth.sign_in(authentication)
|
||||
return server
|
||||
except ServerResponseError as e:
|
||||
raise ValueError(
|
||||
f"Unable to login with credentials provided: {str(e)}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
raise ValueError(f"Unable to login: {str(e)}") from e
|
||||
|
||||
|
||||
class TableauConfig(DatasetLineageProviderConfigBase, TableauConnectionConfig):
|
||||
projects: Optional[List[str]] = Field(
|
||||
default=["default"], description="List of projects"
|
||||
)
|
||||
@ -139,11 +174,6 @@ class TableauConfig(DatasetLineageProviderConfigBase):
|
||||
description="Ingest details for tables external to (not embedded in) tableau as entities.",
|
||||
)
|
||||
|
||||
workbooks_page_size: Optional[int] = Field(
|
||||
default=None,
|
||||
description="@deprecated(use page_size instead) Number of workbooks to query at a time using Tableau api.",
|
||||
)
|
||||
|
||||
page_size: int = Field(
|
||||
default=10,
|
||||
description="Number of metadata objects (e.g. CustomSQLTable, PublishedDatasource, etc) to query at a time using Tableau api.",
|
||||
@ -164,21 +194,6 @@ class TableauConfig(DatasetLineageProviderConfigBase):
|
||||
description="[experimental] Extract usage statistics for dashboards and charts.",
|
||||
)
|
||||
|
||||
@validator("connect_uri")
|
||||
def remove_trailing_slash(cls, v):
|
||||
return config_clean.remove_trailing_slashes(v)
|
||||
|
||||
@root_validator()
|
||||
def show_warning_for_deprecated_config_field(
|
||||
cls, values: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
if values.get("workbooks_page_size") is not None:
|
||||
logger.warn(
|
||||
"Config workbooks_page_size is deprecated. Please use config page_size instead."
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class WorkbookKey(PlatformKey):
|
||||
workbook_id: str
|
||||
@ -198,7 +213,6 @@ class UsageStat:
|
||||
supported=False,
|
||||
)
|
||||
@capability(SourceCapability.DOMAINS, "Requires transformer", supported=False)
|
||||
@capability(SourceCapability.DATA_PROFILING, "", supported=False)
|
||||
@capability(SourceCapability.DESCRIPTIONS, "Enabled by default")
|
||||
@capability(
|
||||
SourceCapability.USAGE_STATS,
|
||||
@ -207,9 +221,6 @@ class UsageStat:
|
||||
@capability(SourceCapability.DELETION_DETECTION, "", supported=False)
|
||||
@capability(SourceCapability.OWNERSHIP, "Requires recipe configuration")
|
||||
@capability(SourceCapability.TAGS, "Requires recipe configuration")
|
||||
@capability(
|
||||
SourceCapability.PARTITION_SUPPORT, "Not applicable to source", supported=False
|
||||
)
|
||||
@capability(SourceCapability.LINEAGE_COARSE, "Enabled by default")
|
||||
class TableauSource(Source):
|
||||
config: TableauConfig
|
||||
@ -258,39 +269,16 @@ class TableauSource(Source):
|
||||
logger.debug("Tableau stats %s", self.tableau_stat_registry)
|
||||
|
||||
def _authenticate(self):
|
||||
# https://tableau.github.io/server-client-python/docs/api-ref#authentication
|
||||
authentication: Optional[Union[TableauAuth, PersonalAccessTokenAuth]] = None
|
||||
if self.config.username and self.config.password:
|
||||
authentication = TableauAuth(
|
||||
username=self.config.username,
|
||||
password=self.config.password,
|
||||
site_id=self.config.site,
|
||||
)
|
||||
elif self.config.token_name and self.config.token_value:
|
||||
authentication = PersonalAccessTokenAuth(
|
||||
self.config.token_name, self.config.token_value, self.config.site
|
||||
)
|
||||
else:
|
||||
raise ConfigurationError(
|
||||
"Tableau Source: Either username/password or token_name/token_value must be set"
|
||||
)
|
||||
|
||||
try:
|
||||
self.server = Server(self.config.connect_uri, use_server_version=True)
|
||||
self.server.auth.sign_in(authentication)
|
||||
except ServerResponseError as e:
|
||||
logger.error(e)
|
||||
self.server = self.config.make_tableau_client()
|
||||
# Note that we're not catching ConfigurationError, since we want that to throw.
|
||||
except ValueError as e:
|
||||
self.report.report_failure(
|
||||
key="tableau-login",
|
||||
reason=f"Unable to Login with credentials provided" f"Reason: {str(e)}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.report.report_failure(
|
||||
key="tableau-login", reason=f"Unable to Login" f"Reason: {str(e)}"
|
||||
reason=str(e),
|
||||
)
|
||||
|
||||
def get_connection_object(
|
||||
def get_connection_object_page(
|
||||
self,
|
||||
query: str,
|
||||
connection_type: str,
|
||||
@ -305,10 +293,11 @@ class TableauSource(Source):
|
||||
self.server, query, connection_type, count, current_count, query_filter
|
||||
)
|
||||
if "errors" in query_data:
|
||||
self.report.report_warning(
|
||||
key="tableau-metadata",
|
||||
reason=f"Connection: {connection_type} Error: {query_data['errors']}",
|
||||
)
|
||||
errors = query_data["errors"]
|
||||
if all(error["extensions"]["severity"] == "WARNING" for error in errors):
|
||||
self.report.report_warning(key=connection_type, reason=f"{errors}")
|
||||
else:
|
||||
raise RuntimeError(f"Query {connection_type} error: {errors}")
|
||||
|
||||
connection_object = (
|
||||
query_data.get("data").get(connection_type, {})
|
||||
@ -320,23 +309,19 @@ class TableauSource(Source):
|
||||
has_next_page = connection_object.get("pageInfo", {}).get("hasNextPage", False)
|
||||
return connection_object, total_count, has_next_page
|
||||
|
||||
def emit_workbooks(self) -> Iterable[MetadataWorkUnit]:
|
||||
count_on_query = (
|
||||
self.config.page_size
|
||||
if self.config.workbooks_page_size is None
|
||||
else self.config.workbooks_page_size
|
||||
)
|
||||
def get_connection_objects(
|
||||
self,
|
||||
query: str,
|
||||
connection_type: str,
|
||||
query_filter: str,
|
||||
) -> Iterable[dict]:
|
||||
# Calls the get_connection_object_page function to get the objects,
|
||||
# and automatically handles pagination.
|
||||
|
||||
projects = (
|
||||
f"projectNameWithin: {json.dumps(self.config.projects)}"
|
||||
if self.config.projects
|
||||
else ""
|
||||
)
|
||||
|
||||
workbook_connection, total_count, has_next_page = self.get_connection_object(
|
||||
workbook_graphql_query, "workbooksConnection", projects
|
||||
)
|
||||
count_on_query = self.config.page_size
|
||||
|
||||
total_count = count_on_query
|
||||
has_next_page = 1
|
||||
current_count = 0
|
||||
while has_next_page:
|
||||
count = (
|
||||
@ -345,25 +330,37 @@ class TableauSource(Source):
|
||||
else total_count - current_count
|
||||
)
|
||||
(
|
||||
workbook_connection,
|
||||
connection_objects,
|
||||
total_count,
|
||||
has_next_page,
|
||||
) = self.get_connection_object(
|
||||
workbook_graphql_query,
|
||||
"workbooksConnection",
|
||||
projects,
|
||||
) = self.get_connection_object_page(
|
||||
query,
|
||||
connection_type,
|
||||
query_filter,
|
||||
count,
|
||||
current_count,
|
||||
)
|
||||
|
||||
current_count += count
|
||||
|
||||
for workbook in workbook_connection.get("nodes", []):
|
||||
yield from self.emit_workbook_as_container(workbook)
|
||||
yield from self.emit_sheets_as_charts(workbook)
|
||||
yield from self.emit_dashboards(workbook)
|
||||
for ds in workbook.get("embeddedDatasources", []):
|
||||
self.embedded_datasource_ids_being_used.append(ds["id"])
|
||||
for obj in connection_objects.get("nodes", []):
|
||||
yield obj
|
||||
|
||||
def emit_workbooks(self) -> Iterable[MetadataWorkUnit]:
|
||||
projects = (
|
||||
f"projectNameWithin: {json.dumps(self.config.projects)}"
|
||||
if self.config.projects
|
||||
else ""
|
||||
)
|
||||
|
||||
for workbook in self.get_connection_objects(
|
||||
workbook_graphql_query, "workbooksConnection", projects
|
||||
):
|
||||
yield from self.emit_workbook_as_container(workbook)
|
||||
yield from self.emit_sheets_as_charts(workbook)
|
||||
yield from self.emit_dashboards(workbook)
|
||||
for ds in workbook.get("embeddedDatasources", []):
|
||||
self.embedded_datasource_ids_being_used.append(ds["id"])
|
||||
|
||||
def _track_custom_sql_ids(self, field: dict) -> None:
|
||||
# Tableau shows custom sql datasource as a table in ColumnField.
|
||||
@ -484,118 +481,98 @@ class TableauSource(Source):
|
||||
return upstream_tables
|
||||
|
||||
def emit_custom_sql_datasources(self) -> Iterable[MetadataWorkUnit]:
|
||||
count_on_query = self.config.page_size
|
||||
custom_sql_filter = f"idWithin: {json.dumps(self.custom_sql_ids_being_used)}"
|
||||
custom_sql_connection, total_count, has_next_page = self.get_connection_object(
|
||||
custom_sql_graphql_query, "customSQLTablesConnection", custom_sql_filter
|
||||
)
|
||||
|
||||
current_count = 0
|
||||
while has_next_page:
|
||||
count = (
|
||||
count_on_query
|
||||
if current_count + count_on_query < total_count
|
||||
else total_count - current_count
|
||||
)
|
||||
(
|
||||
custom_sql_connection,
|
||||
total_count,
|
||||
has_next_page,
|
||||
) = self.get_connection_object(
|
||||
custom_sql_connection = list(
|
||||
self.get_connection_objects(
|
||||
custom_sql_graphql_query,
|
||||
"customSQLTablesConnection",
|
||||
custom_sql_filter,
|
||||
count,
|
||||
current_count,
|
||||
)
|
||||
current_count += count
|
||||
)
|
||||
|
||||
unique_custom_sql = get_unique_custom_sql(
|
||||
custom_sql_connection.get("nodes", [])
|
||||
unique_custom_sql = get_unique_custom_sql(custom_sql_connection)
|
||||
|
||||
for csql in unique_custom_sql:
|
||||
csql_id: str = csql["id"]
|
||||
csql_urn = builder.make_dataset_urn(self.platform, csql_id, self.config.env)
|
||||
dataset_snapshot = DatasetSnapshot(
|
||||
urn=csql_urn,
|
||||
aspects=[],
|
||||
)
|
||||
for csql in unique_custom_sql:
|
||||
csql_id: str = csql["id"]
|
||||
csql_urn = builder.make_dataset_urn(
|
||||
self.platform, csql_id, self.config.env
|
||||
)
|
||||
dataset_snapshot = DatasetSnapshot(
|
||||
urn=csql_urn,
|
||||
aspects=[],
|
||||
|
||||
datasource_name = None
|
||||
project = None
|
||||
if len(csql["datasources"]) > 0:
|
||||
yield from self._create_lineage_from_csql_datasource(
|
||||
csql_urn, csql["datasources"]
|
||||
)
|
||||
|
||||
datasource_name = None
|
||||
project = None
|
||||
if len(csql["datasources"]) > 0:
|
||||
yield from self._create_lineage_from_csql_datasource(
|
||||
csql_urn, csql["datasources"]
|
||||
# CustomSQLTable id owned by exactly one tableau data source
|
||||
logger.debug(
|
||||
f"Number of datasources referencing CustomSQLTable: {len(csql['datasources'])}"
|
||||
)
|
||||
|
||||
datasource = csql["datasources"][0]
|
||||
datasource_name = datasource.get("name")
|
||||
if datasource.get(
|
||||
"__typename"
|
||||
) == "EmbeddedDatasource" and datasource.get("workbook"):
|
||||
datasource_name = (
|
||||
f"{datasource.get('workbook').get('name')}/{datasource_name}"
|
||||
if datasource_name and datasource.get("workbook").get("name")
|
||||
else None
|
||||
)
|
||||
|
||||
# CustomSQLTable id owned by exactly one tableau data source
|
||||
logger.debug(
|
||||
f"Number of datasources referencing CustomSQLTable: {len(csql['datasources'])}"
|
||||
workunits = add_entity_to_container(
|
||||
self.gen_workbook_key(datasource["workbook"]),
|
||||
"dataset",
|
||||
dataset_snapshot.urn,
|
||||
)
|
||||
for wu in workunits:
|
||||
self.report.report_workunit(wu)
|
||||
yield wu
|
||||
project = self._get_project(datasource)
|
||||
|
||||
datasource = csql["datasources"][0]
|
||||
datasource_name = datasource.get("name")
|
||||
if datasource.get(
|
||||
"__typename"
|
||||
) == "EmbeddedDatasource" and datasource.get("workbook"):
|
||||
datasource_name = (
|
||||
f"{datasource.get('workbook').get('name')}/{datasource_name}"
|
||||
if datasource_name
|
||||
and datasource.get("workbook").get("name")
|
||||
else None
|
||||
)
|
||||
workunits = add_entity_to_container(
|
||||
self.gen_workbook_key(datasource["workbook"]),
|
||||
"dataset",
|
||||
dataset_snapshot.urn,
|
||||
)
|
||||
for wu in workunits:
|
||||
self.report.report_workunit(wu)
|
||||
yield wu
|
||||
project = self._get_project(datasource)
|
||||
# lineage from custom sql -> datasets/tables #
|
||||
columns = csql.get("columns", [])
|
||||
yield from self._create_lineage_to_upstream_tables(csql_urn, columns)
|
||||
|
||||
# lineage from custom sql -> datasets/tables #
|
||||
columns = csql.get("columns", [])
|
||||
yield from self._create_lineage_to_upstream_tables(csql_urn, columns)
|
||||
# Schema Metadata
|
||||
schema_metadata = self.get_schema_metadata_for_custom_sql(columns)
|
||||
if schema_metadata is not None:
|
||||
dataset_snapshot.aspects.append(schema_metadata)
|
||||
|
||||
# Schema Metadata
|
||||
schema_metadata = self.get_schema_metadata_for_custom_sql(columns)
|
||||
if schema_metadata is not None:
|
||||
dataset_snapshot.aspects.append(schema_metadata)
|
||||
# Browse path
|
||||
|
||||
# Browse path
|
||||
|
||||
if project and datasource_name:
|
||||
browse_paths = BrowsePathsClass(
|
||||
paths=[
|
||||
f"/{self.config.env.lower()}/{self.platform}/{project}/{datasource['name']}"
|
||||
]
|
||||
)
|
||||
dataset_snapshot.aspects.append(browse_paths)
|
||||
else:
|
||||
logger.debug(f"Browse path not set for Custom SQL table {csql_id}")
|
||||
|
||||
dataset_properties = DatasetPropertiesClass(
|
||||
name=csql.get("name"), description=csql.get("description")
|
||||
if project and datasource_name:
|
||||
browse_paths = BrowsePathsClass(
|
||||
paths=[
|
||||
f"/{self.config.env.lower()}/{self.platform}/{project}/{datasource['name']}"
|
||||
]
|
||||
)
|
||||
dataset_snapshot.aspects.append(browse_paths)
|
||||
else:
|
||||
logger.debug(f"Browse path not set for Custom SQL table {csql_id}")
|
||||
|
||||
dataset_snapshot.aspects.append(dataset_properties)
|
||||
dataset_properties = DatasetPropertiesClass(
|
||||
name=csql.get("name"), description=csql.get("description")
|
||||
)
|
||||
|
||||
view_properties = ViewPropertiesClass(
|
||||
materialized=False,
|
||||
viewLanguage="SQL",
|
||||
viewLogic=clean_query(csql.get("query", "")),
|
||||
)
|
||||
dataset_snapshot.aspects.append(view_properties)
|
||||
dataset_snapshot.aspects.append(dataset_properties)
|
||||
|
||||
yield self.get_metadata_change_event(dataset_snapshot)
|
||||
yield self.get_metadata_change_proposal(
|
||||
dataset_snapshot.urn,
|
||||
aspect_name="subTypes",
|
||||
aspect=SubTypesClass(typeNames=["view", "Custom SQL"]),
|
||||
)
|
||||
view_properties = ViewPropertiesClass(
|
||||
materialized=False,
|
||||
viewLanguage="SQL",
|
||||
viewLogic=clean_query(csql.get("query", "")),
|
||||
)
|
||||
dataset_snapshot.aspects.append(view_properties)
|
||||
|
||||
yield self.get_metadata_change_event(dataset_snapshot)
|
||||
yield self.get_metadata_change_proposal(
|
||||
dataset_snapshot.urn,
|
||||
aspect_name="subTypes",
|
||||
aspect=SubTypesClass(typeNames=["view", "Custom SQL"]),
|
||||
)
|
||||
|
||||
def get_schema_metadata_for_custom_sql(
|
||||
self, columns: List[dict]
|
||||
@ -863,40 +840,14 @@ class TableauSource(Source):
|
||||
yield wu
|
||||
|
||||
def emit_published_datasources(self) -> Iterable[MetadataWorkUnit]:
|
||||
count_on_query = self.config.page_size
|
||||
datasource_filter = f"idWithin: {json.dumps(self.datasource_ids_being_used)}"
|
||||
(
|
||||
published_datasource_conn,
|
||||
total_count,
|
||||
has_next_page,
|
||||
) = self.get_connection_object(
|
||||
|
||||
for datasource in self.get_connection_objects(
|
||||
published_datasource_graphql_query,
|
||||
"publishedDatasourcesConnection",
|
||||
datasource_filter,
|
||||
)
|
||||
|
||||
current_count = 0
|
||||
while has_next_page:
|
||||
count = (
|
||||
count_on_query
|
||||
if current_count + count_on_query < total_count
|
||||
else total_count - current_count
|
||||
)
|
||||
(
|
||||
published_datasource_conn,
|
||||
total_count,
|
||||
has_next_page,
|
||||
) = self.get_connection_object(
|
||||
published_datasource_graphql_query,
|
||||
"publishedDatasourcesConnection",
|
||||
datasource_filter,
|
||||
count,
|
||||
current_count,
|
||||
)
|
||||
|
||||
current_count += count
|
||||
for datasource in published_datasource_conn.get("nodes", []):
|
||||
yield from self.emit_datasource(datasource)
|
||||
):
|
||||
yield from self.emit_datasource(datasource)
|
||||
|
||||
def emit_upstream_tables(self) -> Iterable[MetadataWorkUnit]:
|
||||
for (table_urn, (columns, path, is_embedded)) in self.upstream_tables.items():
|
||||
@ -1056,7 +1007,10 @@ class TableauSource(Source):
|
||||
lastModified=last_modified,
|
||||
externalUrl=sheet_external_url,
|
||||
inputs=sorted(datasource_urn),
|
||||
customProperties=fields,
|
||||
customProperties={
|
||||
"luid": sheet.get("luid") or "",
|
||||
**{f"field: {k}": v for k, v in fields.items()},
|
||||
},
|
||||
)
|
||||
chart_snapshot.aspects.append(chart_info)
|
||||
# chart_snapshot doesn't support the stat aspect as list element and hence need to emit MCP
|
||||
@ -1229,7 +1183,7 @@ class TableauSource(Source):
|
||||
charts=chart_urns,
|
||||
lastModified=last_modified,
|
||||
dashboardUrl=dashboard_external_url,
|
||||
customProperties={},
|
||||
customProperties={"luid": dashboard.get("luid") or ""},
|
||||
)
|
||||
dashboard_snapshot.aspects.append(dashboard_info_class)
|
||||
|
||||
@ -1267,43 +1221,18 @@ class TableauSource(Source):
|
||||
yield wu
|
||||
|
||||
def emit_embedded_datasources(self) -> Iterable[MetadataWorkUnit]:
|
||||
count_on_query = self.config.page_size
|
||||
datasource_filter = (
|
||||
f"idWithin: {json.dumps(self.embedded_datasource_ids_being_used)}"
|
||||
)
|
||||
(
|
||||
embedded_datasource_conn,
|
||||
total_count,
|
||||
has_next_page,
|
||||
) = self.get_connection_object(
|
||||
|
||||
for datasource in self.get_connection_objects(
|
||||
embedded_datasource_graphql_query,
|
||||
"embeddedDatasourcesConnection",
|
||||
datasource_filter,
|
||||
)
|
||||
current_count = 0
|
||||
while has_next_page:
|
||||
count = (
|
||||
count_on_query
|
||||
if current_count + count_on_query < total_count
|
||||
else total_count - current_count
|
||||
):
|
||||
yield from self.emit_datasource(
|
||||
datasource, datasource.get("workbook"), is_embedded_ds=True
|
||||
)
|
||||
(
|
||||
embedded_datasource_conn,
|
||||
total_count,
|
||||
has_next_page,
|
||||
) = self.get_connection_object(
|
||||
embedded_datasource_graphql_query,
|
||||
"embeddedDatasourcesConnection",
|
||||
datasource_filter,
|
||||
count,
|
||||
current_count,
|
||||
)
|
||||
|
||||
current_count += count
|
||||
for datasource in embedded_datasource_conn.get("nodes", []):
|
||||
yield from self.emit_datasource(
|
||||
datasource, datasource.get("workbook"), is_embedded_ds=True
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _get_schema(self, schema_provided: str, database: str, fullName: str) -> str:
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"customSQLTablesConnection": {
|
||||
"nodes": [],
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": null
|
||||
},
|
||||
"totalCount": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"embeddedDatasourcesConnection": {
|
||||
"nodes": [],
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": null
|
||||
},
|
||||
"totalCount": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"publishedDatasourcesConnection": {
|
||||
"nodes": [],
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": null
|
||||
},
|
||||
"totalCount": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"workbooksConnection": {
|
||||
"nodes": [],
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": null
|
||||
},
|
||||
"totalCount": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@
|
||||
"path": "",
|
||||
"createdAt": "2021-12-22T19:10:34Z",
|
||||
"updatedAt": "2021-12-22T19:10:34Z",
|
||||
"luid": "f0779f9d-6765-47a9-a8f6-c740cfd27783",
|
||||
"tags": [],
|
||||
"containedInDashboards": [
|
||||
{
|
||||
@ -957,6 +958,7 @@
|
||||
"path": "EmailPerformancebyCampaign/EmailPerformancebyCampaign",
|
||||
"createdAt": "2021-12-22T19:10:34Z",
|
||||
"updatedAt": "2021-12-22T19:10:34Z",
|
||||
"luid": "fc9ea488-f810-4fa8-ac19-aa96018b5d66",
|
||||
"sheets": [
|
||||
{
|
||||
"id": "222d1406-de0e-cd8d-0b94-9b45a0007e59",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,22 +0,0 @@
|
||||
import pytest
|
||||
import test_tableau_common
|
||||
from freezegun import freeze_time
|
||||
|
||||
FROZEN_TIME = "2021-12-07 07:00:00"
|
||||
|
||||
|
||||
@freeze_time(FROZEN_TIME)
|
||||
@pytest.mark.slow_unit
|
||||
def test_tableau_usage_stat(pytestconfig, tmp_path):
|
||||
output_file_name: str = "tableau_stat_mces.json"
|
||||
golden_file_name: str = "tableau_state_mces_golden.json"
|
||||
side_effect_query_metadata = test_tableau_common.define_query_metadata_func(
|
||||
"workbooksConnection_0.json", "workbooksConnection_state_all.json"
|
||||
)
|
||||
test_tableau_common.tableau_ingest_common(
|
||||
pytestconfig,
|
||||
tmp_path,
|
||||
side_effect_query_metadata,
|
||||
golden_file_name,
|
||||
output_file_name,
|
||||
)
|
||||
@ -69,6 +69,20 @@
|
||||
"runId": "tableau-test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"entityType": "chart",
|
||||
"entityUrn": "urn:li:chart:(tableau,222d1406-de0e-cd8d-0b94-9b45a0007e59)",
|
||||
"changeType": "UPSERT",
|
||||
"aspectName": "chartUsageStatistics",
|
||||
"aspect": {
|
||||
"value": "{\"timestampMillis\": 1638860400000, \"partitionSpec\": {\"type\": \"FULL_TABLE\", \"partition\": \"FULL_TABLE_SNAPSHOT\"}, \"viewsCount\": 5}",
|
||||
"contentType": "application/json"
|
||||
},
|
||||
"systemMetadata": {
|
||||
"lastObserved": 1638860400000,
|
||||
"runId": "tableau-test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"proposedSnapshot": {
|
||||
"com.linkedin.pegasus2avro.metadata.snapshot.ChartSnapshot": {
|
||||
@ -77,12 +91,13 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Name": "",
|
||||
"Activity Date": "",
|
||||
"ID": "",
|
||||
"Program Name": "",
|
||||
"Active": "",
|
||||
"Id": ""
|
||||
"luid": "f0779f9d-6765-47a9-a8f6-c740cfd27783",
|
||||
"field: Name": "",
|
||||
"field: Activity Date": "",
|
||||
"field: ID": "",
|
||||
"field: Program Name": "",
|
||||
"field: Active": "",
|
||||
"field: Id": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/EmailPerformancebyCampaign/EmailPerformancebyCampaign/Timeline - Sent",
|
||||
"title": "Timeline - Sent",
|
||||
@ -155,23 +170,24 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Delivery Rate": "formula: ZN([Delivered Email]/[Sent Email])",
|
||||
"Name": "",
|
||||
"Id (Activity - Click Email)": "",
|
||||
"Activity Date": "",
|
||||
"Clickthrough Rate": "formula: [Clickthrough Emails]/[Delivered Email]",
|
||||
"Open Rate": "formula: ZN([Opened Email]/[Delivered Email])",
|
||||
"Sent Email": "formula: COUNTD([Id])",
|
||||
"Delivered Email": "formula: COUNTD([Id (Activity - Email Delivered)])",
|
||||
"ID": "",
|
||||
"Id (Activity - Open Email)": "",
|
||||
"Clickthrough Emails": "formula: COUNTD([Id (Activity - Click Email)])",
|
||||
"Click-to-Open": "formula: ZN([Clickthrough Emails]\r\n/ \r\n[Opened Email])",
|
||||
"Opened Email": "formula: COUNTD([Id (Activity - Open Email)])",
|
||||
"Id (Activity - Email Delivered)": "",
|
||||
"Program Name": "",
|
||||
"Active": "",
|
||||
"Id": ""
|
||||
"luid": "",
|
||||
"field: Delivery Rate": "formula: ZN([Delivered Email]/[Sent Email])",
|
||||
"field: Name": "",
|
||||
"field: Id (Activity - Click Email)": "",
|
||||
"field: Activity Date": "",
|
||||
"field: Clickthrough Rate": "formula: [Clickthrough Emails]/[Delivered Email]",
|
||||
"field: Open Rate": "formula: ZN([Opened Email]/[Delivered Email])",
|
||||
"field: Sent Email": "formula: COUNTD([Id])",
|
||||
"field: Delivered Email": "formula: COUNTD([Id (Activity - Email Delivered)])",
|
||||
"field: ID": "",
|
||||
"field: Id (Activity - Open Email)": "",
|
||||
"field: Clickthrough Emails": "formula: COUNTD([Id (Activity - Click Email)])",
|
||||
"field: Click-to-Open": "formula: ZN([Clickthrough Emails]\r\n/ \r\n[Opened Email])",
|
||||
"field: Opened Email": "formula: COUNTD([Id (Activity - Open Email)])",
|
||||
"field: Id (Activity - Email Delivered)": "",
|
||||
"field: Program Name": "",
|
||||
"field: Active": "",
|
||||
"field: Id": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/EmailPerformancebyCampaign/EmailPerformancebyCampaign/Campaign List",
|
||||
"title": "Campaign List",
|
||||
@ -244,25 +260,26 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Delivery Rate": "formula: ZN([Delivered Email]/[Sent Email])",
|
||||
"Name": "",
|
||||
"Id (Activity - Click Email)": "",
|
||||
"Activity Date": "",
|
||||
"Clickthrough Rate": "formula: [Clickthrough Emails]/[Delivered Email]",
|
||||
"Open Rate": "formula: ZN([Opened Email]/[Delivered Email])",
|
||||
"Measure Names": "",
|
||||
"Sent Email": "formula: COUNTD([Id])",
|
||||
"Delivered Email": "formula: COUNTD([Id (Activity - Email Delivered)])",
|
||||
"ID": "",
|
||||
"Id (Activity - Open Email)": "",
|
||||
"Clickthrough Emails": "formula: COUNTD([Id (Activity - Click Email)])",
|
||||
"Click-to-Open": "formula: ZN([Clickthrough Emails]\r\n/ \r\n[Opened Email])",
|
||||
"Opened Email": "formula: COUNTD([Id (Activity - Open Email)])",
|
||||
"Id (Activity - Email Delivered)": "",
|
||||
"Program Name": "",
|
||||
"Measure Values": "",
|
||||
"Active": "",
|
||||
"Id": ""
|
||||
"luid": "",
|
||||
"field: Delivery Rate": "formula: ZN([Delivered Email]/[Sent Email])",
|
||||
"field: Name": "",
|
||||
"field: Id (Activity - Click Email)": "",
|
||||
"field: Activity Date": "",
|
||||
"field: Clickthrough Rate": "formula: [Clickthrough Emails]/[Delivered Email]",
|
||||
"field: Open Rate": "formula: ZN([Opened Email]/[Delivered Email])",
|
||||
"field: Measure Names": "",
|
||||
"field: Sent Email": "formula: COUNTD([Id])",
|
||||
"field: Delivered Email": "formula: COUNTD([Id (Activity - Email Delivered)])",
|
||||
"field: ID": "",
|
||||
"field: Id (Activity - Open Email)": "",
|
||||
"field: Clickthrough Emails": "formula: COUNTD([Id (Activity - Click Email)])",
|
||||
"field: Click-to-Open": "formula: ZN([Clickthrough Emails]\r\n/ \r\n[Opened Email])",
|
||||
"field: Opened Email": "formula: COUNTD([Id (Activity - Open Email)])",
|
||||
"field: Id (Activity - Email Delivered)": "",
|
||||
"field: Program Name": "",
|
||||
"field: Measure Values": "",
|
||||
"field: Active": "",
|
||||
"field: Id": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/EmailPerformancebyCampaign/EmailPerformancebyCampaign/Summary",
|
||||
"title": "Summary",
|
||||
@ -335,21 +352,22 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Delivery Rate": "formula: ZN([Delivered Email]/[Sent Email])",
|
||||
"Name": "",
|
||||
"Id (Activity - Click Email)": "",
|
||||
"Activity Date": "",
|
||||
"Open Rate": "formula: ZN([Opened Email]/[Delivered Email])",
|
||||
"Sent Email": "formula: COUNTD([Id])",
|
||||
"Delivered Email": "formula: COUNTD([Id (Activity - Email Delivered)])",
|
||||
"ID": "",
|
||||
"Id (Activity - Open Email)": "",
|
||||
"Clickthrough Emails": "formula: COUNTD([Id (Activity - Click Email)])",
|
||||
"Click-to-Open": "formula: ZN([Clickthrough Emails]\r\n/ \r\n[Opened Email])",
|
||||
"Opened Email": "formula: COUNTD([Id (Activity - Open Email)])",
|
||||
"Id (Activity - Email Delivered)": "",
|
||||
"Program Name": "",
|
||||
"Id": ""
|
||||
"luid": "",
|
||||
"field: Delivery Rate": "formula: ZN([Delivered Email]/[Sent Email])",
|
||||
"field: Name": "",
|
||||
"field: Id (Activity - Click Email)": "",
|
||||
"field: Activity Date": "",
|
||||
"field: Open Rate": "formula: ZN([Opened Email]/[Delivered Email])",
|
||||
"field: Sent Email": "formula: COUNTD([Id])",
|
||||
"field: Delivered Email": "formula: COUNTD([Id (Activity - Email Delivered)])",
|
||||
"field: ID": "",
|
||||
"field: Id (Activity - Open Email)": "",
|
||||
"field: Clickthrough Emails": "formula: COUNTD([Id (Activity - Click Email)])",
|
||||
"field: Click-to-Open": "formula: ZN([Clickthrough Emails]\r\n/ \r\n[Opened Email])",
|
||||
"field: Opened Email": "formula: COUNTD([Id (Activity - Open Email)])",
|
||||
"field: Id (Activity - Email Delivered)": "",
|
||||
"field: Program Name": "",
|
||||
"field: Id": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/EmailPerformancebyCampaign/EmailPerformancebyCampaign/Mobile - Sent by Campaign",
|
||||
"title": "Mobile - Sent by Campaign",
|
||||
@ -414,6 +432,20 @@
|
||||
"runId": "tableau-test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"entityType": "dashboard",
|
||||
"entityUrn": "urn:li:dashboard:(tableau,8f7dd564-36b6-593f-3c6f-687ad06cd40b)",
|
||||
"changeType": "UPSERT",
|
||||
"aspectName": "dashboardUsageStatistics",
|
||||
"aspect": {
|
||||
"value": "{\"timestampMillis\": 1638860400000, \"partitionSpec\": {\"type\": \"FULL_TABLE\", \"partition\": \"FULL_TABLE_SNAPSHOT\"}, \"viewsCount\": 3}",
|
||||
"contentType": "application/json"
|
||||
},
|
||||
"systemMetadata": {
|
||||
"lastObserved": 1638860400000,
|
||||
"runId": "tableau-test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"proposedSnapshot": {
|
||||
"com.linkedin.pegasus2avro.metadata.snapshot.DashboardSnapshot": {
|
||||
@ -421,7 +453,9 @@
|
||||
"aspects": [
|
||||
{
|
||||
"com.linkedin.pegasus2avro.dashboard.DashboardInfo": {
|
||||
"customProperties": {},
|
||||
"customProperties": {
|
||||
"luid": "fc9ea488-f810-4fa8-ac19-aa96018b5d66"
|
||||
},
|
||||
"title": "Email Performance by Campaign",
|
||||
"description": "",
|
||||
"charts": [
|
||||
@ -579,7 +613,8 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Custom SQL Query": ""
|
||||
"luid": "",
|
||||
"field: Custom SQL Query": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/dvdrental/Sheet1",
|
||||
"title": "Sheet 1",
|
||||
@ -652,13 +687,14 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"payment_date": "",
|
||||
"amount": "",
|
||||
"Custom SQL Query": "",
|
||||
"First Name": "",
|
||||
"customer_id": "",
|
||||
"rental_id": "",
|
||||
"Last Name": ""
|
||||
"luid": "",
|
||||
"field: payment_date": "",
|
||||
"field: amount": "",
|
||||
"field: Custom SQL Query": "",
|
||||
"field: First Name": "",
|
||||
"field: customer_id": "",
|
||||
"field: rental_id": "",
|
||||
"field: Last Name": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/dvdrental/Sheet2",
|
||||
"title": "Sheet 2",
|
||||
@ -731,9 +767,10 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Category": "",
|
||||
"Segment": "",
|
||||
"Customer Name": ""
|
||||
"luid": "",
|
||||
"field: Category": "",
|
||||
"field: Segment": "",
|
||||
"field: Customer Name": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/dvdrental/Sheet3",
|
||||
"title": "Sheet 3",
|
||||
@ -814,7 +851,9 @@
|
||||
"aspects": [
|
||||
{
|
||||
"com.linkedin.pegasus2avro.dashboard.DashboardInfo": {
|
||||
"customProperties": {},
|
||||
"customProperties": {
|
||||
"luid": ""
|
||||
},
|
||||
"title": "dvd Rental Dashboard",
|
||||
"description": "",
|
||||
"charts": [
|
||||
@ -884,7 +923,9 @@
|
||||
"aspects": [
|
||||
{
|
||||
"com.linkedin.pegasus2avro.dashboard.DashboardInfo": {
|
||||
"customProperties": {},
|
||||
"customProperties": {
|
||||
"luid": ""
|
||||
},
|
||||
"title": "Story 1",
|
||||
"description": "",
|
||||
"charts": [],
|
||||
@ -1023,17 +1064,18 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Opened": "",
|
||||
"Total # Request": "formula: // This is a calculated field\r\n// It shows the total number of problems ignoring opened date\r\n\r\n{ EXCLUDE [Opened]: COUNTD([Number])}",
|
||||
"Due date": "",
|
||||
"Priority": "",
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It shows if an accident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\nIF [Active]=FALSE \r\nAND\r\n[Closed]>[Due date]\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\n[Active]=TRUE \r\nAND NOW()>[Due date]\r\n\r\nTHEN \"Overdue\"\r\nELSE \"Non Overdue\"\r\nEND",
|
||||
"Active": "",
|
||||
"Current Year Total Cases": "formula: // This is a calculated field\r\n// It counts each distinct request made in the last year. The \"exclude\" is used to avoid filters interfering in the final result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Total Active Requests": "formula: // This is a calculated field\r\n// It counts each distinct active request. The \"exclude\" is used to avoid filters interfering with the final result \r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Active]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Number": "",
|
||||
"Closed": ""
|
||||
"luid": "",
|
||||
"field: Opened": "",
|
||||
"field: Total # Request": "formula: // This is a calculated field\r\n// It shows the total number of problems ignoring opened date\r\n\r\n{ EXCLUDE [Opened]: COUNTD([Number])}",
|
||||
"field: Due date": "",
|
||||
"field: Priority": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It shows if an accident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\nIF [Active]=FALSE \r\nAND\r\n[Closed]>[Due date]\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\n[Active]=TRUE \r\nAND NOW()>[Due date]\r\n\r\nTHEN \"Overdue\"\r\nELSE \"Non Overdue\"\r\nEND",
|
||||
"field: Active": "",
|
||||
"field: Current Year Total Cases": "formula: // This is a calculated field\r\n// It counts each distinct request made in the last year. The \"exclude\" is used to avoid filters interfering in the final result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Total Active Requests": "formula: // This is a calculated field\r\n// It counts each distinct active request. The \"exclude\" is used to avoid filters interfering with the final result \r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Active]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Number": "",
|
||||
"field: Closed": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Opened Requests",
|
||||
"title": "Opened Requests",
|
||||
@ -1109,16 +1151,17 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Opened": "",
|
||||
"Due date": "",
|
||||
"Priority": "",
|
||||
"Name": "",
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It shows if an accident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\nIF [Active]=FALSE \r\nAND\r\n[Closed]>[Due date]\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\n[Active]=TRUE \r\nAND NOW()>[Due date]\r\n\r\nTHEN \"Overdue\"\r\nELSE \"Non Overdue\"\r\nEND",
|
||||
"Active": "",
|
||||
"Current Year Total Cases": "formula: // This is a calculated field\r\n// It counts each distinct request made in the last year. The \"exclude\" is used to avoid filters interfering in the final result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Number": "",
|
||||
"Closed": ""
|
||||
"luid": "",
|
||||
"field: Opened": "",
|
||||
"field: Due date": "",
|
||||
"field: Priority": "",
|
||||
"field: Name": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It shows if an accident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\nIF [Active]=FALSE \r\nAND\r\n[Closed]>[Due date]\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\n[Active]=TRUE \r\nAND NOW()>[Due date]\r\n\r\nTHEN \"Overdue\"\r\nELSE \"Non Overdue\"\r\nEND",
|
||||
"field: Active": "",
|
||||
"field: Current Year Total Cases": "formula: // This is a calculated field\r\n// It counts each distinct request made in the last year. The \"exclude\" is used to avoid filters interfering in the final result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Number": "",
|
||||
"field: Closed": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Top 10 Items by Requests and YoY Change",
|
||||
"title": "Top 10 Items by Requests and YoY Change",
|
||||
@ -1194,17 +1237,18 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It checks if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])> max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active]=TRUE) \r\nAND NOW()> MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"Number": "",
|
||||
"Priority": "",
|
||||
"Due date": "",
|
||||
"Total # Problems": "formula: // This is a calculated field using a level of detail calculation (LOD)\r\n// It counts each distinct problems ignoring date\r\n\r\n{ EXCLUDE [Opened]: COUNTD([Number])}",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Closed": "",
|
||||
"Active": "",
|
||||
"Current Year Total Cases": "formula: // This is a calculated field\r\n// It counts each disctinct problem. The \"exclude\" is used to avoid filters interfering with the result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Total Active Problems": "formula: // This is a calculated field using a level of detail calculation (LOD)\r\n// It counts each distinct active problem. The \"exclude\" is used to avoid filters interfering with the result \r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Active]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}"
|
||||
"luid": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It checks if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])> max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active]=TRUE) \r\nAND NOW()> MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"field: Number": "",
|
||||
"field: Priority": "",
|
||||
"field: Due date": "",
|
||||
"field: Total # Problems": "formula: // This is a calculated field using a level of detail calculation (LOD)\r\n// It counts each distinct problems ignoring date\r\n\r\n{ EXCLUDE [Opened]: COUNTD([Number])}",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Closed": "",
|
||||
"field: Active": "",
|
||||
"field: Current Year Total Cases": "formula: // This is a calculated field\r\n// It counts each disctinct problem. The \"exclude\" is used to avoid filters interfering with the result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Total Active Problems": "formula: // This is a calculated field using a level of detail calculation (LOD)\r\n// It counts each distinct active problem. The \"exclude\" is used to avoid filters interfering with the result \r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Active]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}"
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Opened Problems",
|
||||
"title": "Opened Problems",
|
||||
@ -1280,16 +1324,17 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"% of Overdue": "formula: // This is a calculated field\r\n// It show the percentage incidents which are overdue\r\n\r\n(IF ATTR([Overdue]=\"Overdue\")\r\nTHEN COUNTD([Number])\r\nEND)\r\n/\r\nATTR({ EXCLUDE [Overdue]:\r\nCOUNTD([Number])})",
|
||||
"Priority": "",
|
||||
"Category (Incident)": "",
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It shows if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])>max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active] = TRUE) \r\nAND NOW() > MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"Number": "",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Due date": "",
|
||||
"Active": "",
|
||||
"Closed": ""
|
||||
"luid": "",
|
||||
"field: % of Overdue": "formula: // This is a calculated field\r\n// It show the percentage incidents which are overdue\r\n\r\n(IF ATTR([Overdue]=\"Overdue\")\r\nTHEN COUNTD([Number])\r\nEND)\r\n/\r\nATTR({ EXCLUDE [Overdue]:\r\nCOUNTD([Number])})",
|
||||
"field: Priority": "",
|
||||
"field: Category (Incident)": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It shows if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])>max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active] = TRUE) \r\nAND NOW() > MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"field: Number": "",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Due date": "",
|
||||
"field: Active": "",
|
||||
"field: Closed": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Overdue",
|
||||
"title": "Overdue",
|
||||
@ -1362,12 +1407,13 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Number": "",
|
||||
"Priority": "",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"% of critical and high priority": "formula: // This is a calculated field\r\n// It shows the percentage of critical or high priority problems\r\n\r\nCOUNTD(IF [Priority]=1\r\nOR [Priority]=2\r\nTHEN [Number]\r\nEND)\r\n/\r\nCOUNTD([Number])",
|
||||
"Active": ""
|
||||
"luid": "",
|
||||
"field: Number": "",
|
||||
"field: Priority": "",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: % of critical and high priority": "formula: // This is a calculated field\r\n// It shows the percentage of critical or high priority problems\r\n\r\nCOUNTD(IF [Priority]=1\r\nOR [Priority]=2\r\nTHEN [Number]\r\nEND)\r\n/\r\nCOUNTD([Number])",
|
||||
"field: Active": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/High and Critical Priority Problems",
|
||||
"title": "High and Critical Priority Problems",
|
||||
@ -1443,16 +1489,17 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Priority": "",
|
||||
"Current Year Total Cases": "formula: // This is a calculated field using level of detail calculation\r\n// It counts each distinct incident. The exclude is used to guarantee that filters will not interfere in the result\r\n\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Category (Incident)": "",
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It shows if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])>max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active] = TRUE) \r\nAND NOW() > MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"Number": "",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Due date": "",
|
||||
"Active": "",
|
||||
"Closed": ""
|
||||
"luid": "",
|
||||
"field: Priority": "",
|
||||
"field: Current Year Total Cases": "formula: // This is a calculated field using level of detail calculation\r\n// It counts each distinct incident. The exclude is used to guarantee that filters will not interfere in the result\r\n\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Category (Incident)": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It shows if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])>max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active] = TRUE) \r\nAND NOW() > MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"field: Number": "",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Due date": "",
|
||||
"field: Active": "",
|
||||
"field: Closed": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Total Incidents by Category and YoY Change",
|
||||
"title": "Total Incidents by Category and YoY Change",
|
||||
@ -1525,15 +1572,16 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Number": "",
|
||||
"Priority": "",
|
||||
"Problems with Related Incidents": "formula: // This is a calculated field\r\n// It counts each distinct problems which has a related incident\r\n\r\nCOUNT(IF ([Related Incidents]>0\r\nAND NOT ISNULL([Related Incidents]))=true\r\nTHEN [Number]\r\nEND)",
|
||||
"Related Incidents": "",
|
||||
"Opened": "",
|
||||
"% of known error": "formula: // This is a calculated field\r\n// It shows the percentage of problems that are known errors\r\n\r\nCOUNTD(IF [Known error]=TRUE\r\nTHEN [Number] END)\r\n/\r\nCOUNTD([Number])",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Known error": "",
|
||||
"Active": ""
|
||||
"luid": "",
|
||||
"field: Number": "",
|
||||
"field: Priority": "",
|
||||
"field: Problems with Related Incidents": "formula: // This is a calculated field\r\n// It counts each distinct problems which has a related incident\r\n\r\nCOUNT(IF ([Related Incidents]>0\r\nAND NOT ISNULL([Related Incidents]))=true\r\nTHEN [Number]\r\nEND)",
|
||||
"field: Related Incidents": "",
|
||||
"field: Opened": "",
|
||||
"field: % of known error": "formula: // This is a calculated field\r\n// It shows the percentage of problems that are known errors\r\n\r\nCOUNTD(IF [Known error]=TRUE\r\nTHEN [Number] END)\r\n/\r\nCOUNTD([Number])",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Known error": "",
|
||||
"field: Active": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Known Errors",
|
||||
"title": "Known Errors",
|
||||
@ -1609,14 +1657,15 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Opened": "",
|
||||
"Due date": "",
|
||||
"Priority": "",
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It shows if an accident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\nIF [Active]=FALSE \r\nAND\r\n[Closed]>[Due date]\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\n[Active]=TRUE \r\nAND NOW()>[Due date]\r\n\r\nTHEN \"Overdue\"\r\nELSE \"Non Overdue\"\r\nEND",
|
||||
"Active": "",
|
||||
"% of Overdue": "formula: // This is a calculated field\r\n// It shows the percentage of incidents which are overdue\r\n\r\n(IF ATTR([Overdue]= \"Overdue\")\r\nTHEN COUNTD([Number])\r\nEND)\r\n/\r\nATTR({ EXCLUDE [Overdue]:\r\nCOUNTD([Number])})",
|
||||
"Number": "",
|
||||
"Closed": ""
|
||||
"luid": "",
|
||||
"field: Opened": "",
|
||||
"field: Due date": "",
|
||||
"field: Priority": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It shows if an accident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\nIF [Active]=FALSE \r\nAND\r\n[Closed]>[Due date]\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\n[Active]=TRUE \r\nAND NOW()>[Due date]\r\n\r\nTHEN \"Overdue\"\r\nELSE \"Non Overdue\"\r\nEND",
|
||||
"field: Active": "",
|
||||
"field: % of Overdue": "formula: // This is a calculated field\r\n// It shows the percentage of incidents which are overdue\r\n\r\n(IF ATTR([Overdue]= \"Overdue\")\r\nTHEN COUNTD([Number])\r\nEND)\r\n/\r\nATTR({ EXCLUDE [Overdue]:\r\nCOUNTD([Number])})",
|
||||
"field: Number": "",
|
||||
"field: Closed": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Overdue Requests",
|
||||
"title": "Overdue Requests",
|
||||
@ -1692,14 +1741,15 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Priority": "",
|
||||
"Category (Incident)": "",
|
||||
"Time to Close an Incident (seconds)": "formula: // This is a calculated field\r\n// It calculates the difference in seconds between opening and closing an incident\r\n\r\n// Check if closed date is valid\r\nIF [Closed]>[Opened]\r\nTHEN\r\n//Calculate difference\r\nDATEDIFF('second', [Opened], [Closed])\r\nEND",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Active": "",
|
||||
"Closed": "",
|
||||
"Time to Close an Incident": "formula: // This is a calculated field\r\n// It transforms time in seconds into DD:HH:MM:SS format\r\nSTR(INT(AVG([Time to Close an Incident (seconds)])/86400)) \r\n \r\n+ \" day(s) \" + \r\n \r\nIF (INT(AVG([Time to Close an Incident (seconds)])%86400/3600)) \r\n< 10 THEN \"0\" ELSE \"\" END + STR(INT(AVG([Time to Close an Incident (seconds)])%86400/3600))\r\n \r\n+ \":\" + \r\n \r\nIF INT(AVG([Time to Close an Incident (seconds)])%3600/60) \r\n< 10 THEN \"0\" ELSE \"\" END + STR(INT(AVG([Time to Close an Incident (seconds)])%3600/60)) \r\n \r\n \r\n+ \":\" + \r\n \r\nIF INT(AVG([Time to Close an Incident (seconds)]) %3600 %60) \r\n< 10 THEN \"0\" ELSE \"\" END + STR(INT(AVG([Time to Close an Incident (seconds)]) %3600 %60))"
|
||||
"luid": "",
|
||||
"field: Priority": "",
|
||||
"field: Category (Incident)": "",
|
||||
"field: Time to Close an Incident (seconds)": "formula: // This is a calculated field\r\n// It calculates the difference in seconds between opening and closing an incident\r\n\r\n// Check if closed date is valid\r\nIF [Closed]>[Opened]\r\nTHEN\r\n//Calculate difference\r\nDATEDIFF('second', [Opened], [Closed])\r\nEND",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Active": "",
|
||||
"field: Closed": "",
|
||||
"field: Time to Close an Incident": "formula: // This is a calculated field\r\n// It transforms time in seconds into DD:HH:MM:SS format\r\nSTR(INT(AVG([Time to Close an Incident (seconds)])/86400)) \r\n \r\n+ \" day(s) \" + \r\n \r\nIF (INT(AVG([Time to Close an Incident (seconds)])%86400/3600)) \r\n< 10 THEN \"0\" ELSE \"\" END + STR(INT(AVG([Time to Close an Incident (seconds)])%86400/3600))\r\n \r\n+ \":\" + \r\n \r\nIF INT(AVG([Time to Close an Incident (seconds)])%3600/60) \r\n< 10 THEN \"0\" ELSE \"\" END + STR(INT(AVG([Time to Close an Incident (seconds)])%3600/60)) \r\n \r\n \r\n+ \":\" + \r\n \r\nIF INT(AVG([Time to Close an Incident (seconds)]) %3600 %60) \r\n< 10 THEN \"0\" ELSE \"\" END + STR(INT(AVG([Time to Close an Incident (seconds)]) %3600 %60))"
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/AVG Time to Solve an Incident",
|
||||
"title": "AVG Time to Solve an Incident",
|
||||
@ -1772,12 +1822,13 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Opened": "",
|
||||
"% made SLA": "formula: // This is a calculated field\r\n// It shows the percentage of requests which made SLA\r\n\r\nCOUNTD(IF [Made SLA]= TRUE\r\nTHEN [Number]\r\nEND)\r\n/\r\nCOUNTD([Number])",
|
||||
"Priority": "",
|
||||
"Active": "",
|
||||
"Made SLA": "",
|
||||
"Number": ""
|
||||
"luid": "",
|
||||
"field: Opened": "",
|
||||
"field: % made SLA": "formula: // This is a calculated field\r\n// It shows the percentage of requests which made SLA\r\n\r\nCOUNTD(IF [Made SLA]= TRUE\r\nTHEN [Number]\r\nEND)\r\n/\r\nCOUNTD([Number])",
|
||||
"field: Priority": "",
|
||||
"field: Active": "",
|
||||
"field: Made SLA": "",
|
||||
"field: Number": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Made SLA?",
|
||||
"title": "Made SLA?",
|
||||
@ -1853,11 +1904,12 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Priority": "",
|
||||
"Number": "",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Active": ""
|
||||
"luid": "",
|
||||
"field: Priority": "",
|
||||
"field: Number": "",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Active": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/ExecutiveDashboard/Tooltip-IncidentBreakdownbyPriority",
|
||||
"title": "Tooltip - Incident Breakdown by Priority",
|
||||
@ -1930,15 +1982,16 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It checks if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])> max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active]=TRUE) \r\nAND NOW()> MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"Number": "",
|
||||
"Priority": "",
|
||||
"Due date": "",
|
||||
"% of Overdue": "formula: // This is a calculated field\r\n// It shows the percentage of incidents that are overdue\r\n\r\nIFNULL((IF ATTR([Overdue]= \"Overdue\")\r\nTHEN COUNTD([Number])\r\nEND),0)\r\n/\r\nATTR({ EXCLUDE [Overdue]:\r\nCOUNTD([Number])})",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Closed": "",
|
||||
"Active": ""
|
||||
"luid": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It checks if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])> max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active]=TRUE) \r\nAND NOW()> MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"field: Number": "",
|
||||
"field: Priority": "",
|
||||
"field: Due date": "",
|
||||
"field: % of Overdue": "formula: // This is a calculated field\r\n// It shows the percentage of incidents that are overdue\r\n\r\nIFNULL((IF ATTR([Overdue]= \"Overdue\")\r\nTHEN COUNTD([Number])\r\nEND),0)\r\n/\r\nATTR({ EXCLUDE [Overdue]:\r\nCOUNTD([Number])})",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Closed": "",
|
||||
"field: Active": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Overdue Problems",
|
||||
"title": "Overdue Problems",
|
||||
@ -2014,11 +2067,12 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Number": "",
|
||||
"Priority": "",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Active": ""
|
||||
"luid": "",
|
||||
"field: Number": "",
|
||||
"field: Priority": "",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Active": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/ExecutiveDashboard/Tooltip-ProblemBreakdownbyPriority",
|
||||
"title": "Tooltip - Problem Breakdown by Priority",
|
||||
@ -2094,11 +2148,12 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Opened": "",
|
||||
"Priority": "",
|
||||
"Active": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Number": ""
|
||||
"luid": "",
|
||||
"field: Opened": "",
|
||||
"field: Priority": "",
|
||||
"field: Active": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Number": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/ExecutiveDashboard/Tooltip-RequestBreakdownbyPriority",
|
||||
"title": "Tooltip - Request Breakdown by Priority",
|
||||
@ -2174,13 +2229,14 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Number": "",
|
||||
"Priority": "",
|
||||
"Time Span Breakdown": "formula: // This is a calculated field\r\n// It groups problems accordingly with their ages\r\n\r\nIF [Age of Problems]< 2592000\r\nTHEN \"Under 30 d\"\r\nELSEIF [Age of Problems] > 7776000\r\nTHEN \"+ than 90d\"\r\nELSE \" 30-90 d\"\r\nEND",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Age of Problems": "formula: //Calculates the age of active problems since opened until now\r\n\r\nDATEDIFF('second', [Opened], NOW())",
|
||||
"Active": ""
|
||||
"luid": "",
|
||||
"field: Number": "",
|
||||
"field: Priority": "",
|
||||
"field: Time Span Breakdown": "formula: // This is a calculated field\r\n// It groups problems accordingly with their ages\r\n\r\nIF [Age of Problems]< 2592000\r\nTHEN \"Under 30 d\"\r\nELSEIF [Age of Problems] > 7776000\r\nTHEN \"+ than 90d\"\r\nELSE \" 30-90 d\"\r\nEND",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if the year of opened is equal the max year of the dataset \r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Age of Problems": "formula: //Calculates the age of active problems since opened until now\r\n\r\nDATEDIFF('second', [Opened], NOW())",
|
||||
"field: Active": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Age of Active Problems",
|
||||
"title": "Age of Active Problems",
|
||||
@ -2256,18 +2312,19 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"Priority": "",
|
||||
"Current Year Total Cases": "formula: // This is a calculated field using level of detail calculation\r\n// It counts each distinct incident. The exclude is used to guarantee that filters will not interfere in the result\r\n\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Category (Incident)": "",
|
||||
"Overdue": "formula: // This is a calculated field\r\n// It shows if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])>max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active] = TRUE) \r\nAND NOW() > MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"Total Active Incidents": "formula: // This is a calculated field\r\n// It counts each distinct incident. The \"exclude\" is used to avoid filters interfering in the final result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Active]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"Number": "",
|
||||
"Opened": "",
|
||||
"Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"Opened Month Tooltip": "formula: // This is an IF statment using the opened field to allow for different formats\r\n// original used on columns for line charts are formatted in starting letter of month i.e. J for January\r\n// This version formats to full month name for the tooltip \r\n\r\n\r\n// The IF statement names the month based on the month number of the datefield\r\nIF DATEPART('month', [Opened]) = 1 THEN 'January'\r\nELSEIF DATEPART('month', [Opened]) = 2 THEN 'February'\r\nELSEIF DATEPART('month', [Opened]) = 3 THEN 'March'\r\nELSEIF DATEPART('month', [Opened]) = 4 THEN 'April'\r\nELSEIF DATEPART('month', [Opened]) = 5 THEN 'May'\r\nELSEIF DATEPART('month', [Opened]) = 6 THEN 'June'\r\nELSEIF DATEPART('month', [Opened]) = 7 THEN 'July'\r\nELSEIF DATEPART('month', [Opened]) = 8 THEN 'August'\r\nELSEIF DATEPART('month', [Opened]) = 9 THEN 'September'\r\nELSEIF DATEPART('month', [Opened]) = 10 THEN 'October'\r\nELSEIF DATEPART('month', [Opened]) = 11 THEN 'Novemeber'\r\nELSEIF DATEPART('month', [Opened]) = 12 THEN 'December'\r\nELSE NULL\r\nEND",
|
||||
"Due date": "",
|
||||
"Active": "",
|
||||
"Closed": ""
|
||||
"luid": "",
|
||||
"field: Priority": "",
|
||||
"field: Current Year Total Cases": "formula: // This is a calculated field using level of detail calculation\r\n// It counts each distinct incident. The exclude is used to guarantee that filters will not interfere in the result\r\n\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Max Year?]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Category (Incident)": "",
|
||||
"field: Overdue": "formula: // This is a calculated field\r\n// It shows if an incident is overdue\r\n\r\n//Check overdue cases among inactive incidents\r\n{ FIXED [Number]:\r\n\r\nIF MAX([Active]=FALSE) \r\nAND\r\nmax([Closed])>max([Due date])\r\n\r\nOR\r\n//Check overdue cases among active incidents\r\nMAX([Active] = TRUE) \r\nAND NOW() > MAX([Due date]) \r\n\r\nTHEN \"Overdue\"\r\nEND\r\n}",
|
||||
"field: Total Active Incidents": "formula: // This is a calculated field\r\n// It counts each distinct incident. The \"exclude\" is used to avoid filters interfering in the final result\r\n\r\n{EXCLUDE [Opened], [Overdue], [Max Year?]: \r\nCOUNTD(IF [Active]=TRUE\r\nTHEN [Number]\r\nEND)\r\n}",
|
||||
"field: Number": "",
|
||||
"field: Opened": "",
|
||||
"field: Max Year?": "formula: // This is a calculated field\r\n// It shows TRUE/FALSE if opened date equals maximum year in the dataset\r\n\r\nDATEPART(\"year\", [Opened]) = DATEPART(\"year\", {MAX([Opened])})",
|
||||
"field: Opened Month Tooltip": "formula: // This is an IF statment using the opened field to allow for different formats\r\n// original used on columns for line charts are formatted in starting letter of month i.e. J for January\r\n// This version formats to full month name for the tooltip \r\n\r\n\r\n// The IF statement names the month based on the month number of the datefield\r\nIF DATEPART('month', [Opened]) = 1 THEN 'January'\r\nELSEIF DATEPART('month', [Opened]) = 2 THEN 'February'\r\nELSEIF DATEPART('month', [Opened]) = 3 THEN 'March'\r\nELSEIF DATEPART('month', [Opened]) = 4 THEN 'April'\r\nELSEIF DATEPART('month', [Opened]) = 5 THEN 'May'\r\nELSEIF DATEPART('month', [Opened]) = 6 THEN 'June'\r\nELSEIF DATEPART('month', [Opened]) = 7 THEN 'July'\r\nELSEIF DATEPART('month', [Opened]) = 8 THEN 'August'\r\nELSEIF DATEPART('month', [Opened]) = 9 THEN 'September'\r\nELSEIF DATEPART('month', [Opened]) = 10 THEN 'October'\r\nELSEIF DATEPART('month', [Opened]) = 11 THEN 'Novemeber'\r\nELSEIF DATEPART('month', [Opened]) = 12 THEN 'December'\r\nELSE NULL\r\nEND",
|
||||
"field: Due date": "",
|
||||
"field: Active": "",
|
||||
"field: Closed": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/t/acryl/authoring/ExecutiveDashboard/ExecutiveDashboard/Opened Incidents",
|
||||
"title": "Opened Incidents",
|
||||
@ -2339,7 +2396,9 @@
|
||||
"aspects": [
|
||||
{
|
||||
"com.linkedin.pegasus2avro.dashboard.DashboardInfo": {
|
||||
"customProperties": {},
|
||||
"customProperties": {
|
||||
"luid": ""
|
||||
},
|
||||
"title": "Executive Dashboard",
|
||||
"description": "",
|
||||
"charts": [
|
||||
@ -2492,9 +2551,10 @@
|
||||
{
|
||||
"com.linkedin.pegasus2avro.chart.ChartInfo": {
|
||||
"customProperties": {
|
||||
"staff_last_name": "",
|
||||
"amount": "",
|
||||
"customer_first_name": ""
|
||||
"luid": "",
|
||||
"field: staff_last_name": "",
|
||||
"field: amount": "",
|
||||
"field: customer_first_name": ""
|
||||
},
|
||||
"externalUrl": "https://do-not-connect/#/site/acryl/views/Workbookpublishedds/Sheet1",
|
||||
"title": "published sheet ds",
|
||||
|
||||
@ -26,32 +26,22 @@ def _read_response(file_name):
|
||||
return data
|
||||
|
||||
|
||||
def define_query_metadata_func(workbook_0: str, workbook_all: str): # type: ignore
|
||||
def define_query_metadata_func(workbook_all: str): # type: ignore
|
||||
def side_effect_query_metadata(query):
|
||||
if "workbooksConnection (first:0" in query:
|
||||
return _read_response(workbook_0)
|
||||
|
||||
if "workbooksConnection (first:3" in query:
|
||||
if "workbooksConnection (first:10," in query:
|
||||
return _read_response(workbook_all)
|
||||
|
||||
if "embeddedDatasourcesConnection (first:0" in query:
|
||||
return _read_response("embeddedDatasourcesConnection_0.json")
|
||||
|
||||
if "embeddedDatasourcesConnection (first:8" in query:
|
||||
if "embeddedDatasourcesConnection (first:10," in query:
|
||||
return _read_response("embeddedDatasourcesConnection_all.json")
|
||||
|
||||
if "publishedDatasourcesConnection (first:0" in query:
|
||||
return _read_response("publishedDatasourcesConnection_0.json")
|
||||
|
||||
if "publishedDatasourcesConnection (first:2" in query:
|
||||
if "publishedDatasourcesConnection (first:10," in query:
|
||||
return _read_response("publishedDatasourcesConnection_all.json")
|
||||
|
||||
if "customSQLTablesConnection (first:0" in query:
|
||||
return _read_response("customSQLTablesConnection_0.json")
|
||||
|
||||
if "customSQLTablesConnection (first:2" in query:
|
||||
if "customSQLTablesConnection (first:10," in query:
|
||||
return _read_response("customSQLTablesConnection_all.json")
|
||||
|
||||
raise Exception(f"Missing mock for query: {query}")
|
||||
|
||||
return side_effect_query_metadata
|
||||
|
||||
|
||||
@ -146,7 +136,7 @@ def test_tableau_ingest(pytestconfig, tmp_path):
|
||||
output_file_name: str = "tableau_mces.json"
|
||||
golden_file_name: str = "tableau_mces_golden.json"
|
||||
side_effect_query_metadata = define_query_metadata_func(
|
||||
"workbooksConnection_0.json", "workbooksConnection_all.json"
|
||||
"workbooksConnection_all.json"
|
||||
)
|
||||
tableau_ingest_common(
|
||||
pytestconfig,
|
||||
@ -157,23 +147,6 @@ def test_tableau_ingest(pytestconfig, tmp_path):
|
||||
)
|
||||
|
||||
|
||||
@freeze_time(FROZEN_TIME)
|
||||
@pytest.mark.slow_unit
|
||||
def test_tableau_usage_stat(pytestconfig, tmp_path):
|
||||
output_file_name: str = "tableau_stat_mces.json"
|
||||
golden_file_name: str = "tableau_state_mces_golden.json"
|
||||
func = define_query_metadata_func(
|
||||
"workbooksConnection_0.json", "workbooksConnection_state_all.json"
|
||||
)
|
||||
tableau_ingest_common(
|
||||
pytestconfig,
|
||||
tmp_path,
|
||||
func,
|
||||
golden_file_name,
|
||||
output_file_name,
|
||||
)
|
||||
|
||||
|
||||
def test_lineage_overrides():
|
||||
# Simple - specify platform instance to presto table
|
||||
assert (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user