From 9e19b4f888b28bb0d3ec6d123a17ee7604c3b23b Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Tue, 11 Apr 2023 18:51:20 -0700 Subject: [PATCH 1/8] feat: add verbose map to /dataset/:id endpoint --- superset/datasets/api.py | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/superset/datasets/api.py b/superset/datasets/api.py index 48c429d32d4b1..c86baf796d273 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -63,6 +63,7 @@ get_export_ids_schema, GetOrCreateDatasetSchema, ) +from superset.exceptions import SupersetException from superset.utils.core import parse_boolean_string from superset.views.base import DatasourceFilter, generate_download_headers from superset.views.base_api import ( @@ -248,6 +249,59 @@ class DatasetRestApi(BaseSupersetModelRestApi): list_outer_default_load = True show_outer_default_load = True + @expose("/", methods=["GET"]) + @protect() + @safe + def get(self, pk: int, **kwargs: Any) -> Response: + """Get a dataset + --- + get: + description: >- + Get a dataset + parameters: + - in: path + schema: + type: integer + description: The dataset id + name: pk + responses: + 200: + description: dataset + content: + application/json: + schema: + type: object + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 422: + $ref: '#/components/responses/422' + 500: + $ref: '#/components/responses/500' + """ + data = self.get_headless(pk, **kwargs) + try: + verbose_map = {} + payload = data.json + verbose_map = {"__timestamp": "Time"} + verbose_map.update( + { + o["metric_name"]: o["verbose_name"] or o["metric_name"] + for o in payload["result"]["metrics"] + } + ) + verbose_map.update( + { + o["column_name"]: o["verbose_name"] or o["column_name"] + for o in payload["result"]["columns"] + } + ) + payload["result"]["verbose_map"] = verbose_map + return payload + except SupersetException as ex: + return self.response(ex.status, message=ex.message) + @expose("/", methods=["POST"]) @protect() @safe From 69215b8e4c5e0ba51d27aa0e55910346eefe366d Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Wed, 12 Apr 2023 15:43:26 -0700 Subject: [PATCH 2/8] update schema --- superset/datasets/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/superset/datasets/api.py b/superset/datasets/api.py index c86baf796d273..ad65b29d83f14 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -271,6 +271,12 @@ def get(self, pk: int, **kwargs: Any) -> Response: application/json: schema: type: object + properties: + id: + type: number + result: + items: + $ref: '#/components/schemas/{{self.__class__.__name__}}.get' 400: $ref: '#/components/responses/400' 401: From 8b2595cf168fd700a5a7d2152f7b152d9128e3dc Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Wed, 12 Apr 2023 18:37:44 -0700 Subject: [PATCH 3/8] add dataset response to schema --- superset/datasets/api.py | 2 +- superset/datasets/schemas.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/superset/datasets/api.py b/superset/datasets/api.py index ad65b29d83f14..20ea448887755 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -276,7 +276,7 @@ def get(self, pk: int, **kwargs: Any) -> Response: type: number result: items: - $ref: '#/components/schemas/{{self.__class__.__name__}}.get' + $ref: '#/components/schemas/DatasetResponse' 400: $ref: '#/components/responses/400' 401: diff --git a/superset/datasets/schemas.py b/superset/datasets/schemas.py index 103359a2c3f03..793ad35a91373 100644 --- a/superset/datasets/schemas.py +++ b/superset/datasets/schemas.py @@ -23,6 +23,7 @@ from marshmallow.validate import Length from marshmallow_sqlalchemy import SQLAlchemyAutoSchema +from superset.dashboards.schemas import DatabaseSchema from superset.datasets.models import Dataset get_delete_ids_schema = {"type": "array", "items": {"type": "integer"}} @@ -145,6 +146,34 @@ class DatasetRelatedObjectsResponse(Schema): dashboards = fields.Nested(DatasetRelatedDashboards) +class DatasetResponse(Schema): + id = fields.Int() + cache_timeout = fields.Int() + columns = fields.List(fields.Dict()) + database = fields.Nested(DatabaseSchema) + datasource_type = fields.String() + default_endpoint = fields.String() + description = fields.String() + extra = fields.String() + fetch_values_predicate = fields.String() + filter_select_enabled = fields.Bool() + is_managed_externally = fields.Bool() + is_sqllab_view = fields.Bool() + kind = fields.String() + main_dttm_col = fields.String() + metrics = fields.List(fields.Dict()) + name = fields.String() + offset = fields.Int() + owners = fields.List(fields.Dict()) + schema = fields.String() + select_star = fields.String() + sql = fields.String() + table_name = fields.String() + template_params = fields.String() + url = fields.String() + verbose_map = fields.Dict(fields.String(), fields.String()) + + class ImportV1ColumnSchema(Schema): # pylint: disable=no-self-use, unused-argument @pre_load From 34f955c332c8c33bc4ff08091ac82f886c9626f1 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Thu, 13 Apr 2023 12:06:33 -0700 Subject: [PATCH 4/8] fix pylint --- superset/datasets/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/superset/datasets/api.py b/superset/datasets/api.py index 20ea448887755..e36af0642e36a 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +# pylint: disable=too-many-lines import json import logging from datetime import datetime From 0c28aefc82ed141f0f608b9918ea00f245da6056 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Thu, 13 Apr 2023 13:58:30 -0700 Subject: [PATCH 5/8] fix tests --- superset/datasets/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/superset/datasets/api.py b/superset/datasets/api.py index e36af0642e36a..6d446ac92809b 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -60,6 +60,7 @@ DatasetPostSchema, DatasetPutSchema, DatasetRelatedObjectsResponse, + DatasetResponse, get_delete_ids_schema, get_export_ids_schema, GetOrCreateDatasetSchema, @@ -242,8 +243,9 @@ class DatasetRestApi(BaseSupersetModelRestApi): "get_export_ids_schema": get_export_ids_schema, } openapi_spec_component_schemas = ( - DatasetRelatedObjectsResponse, DatasetDuplicateSchema, + DatasetRelatedObjectsResponse, + DatasetResponse, GetOrCreateDatasetSchema, ) From 035cc8a5124c263a46c47dd269b214cfef9c64fe Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Fri, 21 Apr 2023 12:40:12 -0700 Subject: [PATCH 6/8] format verbose_map --- .../components/Chart/DrillBy/DrillByChart.tsx | 9 ++- .../Chart/DrillBy/DrillByMenuItems.tsx | 12 ++- .../components/Chart/DrillBy/DrillByModal.tsx | 6 +- superset/datasets/api.py | 73 +++---------------- superset/datasets/commands/exceptions.py | 17 +++++ superset/datasets/commands/update.py | 11 +++ superset/datasets/schemas.py | 67 ++++++----------- 7 files changed, 82 insertions(+), 113 deletions(-) diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx index d19dbe9137669..5c88c530aba20 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx @@ -24,13 +24,19 @@ import { SuperChart, css, } from '@superset-ui/core'; +import { Dataset } from '../types'; interface DrillByChartProps { formData: BaseFormData & { [key: string]: any }; result: QueryData[]; + dataset: Dataset; } -export default function DrillByChart({ formData, result }: DrillByChartProps) { +export default function DrillByChart({ + formData, + result, + dataset, +}: DrillByChartProps) { return (
{ - setDataset(result); + const verbose_map = {}; + result.columns.forEach((column: Column) => { + verbose_map[column.column_name] = + column.verbose_name || column.column_name; + }); + result.metrics.forEach((metric: Metric) => { + verbose_map[metric.metric_name] = + metric.verbose_name || metric.metric_name; + }); + setDataset({ ...result, verbose_map }); setColumns( ensureIsArray(result.columns) .filter(column => column.groupby) diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx index 776346705c269..c5328182414d9 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx @@ -228,7 +228,11 @@ export default function DrillByModal({
{!chartDataResult && } {drillByDisplayMode === DrillByType.Chart && chartDataResult && ( - + )} {drillByDisplayMode === DrillByType.Table && chartDataResult && (
", methods=["GET"]) - @protect() - @safe - def get(self, pk: int, **kwargs: Any) -> Response: - """Get a dataset - --- - get: - description: >- - Get a dataset - parameters: - - in: path - schema: - type: integer - description: The dataset id - name: pk - responses: - 200: - description: dataset - content: - application/json: - schema: - type: object - properties: - id: - type: number - result: - items: - $ref: '#/components/schemas/DatasetResponse' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 422: - $ref: '#/components/responses/422' - 500: - $ref: '#/components/responses/500' - """ - data = self.get_headless(pk, **kwargs) - try: - verbose_map = {} - payload = data.json - verbose_map = {"__timestamp": "Time"} - verbose_map.update( - { - o["metric_name"]: o["verbose_name"] or o["metric_name"] - for o in payload["result"]["metrics"] - } - ) - verbose_map.update( - { - o["column_name"]: o["verbose_name"] or o["column_name"] - for o in payload["result"]["columns"] - } - ) - payload["result"]["verbose_map"] = verbose_map - return payload - except SupersetException as ex: - return self.response(ex.status, message=ex.message) - @expose("/", methods=["POST"]) @protect() @safe diff --git a/superset/datasets/commands/exceptions.py b/superset/datasets/commands/exceptions.py index 91af2fdde4e9c..e06e92802f04c 100644 --- a/superset/datasets/commands/exceptions.py +++ b/superset/datasets/commands/exceptions.py @@ -61,6 +61,23 @@ def __init__(self, table_name: str) -> None: ) +class DatasetEndpointUnsafeValidationError(ValidationError): + """ + Marshmallow validation error for unsafe dataset default endpoint + """ + + def __init__(self) -> None: + super().__init__( + [ + _( + "The submitted URL is not considered safe," + " only use URLs with the same domain as Superset." + ) + ], + field_name="default_endpoint", + ) + + class DatasetColumnNotFoundValidationError(ValidationError): """ Marshmallow validation error when dataset column for update does not exist diff --git a/superset/datasets/commands/update.py b/superset/datasets/commands/update.py index 483a98e76c3b8..a2e483ba93ddb 100644 --- a/superset/datasets/commands/update.py +++ b/superset/datasets/commands/update.py @@ -18,6 +18,7 @@ from collections import Counter from typing import Any, Dict, List, Optional +from flask import current_app from flask_appbuilder.models.sqla import Model from marshmallow import ValidationError @@ -30,6 +31,7 @@ DatasetColumnNotFoundValidationError, DatasetColumnsDuplicateValidationError, DatasetColumnsExistsValidationError, + DatasetEndpointUnsafeValidationError, DatasetExistsValidationError, DatasetForbiddenError, DatasetInvalidError, @@ -41,6 +43,7 @@ ) from superset.datasets.dao import DatasetDAO from superset.exceptions import SupersetSecurityException +from superset.utils.urls import is_safe_url logger = logging.getLogger(__name__) @@ -101,6 +104,14 @@ def validate(self) -> None: self._properties["owners"] = owners except ValidationError as ex: exceptions.append(ex) + # Validate default URL safety + default_endpoint = self._properties.get("default_endpoint") + if ( + default_endpoint + and not is_safe_url(default_endpoint) + and current_app.config["PREVENT_UNSAFE_DEFAULT_URLS_ON_DATASET"] + ): + exceptions.append(DatasetEndpointUnsafeValidationError()) # Validate columns columns = self._properties.get("columns") diff --git a/superset/datasets/schemas.py b/superset/datasets/schemas.py index 793ad35a91373..eaf5963fdf32e 100644 --- a/superset/datasets/schemas.py +++ b/superset/datasets/schemas.py @@ -23,7 +23,6 @@ from marshmallow.validate import Length from marshmallow_sqlalchemy import SQLAlchemyAutoSchema -from superset.dashboards.schemas import DatabaseSchema from superset.datasets.models import Dataset get_delete_ids_schema = {"type": "array", "items": {"type": "integer"}} @@ -50,14 +49,14 @@ class DatasetColumnsPutSchema(Schema): column_name = fields.String(required=True, validate=Length(1, 255)) type = fields.String(allow_none=True) advanced_data_type = fields.String(allow_none=True, validate=Length(1, 255)) - verbose_name = fields.String(allow_none=True, Length=(1, 1024)) + verbose_name = fields.String(allow_none=True, metadata={Length: (1, 1024)}) description = fields.String(allow_none=True) expression = fields.String(allow_none=True) extra = fields.String(allow_none=True) filterable = fields.Boolean() groupby = fields.Boolean() - is_active = fields.Boolean() - is_dttm = fields.Boolean(default=False) + is_active = fields.Boolean(allow_none=True) + is_dttm = fields.Boolean(dump_default=False) python_date_format = fields.String( allow_none=True, validate=[Length(1, 255), validate_python_date_format] ) @@ -72,7 +71,7 @@ class DatasetMetricsPutSchema(Schema): metric_name = fields.String(required=True, validate=Length(1, 255)) metric_type = fields.String(allow_none=True, validate=Length(1, 32)) d3format = fields.String(allow_none=True, validate=Length(1, 128)) - verbose_name = fields.String(allow_none=True, Length=(1, 1024)) + verbose_name = fields.String(allow_none=True, metadata={Length: (1, 1024)}) warning_text = fields.String(allow_none=True) uuid = fields.UUID(allow_none=True) @@ -83,7 +82,7 @@ class DatasetPostSchema(Schema): table_name = fields.String(required=True, allow_none=False, validate=Length(1, 250)) sql = fields.String(allow_none=True) owners = fields.List(fields.Integer()) - is_managed_externally = fields.Boolean(allow_none=True, default=False) + is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) @@ -105,7 +104,7 @@ class DatasetPutSchema(Schema): columns = fields.List(fields.Nested(DatasetColumnsPutSchema)) metrics = fields.List(fields.Nested(DatasetMetricsPutSchema)) extra = fields.String(allow_none=True) - is_managed_externally = fields.Boolean(allow_none=True, default=False) + is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) @@ -128,16 +127,18 @@ class DatasetRelatedDashboard(Schema): class DatasetRelatedCharts(Schema): - count = fields.Integer(description="Chart count") + count = fields.Integer(metadata={"description": "Chart count"}) result = fields.List( - fields.Nested(DatasetRelatedChart), description="A list of dashboards" + fields.Nested(DatasetRelatedChart), + metadata={"description": "A list of dashboards"}, ) class DatasetRelatedDashboards(Schema): - count = fields.Integer(description="Dashboard count") + count = fields.Integer(metadata={"description": "Dashboard count"}) result = fields.List( - fields.Nested(DatasetRelatedDashboard), description="A list of dashboards" + fields.Nested(DatasetRelatedDashboard), + metadata={"description": "A list of dashboards"}, ) @@ -146,34 +147,6 @@ class DatasetRelatedObjectsResponse(Schema): dashboards = fields.Nested(DatasetRelatedDashboards) -class DatasetResponse(Schema): - id = fields.Int() - cache_timeout = fields.Int() - columns = fields.List(fields.Dict()) - database = fields.Nested(DatabaseSchema) - datasource_type = fields.String() - default_endpoint = fields.String() - description = fields.String() - extra = fields.String() - fetch_values_predicate = fields.String() - filter_select_enabled = fields.Bool() - is_managed_externally = fields.Bool() - is_sqllab_view = fields.Bool() - kind = fields.String() - main_dttm_col = fields.String() - metrics = fields.List(fields.Dict()) - name = fields.String() - offset = fields.Int() - owners = fields.List(fields.Dict()) - schema = fields.String() - select_star = fields.String() - sql = fields.String() - table_name = fields.String() - template_params = fields.String() - url = fields.String() - verbose_map = fields.Dict(fields.String(), fields.String()) - - class ImportV1ColumnSchema(Schema): # pylint: disable=no-self-use, unused-argument @pre_load @@ -189,8 +162,8 @@ def fix_extra(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: column_name = fields.String(required=True) extra = fields.Dict(allow_none=True) verbose_name = fields.String(allow_none=True) - is_dttm = fields.Boolean(default=False, allow_none=True) - is_active = fields.Boolean(default=True, allow_none=True) + is_dttm = fields.Boolean(dump_default=False, allow_none=True) + is_active = fields.Boolean(dump_default=True, allow_none=True) type = fields.String(allow_none=True) advanced_data_type = fields.String(allow_none=True) groupby = fields.Boolean() @@ -253,19 +226,21 @@ def fix_extra(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: version = fields.String(required=True) database_uuid = fields.UUID(required=True) data = fields.URL() - is_managed_externally = fields.Boolean(allow_none=True, default=False) + is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) external_url = fields.String(allow_none=True) class GetOrCreateDatasetSchema(Schema): - table_name = fields.String(required=True, description="Name of table") + table_name = fields.String(required=True, metadata={"description": "Name of table"}) database_id = fields.Integer( - required=True, description="ID of database table belongs to" + required=True, metadata={"description": "ID of database table belongs to"} ) schema = fields.String( - description="The schema the table belongs to", allow_none=True + metadata={"description": "The schema the table belongs to"}, allow_none=True + ) + template_params = fields.String( + metadata={"description": "Template params for the table"} ) - template_params = fields.String(description="Template params for the table") class DatasetSchema(SQLAlchemyAutoSchema): From 8e9d26548767c18fe5263bf4c07930d61e01c513 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Fri, 21 Apr 2023 13:09:58 -0700 Subject: [PATCH 7/8] fix lint and test --- .../Chart/DrillBy/DrillByChart.test.tsx | 19 +++++++++++++++++++ .../Chart/DrillBy/DrillByMenuItems.tsx | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx index 497e2bb065082..a7d314a20aeed 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx @@ -23,6 +23,24 @@ import { noOp } from 'src/utils/common'; import DrillByChart from './DrillByChart'; const chart = chartQueries[sliceId]; +const dataset = { + changed_on_humanized: '01-01-2001', + created_on_humanized: '01-01-2001', + description: 'desc', + table_name: 'my_dataset', + owners: [ + { + first_name: 'Sarah', + last_name: 'Connor', + }, + ], + columns: [ + { + column_name: 'gender', + }, + { column_name: 'name' }, + ], +}; const setup = (overrides: Record = {}, result?: any) => render( @@ -31,6 +49,7 @@ const setup = (overrides: Record = {}, result?: any) => onContextMenu={noOp} inContextMenu={false} result={result} + dataset={dataset} />, { useRedux: true, diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx index a76aa8a898ae4..aa052a460ba8e 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx @@ -125,11 +125,11 @@ export const DrillByMenuItems = ({ }) .then(({ json: { result } }) => { const verbose_map = {}; - result.columns.forEach((column: Column) => { + ensureIsArray(result.columns).forEach((column: Column) => { verbose_map[column.column_name] = column.verbose_name || column.column_name; }); - result.metrics.forEach((metric: Metric) => { + ensureIsArray(result.metrics).forEach((metric: Metric) => { verbose_map[metric.metric_name] = metric.verbose_name || metric.metric_name; }); From 0afc61f21da7a7c83d8d586c571e141f27ced749 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Mon, 24 Apr 2023 21:42:36 -0700 Subject: [PATCH 8/8] create useVerboseMap in hooks --- .../Chart/DrillBy/DrillByMenuItems.tsx | 16 +++------- .../src/components/Chart/types.ts | 4 ++- .../src/hooks/apiResources/datasets.ts | 32 +++++++++++++++++++ 3 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 superset-frontend/src/hooks/apiResources/datasets.ts diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx index aa052a460ba8e..a1996027971e5 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx @@ -31,7 +31,6 @@ import { Behavior, Column, ContextMenuFilters, - Metric, css, ensureIsArray, getChartMetadataRegistry, @@ -45,6 +44,7 @@ import { cachedSupersetGet, supersetGetCache, } from 'src/utils/cachedSupersetGet'; +import { useVerboseMap } from 'src/hooks/apiResources/datasets'; import { MenuItemTooltip } from '../DisabledMenuItemTooltip'; import DrillByModal from './DrillByModal'; import { getSubmenuYOffset } from '../utils'; @@ -116,6 +116,7 @@ export const DrillByMenuItems = ({ ?.behaviors.find(behavior => behavior === Behavior.DRILL_BY), [formData.viz_type], ); + const verboseMap = useVerboseMap(dataset); useEffect(() => { if (handlesDimensionContextMenu && hasDrillBy) { @@ -124,16 +125,7 @@ export const DrillByMenuItems = ({ endpoint: `/api/v1/dataset/${datasetId}`, }) .then(({ json: { result } }) => { - const verbose_map = {}; - ensureIsArray(result.columns).forEach((column: Column) => { - verbose_map[column.column_name] = - column.verbose_name || column.column_name; - }); - ensureIsArray(result.metrics).forEach((metric: Metric) => { - verbose_map[metric.metric_name] = - metric.verbose_name || metric.metric_name; - }); - setDataset({ ...result, verbose_map }); + setDataset(result); setColumns( ensureIsArray(result.columns) .filter(column => column.groupby) @@ -280,7 +272,7 @@ export const DrillByMenuItems = ({ drillByConfig={drillByConfig} formData={formData} onHideModal={closeModal} - dataset={dataset!} + dataset={{ ...dataset!, verbose_map: verboseMap }} /> )} diff --git a/superset-frontend/src/components/Chart/types.ts b/superset-frontend/src/components/Chart/types.ts index 2b3eb1cea0cc6..bafedd2df87cd 100644 --- a/superset-frontend/src/components/Chart/types.ts +++ b/superset-frontend/src/components/Chart/types.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Column } from '@superset-ui/core'; +import { Column, Metric } from '@superset-ui/core'; export enum DrillByType { Chart, @@ -41,4 +41,6 @@ export type Dataset = { last_name: string; }[]; columns?: Column[]; + metrics?: Metric[]; + verbose_map?: Record; }; diff --git a/superset-frontend/src/hooks/apiResources/datasets.ts b/superset-frontend/src/hooks/apiResources/datasets.ts new file mode 100644 index 0000000000000..cd44b2426e00e --- /dev/null +++ b/superset-frontend/src/hooks/apiResources/datasets.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-underscore-dangle */ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +import { Column, Metric, ensureIsArray } from '@superset-ui/core'; +import { Dataset } from 'src/components/Chart/types'; + +export const useVerboseMap = (dataset?: Dataset) => { + const verbose_map = {}; + ensureIsArray(dataset?.columns).forEach((column: Column) => { + verbose_map[column.column_name] = column.verbose_name || column.column_name; + }); + ensureIsArray(dataset?.metrics).forEach((metric: Metric) => { + verbose_map[metric.metric_name] = metric.verbose_name || metric.metric_name; + }); + return verbose_map; +};