From ba2201f4eab915b1e6796aec0d6745493673461b Mon Sep 17 00:00:00 2001 From: gauthk6 <58676663+gauthk6@users.noreply.github.com> Date: Thu, 31 Aug 2023 08:28:07 -0700 Subject: [PATCH] draft: implementation of lightdash connector. (#12957) * Fixes a bug while patching the description of a TestCase * Update docker-compose.yml * Update docker-compose.yml * Ran pre-commit checks and linter * Added some clarifying points and fixed some grammatical errors in the documentation for installation instructions. * revert changes made to docs * implementation of lightdash connector. * ui: add icon for lightdash service * chore: update lightdash image * chore: update the icon * Cleaned up code (took out debug statements, etc.). Still TODO: yield_dashboard_lineage_details() not being called just yet. * fix: ran linting * added null checks * Delete openmetadata-server.md --------- Co-authored-by: Sachin Chaurasiya Co-authored-by: Teddy --- ingestion/examples/workflows/lightdash.yaml | 24 +++ .../source/dashboard/lightdash/__init__.py | 0 .../source/dashboard/lightdash/client.py | 140 +++++++++++++++ .../source/dashboard/lightdash/connection.py | 68 +++++++ .../source/dashboard/lightdash/metadata.py | 169 ++++++++++++++++++ .../source/dashboard/lightdash/models.py | 46 +++++ .../cli_e2e/dashboard/lightdash/redshift.yaml | 25 +++ .../testConnections/dashboard/lightdash.json | 15 ++ .../dashboard/lightdashConnection.json | 60 +++++++ .../entity/services/dashboardService.json | 11 +- .../src/assets/img/service-icon-lightdash.png | Bin 0 -> 30534 bytes .../ui/src/constants/Services.constant.ts | 2 + .../ui/src/utils/DashboardServiceUtils.ts | 8 + .../resources/ui/src/utils/ServiceUtils.tsx | 5 + 14 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 ingestion/examples/workflows/lightdash.yaml create mode 100644 ingestion/src/metadata/ingestion/source/dashboard/lightdash/__init__.py create mode 100644 ingestion/src/metadata/ingestion/source/dashboard/lightdash/client.py create mode 100644 ingestion/src/metadata/ingestion/source/dashboard/lightdash/connection.py create mode 100644 ingestion/src/metadata/ingestion/source/dashboard/lightdash/metadata.py create mode 100644 ingestion/src/metadata/ingestion/source/dashboard/lightdash/models.py create mode 100644 ingestion/tests/cli_e2e/dashboard/lightdash/redshift.yaml create mode 100644 openmetadata-service/src/main/resources/json/data/testConnections/dashboard/lightdash.json create mode 100644 openmetadata-spec/src/main/resources/json/schema/entity/services/connections/dashboard/lightdashConnection.json create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-lightdash.png diff --git a/ingestion/examples/workflows/lightdash.yaml b/ingestion/examples/workflows/lightdash.yaml new file mode 100644 index 00000000000..822d55e5be8 --- /dev/null +++ b/ingestion/examples/workflows/lightdash.yaml @@ -0,0 +1,24 @@ +source: + type: lightdash + serviceName: local_test2 + serviceConnection: + config: + type: Lightdash + hostPort: https://app.lightdash.cloud + apiKey: + projectUUID: + spaceUUID: + sourceConfig: + config: + type: DashboardMetadata + dashboardFilterPattern: {} + chartFilterPattern: {} +sink: + type: metadata-rest + config: {} +workflowConfig: + openMetadataServerConfig: + hostPort: http://localhost:8585/api + authProvider: openmetadata + securityConfig: + jwtToken: "eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlzQm90IjpmYWxzZSwiaXNzIjoib3Blbi1tZXRhZGF0YS5vcmciLCJpYXQiOjE2NjM5Mzg0NjIsImVtYWlsIjoiYWRtaW5Ab3Blbm1ldGFkYXRhLm9yZyJ9.tS8um_5DKu7HgzGBzS1VTA5uUjKWOCU0B_j08WXBiEC0mr0zNREkqVfwFDD-d24HlNEbrqioLsBuFRiwIWKc1m_ZlVQbG7P36RUxhuv2vbSp80FKyNM-Tj93FDzq91jsyNmsQhyNv_fNr3TXfzzSPjHt8Go0FMMP66weoKMgW2PbXlhVKwEuXUHyakLLzewm9UMeQaEiRzhiTMU3UkLXcKbYEJJvfNFcLwSl9W8JCO_l0Yj3ud-qt_nQYEZwqW6u5nfdQllN133iikV4fM5QZsMCnm8Rq1mvLR0y9bmJiD7fwM1tmJ791TUWqmKaTnP49U493VanKpUAfzIiOiIbhg" diff --git a/ingestion/src/metadata/ingestion/source/dashboard/lightdash/__init__.py b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ingestion/src/metadata/ingestion/source/dashboard/lightdash/client.py b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/client.py new file mode 100644 index 00000000000..bc8ac667c49 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/client.py @@ -0,0 +1,140 @@ +# Copyright 2021 Collate +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +REST Auth & Client for Lightdash +""" +import traceback +from typing import List + +from metadata.ingestion.ometa.client import REST, ClientConfig +from metadata.ingestion.source.dashboard.lightdash.models import ( + LightdashChart, + LightdashDashboard, +) +from metadata.utils.logger import utils_logger + +logger = utils_logger() + + +class LightdashApiClient: + """ + REST Auth & Client for Lightdash + """ + + client: REST + + def __init__(self, config): + self.config = config + client_config = ClientConfig( + base_url=self.config.hostPort, + api_version="", + access_token=self.config.apiKey.get_secret_value(), + auth_header="Authorization", + auth_token_mode="ApiKey", + allow_redirects=True, + ) + self.client = REST(client_config) + + def get_org(self): + """GET api/org""" + return self.client.get( + "/api/v1/org", + ) + + def get_charts_list(self) -> List[LightdashChart]: + """ + Get List of all charts + """ + try: + response = self.client.get( + f"api/v1/projects/{self.config.projectUUID}/charts" + ) + response_json_results = response.get("results") + if response_json_results is None: + logger.warning( + "Failed to fetch the charts list for the Lightdash Connector" + ) + return [] + + if len(response_json_results) > 0: + charts_list = [] + for chart in response_json_results: + charts_list.append(LightdashChart(**chart)) + return charts_list + except Exception: + logger.debug(traceback.format_exc()) + logger.warning( + "Failed to fetch the charts list for the Lightdash Connector" + ) + return [] + + def get_dashboards_list(self) -> List[LightdashDashboard]: + """ + Get List of all charts + """ + + try: + response = self.client.get( + f"api/v1/projects/{self.config.projectUUID}/spaces/{self.config.spaceUUID}" + ) + results = response.get("results") + if results is None: + logger.warning( + "Failed to fetch the dashboard list for the Lightdash Connector" + ) + return [] + + dashboards_raw = results["dashboards"] + + if len(dashboards_raw) > 0: + dashboards_list = [] + for dashboard in dashboards_raw: + dashboards_list.append(LightdashDashboard(**dashboard)) + + self.add_dashboard_lineage(dashboards_list=dashboards_list) + return dashboards_list + except Exception: + logger.debug(traceback.format_exc()) + logger.warning( + "Failed to fetch the dashboard list for the Lightdash Connector" + ) + return [] + + def add_dashboard_lineage(self, dashboards_list) -> None: + charts_uuid_list = [] + for dashboard in dashboards_list: + response = self.client.get(f"api/v1/dashboards/{dashboard.uuid}") + response_json_results = response.get("results") + + if response_json_results is None: + logger.warning( + "Failed to fetch dashboard charts for the Lightdash Connector" + ) + return + + charts = response_json_results["tiles"] + charts_properties = [chart["properties"] for chart in charts] + + for chart in charts_properties: + charts_uuid_list.append(chart["savedChartUuid"]) + + dashboard.charts = self.get_charts_objects(charts_uuid_list) + + def get_charts_objects(self, charts_uuid_list) -> List[LightdashChart]: + all_charts = self.get_charts_list() + charts_objects = [] + + for chart_uuid in charts_uuid_list: + for chart in all_charts: + if chart.uuid == chart_uuid: + charts_objects.append(chart) + + return charts_objects diff --git a/ingestion/src/metadata/ingestion/source/dashboard/lightdash/connection.py b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/connection.py new file mode 100644 index 00000000000..db653b1b02b --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/connection.py @@ -0,0 +1,68 @@ +# Copyright 2021 Collate +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Source connection handler +""" + +from typing import Optional + +from metadata.generated.schema.entity.automations.workflow import ( + Workflow as AutomationWorkflow, +) +from metadata.generated.schema.entity.services.connections.dashboard.lightdashConnection import ( + LightdashConnection, +) +from metadata.ingestion.connections.test_connections import ( + SourceConnectionException, + test_connection_steps, +) +from metadata.ingestion.ometa.ometa_api import OpenMetadata +from metadata.ingestion.source.dashboard.lightdash.client import LightdashApiClient +from metadata.utils.logger import ingestion_logger + +logger = ingestion_logger() + + +def get_connection(connection: LightdashConnection) -> LightdashApiClient: + """ + Create connection + """ + try: + logger.debug("creating a new Lightdash connection") + return LightdashApiClient(connection) + except Exception as exc: + msg = "Unknown error connecting with {connection}: {exc}." + raise SourceConnectionException(msg) from exc + + +def test_connection( + metadata: OpenMetadata, + client: LightdashApiClient, + service_connection: LightdashConnection, + automation_workflow: Optional[AutomationWorkflow] = None, +) -> None: + """ + Test connection. This can be executed either as part + of a metadata workflow or during an Automation Workflow + """ + + def custom_executor(): + return client.get_dashboards_list() + + test_fn = {"GetDashboards": custom_executor} + + test_connection_steps( + metadata=metadata, + test_fn=test_fn, + service_type=service_connection.type.value, + automation_workflow=automation_workflow, + ) diff --git a/ingestion/src/metadata/ingestion/source/dashboard/lightdash/metadata.py b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/metadata.py new file mode 100644 index 00000000000..b574c99dd37 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/metadata.py @@ -0,0 +1,169 @@ +# Copyright 2021 Collate +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Lightdash source module""" + +import traceback +from typing import Iterable, List, Optional + +from metadata.generated.schema.api.data.createChart import CreateChartRequest +from metadata.generated.schema.api.data.createDashboard import CreateDashboardRequest +from metadata.generated.schema.api.lineage.addLineage import AddLineageRequest +from metadata.generated.schema.entity.data.chart import Chart +from metadata.generated.schema.entity.services.connections.dashboard.lightdashConnection import ( + LightdashConnection, +) +from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import ( + OpenMetadataConnection, +) +from metadata.generated.schema.metadataIngestion.workflow import ( + Source as WorkflowSource, +) +from metadata.ingestion.api.source import InvalidSourceException +from metadata.ingestion.source.dashboard.dashboard_service import DashboardServiceSource +from metadata.ingestion.source.dashboard.lightdash.models import ( + LightdashChart, + LightdashDashboard, +) +from metadata.utils import fqn +from metadata.utils.filters import filter_by_chart +from metadata.utils.helpers import clean_uri, replace_special_with +from metadata.utils.logger import ingestion_logger + +logger = ingestion_logger() + + +class LightdashSource(DashboardServiceSource): + """ + Lightdash Source Class + """ + + config: WorkflowSource + metadata_config: OpenMetadataConnection + + @classmethod + def create(cls, config_dict, metadata_config: OpenMetadataConnection): + config = WorkflowSource.parse_obj(config_dict) + connection: LightdashConnection = config.serviceConnection.__root__.config + if not isinstance(connection, LightdashConnection): + raise InvalidSourceException( + f"Expected LightdashConnection, but got {connection}" + ) + return cls(config, metadata_config) + + def __init__( + self, + config: WorkflowSource, + metadata_config: OpenMetadataConnection, + ): + super().__init__(config, metadata_config) + self.charts: List[LightdashChart] = [] + + def prepare(self): + self.charts = self.client.get_charts_list() + return super().prepare() + + def get_dashboards_list(self) -> Optional[List[LightdashDashboard]]: + """ + Get List of all dashboards + """ + return self.client.get_dashboards_list() + + def get_dashboard_name(self, dashboard: LightdashDashboard) -> str: + """ + Get Dashboard Name + """ + return dashboard.name + + def get_dashboard_details( + self, dashboard: LightdashDashboard + ) -> LightdashDashboard: + """ + Get Dashboard Details + """ + return dashboard + + def yield_dashboard( + self, dashboard_details: LightdashDashboard + ) -> Iterable[CreateDashboardRequest]: + """ + Method to Get Dashboard Entity + """ + try: + dashboard_url = ( + f"{clean_uri(self.service_connection.hostPort)}/dashboard/{dashboard_details.uuid}-" + f"{replace_special_with(raw=dashboard_details.name.lower(), replacement='-')}" + ) + dashboard_request = CreateDashboardRequest( + name=dashboard_details.uuid, + sourceUrl=dashboard_url, + displayName=dashboard_details.name, + description=dashboard_details.description, + charts=[ + fqn.build( + self.metadata, + entity_type=Chart, + service_name=self.context.dashboard_service.fullyQualifiedName.__root__, + chart_name=chart.name.__root__, + ) + for chart in self.context.charts + ], + service=self.context.dashboard_service.fullyQualifiedName.__root__, + ) + yield dashboard_request + self.register_record(dashboard_request=dashboard_request) + except Exception as exc: # pylint: disable=broad-except + logger.debug(traceback.format_exc()) + logger.warning( + f"Error creating dashboard [{dashboard_details.name}]: {exc}" + ) + + def yield_dashboard_chart( + self, dashboard_details: LightdashChart + ) -> Optional[Iterable[CreateChartRequest]]: + """Get chart method + + Args: + dashboard_details: + Returns: + Iterable[CreateChartRequest] + """ + charts = self.charts + for chart in charts: + try: + chart_url = ( + f"{clean_uri(self.service_connection.hostPort)}/question/{chart.uuid}-" + f"{replace_special_with(raw=chart.name.lower(), replacement='-')}" + ) + if filter_by_chart(self.source_config.chartFilterPattern, chart.name): + self.status.filter(chart.name, "Chart Pattern not allowed") + continue + yield CreateChartRequest( + name=chart.uuid, + displayName=chart.name, + description=chart.description, + sourceUrl=chart_url, + service=self.context.dashboard_service.fullyQualifiedName.__root__, + ) + self.status.scanned(chart.name) + except Exception as exc: # pylint: disable=broad-except + logger.debug(traceback.format_exc()) + logger.warning(f"Error creating chart [{chart}]: {exc}") + + def yield_dashboard_lineage_details( + self, + dashboard_details: LightdashDashboard, + db_service_name: Optional[str], + ) -> Optional[Iterable[AddLineageRequest]]: + """Get lineage method + + Args: + dashboard_details + """ diff --git a/ingestion/src/metadata/ingestion/source/dashboard/lightdash/models.py b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/models.py new file mode 100644 index 00000000000..46d42610990 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/dashboard/lightdash/models.py @@ -0,0 +1,46 @@ +"""Lightdash models""" + +from typing import List, Optional + +from pydantic import BaseModel + + +class LightdashChart(BaseModel): + """ + Lightdash chart model + """ + + name: str + organizationUuid: str + uuid: str + description: Optional[str] + projectUuid: str + spaceUuid: str + pinnedListUuid: Optional[str] + spaceName: str + chartType: Optional[str] + dashboardUuid: Optional[str] + dashboardName: Optional[str] + + +class LightdashDashboard(BaseModel): + organizationUuid: str + name: str + description: Optional[str] + uuid: str + projectUuid: str + updatedAt: str + spaceUuid: str + views: float + firstViewedAt: str + pinnedListUuid: Optional[str] + pinnedListOrder: Optional[float] + charts: Optional[List[LightdashChart]] + + +class LightdashChartList(BaseModel): + charts: Optional[List[LightdashChart]] + + +class LightdashDashboardList(BaseModel): + dashboards: Optional[List[LightdashDashboard]] diff --git a/ingestion/tests/cli_e2e/dashboard/lightdash/redshift.yaml b/ingestion/tests/cli_e2e/dashboard/lightdash/redshift.yaml new file mode 100644 index 00000000000..c65207f96e8 --- /dev/null +++ b/ingestion/tests/cli_e2e/dashboard/lightdash/redshift.yaml @@ -0,0 +1,25 @@ +source: + type: redshift + serviceName: local_redshift + serviceConnection: + config: + hostPort: $E2E_REDSHIFT_HOST_PORT + username: $E2E_REDSHIFT_USERNAME + password: $E2E_REDSHIFT_PASSWORD + database: $E2E_REDSHIFT_DATABASE + type: Redshift + sourceConfig: + config: + schemaFilterPattern: + includes: + - dbt_jaffle +sink: + type: metadata-rest + config: {} +workflowConfig: + loggerLevel: DEBUG + openMetadataServerConfig: + hostPort: http://localhost:8585/api + authProvider: openmetadata + securityConfig: + "jwtToken": "eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlzQm90IjpmYWxzZSwiaXNzIjoib3Blbi1tZXRhZGF0YS5vcmciLCJpYXQiOjE2NjM5Mzg0NjIsImVtYWlsIjoiYWRtaW5Ab3Blbm1ldGFkYXRhLm9yZyJ9.tS8um_5DKu7HgzGBzS1VTA5uUjKWOCU0B_j08WXBiEC0mr0zNREkqVfwFDD-d24HlNEbrqioLsBuFRiwIWKc1m_ZlVQbG7P36RUxhuv2vbSp80FKyNM-Tj93FDzq91jsyNmsQhyNv_fNr3TXfzzSPjHt8Go0FMMP66weoKMgW2PbXlhVKwEuXUHyakLLzewm9UMeQaEiRzhiTMU3UkLXcKbYEJJvfNFcLwSl9W8JCO_l0Yj3ud-qt_nQYEZwqW6u5nfdQllN133iikV4fM5QZsMCnm8Rq1mvLR0y9bmJiD7fwM1tmJ791TUWqmKaTnP49U493VanKpUAfzIiOiIbhg" diff --git a/openmetadata-service/src/main/resources/json/data/testConnections/dashboard/lightdash.json b/openmetadata-service/src/main/resources/json/data/testConnections/dashboard/lightdash.json new file mode 100644 index 00000000000..ffb4b416bf8 --- /dev/null +++ b/openmetadata-service/src/main/resources/json/data/testConnections/dashboard/lightdash.json @@ -0,0 +1,15 @@ +{ + "name": "Lightdash", + "displayName": "Lightdash Test Connection", + "description": "This Test Connection validates the access against the server and basic metadata extraction of dashboards and charts.", + "steps": [ + { + "name": "GetDashboards", + "description": "List all the dashboards available to the user", + "errorMessage": "Failed to fetch orgs, please validate the credentials or validate if user has access to fetch orgs", + "shortCircuit": true, + "mandatory": true + } + ] + } + diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/dashboard/lightdashConnection.json b/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/dashboard/lightdashConnection.json new file mode 100644 index 00000000000..02f776f5bf2 --- /dev/null +++ b/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/dashboard/lightdashConnection.json @@ -0,0 +1,60 @@ +{ + "$id": "https://open-metadata.org/schema/entity/services/connections/dashboard/lightdashConnection.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LightdashConnection", + "description": "Lightdash Connection Config", + "type": "object", + "javaType": "org.openmetadata.schema.services.connections.dashboard.LightdashConnection", + "definitions": { + "lightdashType": { + "description": "Lightdash service type", + "type": "string", + "enum": ["Lightdash"], + "default": "Lightdash" + } + }, + "properties": { + "type": { + "title": "Service Type", + "description": "Service Type", + "$ref": "#/definitions/lightdashType", + "default": "Lightdash" + }, + "hostPort": { + "expose": true, + "title": "Host Port", + "description": "Address for your running Lightdash instance", + "type": "string", + "format": "uri", + "default": "http://localhost:5000" + }, + "apiKey": { + "title": "API Key", + "description": "The personal access token you can generate in the Lightdash app under the user settings", + "type": "string", + "format": "password" + }, + "projectUUID": { + "title": "Project UUID", + "description": "The Project UUID for your Lightdash instance", + "type": "string" + }, + "spaceUUID": { + "title": "Space UUID", + "description": "The Space UUID for your Lightdash instance", + "type": "string" + }, + "proxyAuthentication": { + "title": "Proxy Authentication", + "description": "Use if your Lightdash instance is behind a proxy like (Cloud IAP)", + "type": "string", + "format": "password" + }, + "supportsMetadataExtraction": { + "title": "Supports Metadata Extraction", + "$ref": "../connectionBasicType.json#/definitions/supportsMetadataExtraction" + } + }, + "additionalProperties": false, + "required": ["hostPort", "apiKey","projectUUID","spaceUUID"] +} diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/services/dashboardService.json b/openmetadata-spec/src/main/resources/json/schema/entity/services/dashboardService.json index dd6d85c41c8..f9cf93405ed 100644 --- a/openmetadata-spec/src/main/resources/json/schema/entity/services/dashboardService.json +++ b/openmetadata-spec/src/main/resources/json/schema/entity/services/dashboardService.json @@ -11,7 +11,7 @@ ], "definitions": { "dashboardServiceType": { - "description": "Type of Dashboard service - Superset, Looker, Redash, Tableau, Metabase, PowerBi or Mode", + "description": "Type of Dashboard service - Superset, Looker, Redash, Tableau, Metabase, PowerBi, Mode, or Lightdash", "type": "string", "javaInterfaces": ["org.openmetadata.schema.EnumInterface"], "enum": [ @@ -25,7 +25,8 @@ "CustomDashboard", "DomoDashboard", "QuickSight", - "QlikSense" + "QlikSense", + "Lightdash" ], "javaEnums": [ { @@ -60,6 +61,9 @@ }, { "name": "QlikSense" + }, + { + "name": "Lightdash" } ] }, @@ -106,6 +110,9 @@ }, { "$ref": "./connections/dashboard/qlikSenseConnection.json" + }, + { + "$ref": "./connections/dashboard/lightdashConnection.json" } ] } diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-lightdash.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-lightdash.png new file mode 100644 index 0000000000000000000000000000000000000000..eb499f0371cf75aeb633b4fcc4844123ee32236b GIT binary patch literal 30534 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 Knsek#@c#gqQ&~p< literal 0 HcmV?d00001 diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts index f0eb9f21951..0f2d4e62353 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts @@ -13,6 +13,7 @@ import amazonS3 from 'assets/img/service-icon-amazon-s3.svg'; import gcs from 'assets/img/service-icon-gcs.png'; +import lightDash from 'assets/img/service-icon-lightdash.png'; import msAzure from 'assets/img/service-icon-ms-azure.png'; import { EntityType } from 'enums/entity.enum'; import { PipelineType } from 'generated/api/services/ingestionPipelines/createIngestionPipeline'; @@ -175,6 +176,7 @@ export const MS_AZURE = msAzure; export const SPLINE = spline; export const MONGODB = mongodb; export const QLIK_SENSE = qlikSense; +export const LIGHT_DASH = lightDash; export const COUCHBASE = couchbase; export const PLUS = plus; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts index 683bd311ce8..4e94eba99ca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts @@ -19,6 +19,7 @@ import { } from '../generated/entity/services/dashboardService'; import customDashboardConnection from '../jsons/connectionSchemas/connections/dashboard/customDashboardConnection.json'; import domoDashboardConnection from '../jsons/connectionSchemas/connections/dashboard/domoDashboardConnection.json'; +import lightdashConnection from '../jsons/connectionSchemas/connections/dashboard/lightdashConnection.json'; import lookerConnection from '../jsons/connectionSchemas/connections/dashboard/lookerConnection.json'; import metabaseConnection from '../jsons/connectionSchemas/connections/dashboard/metabaseConnection.json'; import modeConnection from '../jsons/connectionSchemas/connections/dashboard/modeConnection.json'; @@ -90,11 +91,18 @@ export const getDashboardConfig = (type: DashboardServiceType) => { break; } + case DashboardServiceType.QlikSense: { schema = qliksenseConnection; break; } + + case DashboardServiceType.Lightdash: { + schema = lightdashConnection; + + break; + } } return cloneDeep({ schema, uiSchema }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx index 6f4fa2bc33f..095b4669599 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx @@ -51,6 +51,7 @@ import { IMPALA, KAFKA, KINESIS, + LIGHT_DASH, LOGO, LOOKER, MARIADB, @@ -248,12 +249,16 @@ export const serviceTypeLogo = (type: string) => { case DashboardServiceType.DomoDashboard: return DOMO; + case DashboardServiceType.Mode: return MODE; case DashboardServiceType.QlikSense: return QLIK_SENSE; + case DashboardServiceType.Lightdash: + return LIGHT_DASH; + case PipelineServiceType.Airflow: return AIRFLOW;