From 39ad3226c7bb7a3c1ad731d3e5063e70a5adba08 Mon Sep 17 00:00:00 2001 From: Sonia Gautam <96282005+soniagtm@users.noreply.github.com> Date: Fri, 20 Oct 2023 01:05:58 +0700 Subject: [PATCH] feat: add database and schema names to dataset option (#25569) Co-authored-by: Sonia --- .../FiltersConfigForm/DatasetSelect.tsx | 29 ++-- .../FiltersConfigForm/FiltersConfigForm.tsx | 11 +- .../FiltersConfigForm/utils.ts | 12 -- .../datasets/DatasetSelectLabel/index.tsx | 136 ++++++++++++++++++ .../src/pages/ChartCreation/index.tsx | 76 ++-------- 5 files changed, 179 insertions(+), 85 deletions(-) create mode 100644 superset-frontend/src/features/datasets/DatasetSelectLabel/index.tsx diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx index e5e62b008..532547e58 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx @@ -16,16 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, ReactNode } from 'react'; import rison from 'rison'; -import { t } from '@superset-ui/core'; +import { t, JsonResponse } from '@superset-ui/core'; import { AsyncSelect } from 'src/components'; import { ClientErrorObject, getClientErrorObject, } from 'src/utils/getClientErrorObject'; import { cachedSupersetGet } from 'src/utils/cachedSupersetGet'; -import { datasetToSelectOption } from './utils'; +import { + Dataset, + DatasetSelectLabel, +} from 'src/features/datasets/DatasetSelectLabel'; interface DatasetSelectProps { onChange: (value: { label: string; value: number }) => void; @@ -49,24 +52,29 @@ const DatasetSelect = ({ onChange, value }: DatasetSelectProps) => { page: number, pageSize: number, ) => { - const searchColumn = 'table_name'; const query = rison.encode({ - filters: [{ col: searchColumn, opr: 'ct', value: search }], + columns: ['id', 'table_name', 'database.database_name', 'schema'], + filters: [{ col: 'table_name', opr: 'ct', value: search }], page, page_size: pageSize, - order_column: searchColumn, + order_column: 'table_name', order_direction: 'asc', }); return cachedSupersetGet({ endpoint: `/api/v1/dataset/?q=${query}`, }) - .then(response => { - const data: { + .then((response: JsonResponse) => { + const list: { + customLabel: ReactNode; label: string; value: string | number; - }[] = response.json.result.map(datasetToSelectOption); + }[] = response.json.result.map((item: Dataset) => ({ + customLabel: DatasetSelectLabel(item), + label: item.table_name, + value: item.id, + })); return { - data, + data: list, totalCount: response.json.count, }; }) @@ -83,6 +91,7 @@ const DatasetSelect = ({ onChange, value }: DatasetSelectProps) => { options={loadDatasetOptions} onChange={onChange} notFoundContent={t('No compatible datasets found')} + placeholder={t('Select a dataset')} /> ); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx index 94b336af9..e9c0f52cc 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx @@ -82,6 +82,7 @@ import { getFormData, mergeExtraFormData, } from 'src/dashboard/components/nativeFilters/utils'; +import { DatasetSelectLabel } from 'src/features/datasets/DatasetSelectLabel'; import { ALLOW_DEPENDENCIES as TYPES_SUPPORT_DEPENDENCIES, getFiltersConfigModalTestId, @@ -883,7 +884,15 @@ const FiltersConfigForm = ( initialValue={ datasetDetails ? { - label: datasetDetails.table_name, + label: DatasetSelectLabel({ + id: datasetDetails.id, + table_name: datasetDetails.table_name, + schema: datasetDetails.schema, + database: { + database_name: + datasetDetails.database.database_name, + }, + }), value: datasetDetails.id, } : undefined diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts index 92185039d..b7aa2cab3 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts @@ -66,18 +66,6 @@ export const getControlItems = ( [], ) as CustomControlItem[]) ?? []; -type DatasetSelectValue = { - value: number; - label: string; -}; - -export const datasetToSelectOption = ( - item: Dataset & { table_name: string }, -): DatasetSelectValue => ({ - value: item.id, - label: item.table_name, -}); - // TODO: add column_types field to Dataset // We return true if column_types is undefined or empty as a precaution against backend failing to return column_types export const hasTemporalColumns = ( diff --git a/superset-frontend/src/features/datasets/DatasetSelectLabel/index.tsx b/superset-frontend/src/features/datasets/DatasetSelectLabel/index.tsx new file mode 100644 index 000000000..f31c78910 --- /dev/null +++ b/superset-frontend/src/features/datasets/DatasetSelectLabel/index.tsx @@ -0,0 +1,136 @@ +/** + * 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 React from 'react'; +import { Tooltip } from 'src/components/Tooltip'; +import { styled, t } from '@superset-ui/core'; + +type Database = { + database_name: string; +}; + +export type Dataset = { + id: number; + table_name: string; + datasource_type?: string; + schema: string; + database: Database; +}; + +const TooltipContent = styled.div` + ${({ theme }) => ` + .tooltip-header { + font-size: ${theme.typography.sizes.m}px; + font-weight: ${theme.typography.weights.bold}; + } + + .tooltip-description { + margin-top: ${theme.gridUnit * 2}px; + display: -webkit-box; + -webkit-line-clamp: 20; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + `} +`; + +const StyledLabelContainer = styled.div` + ${({ theme }) => ` + left: ${theme.gridUnit * 3}px; + right: ${theme.gridUnit * 3}px; + overflow: hidden; + text-overflow: ellipsis; + display: block; + `} +`; + +const StyledLabel = styled.span` + ${({ theme }) => ` + left: ${theme.gridUnit * 3}px; + right: ${theme.gridUnit * 3}px; + overflow: hidden; + text-overflow: ellipsis; + display: block; + `} +`; + +const StyledDetailWrapper = styled.div` + display: grid; + grid-template-columns: auto auto; + justify-content: start; + width: 100%; +`; + +const StyledLabelDetail = styled.span` + ${({ + theme: { + typography: { sizes, weights }, + }, + }) => ` + overflow: hidden; + text-overflow: ellipsis; + font-size: ${sizes.s}px; + font-weight: ${weights.light}; + line-height: 1.6; + `} +`; + +const isValidValue = (value: string): boolean => + !['null', 'none'].includes(value.toLowerCase()) && value.trim() !== ''; + +export const DatasetSelectLabel = (item: Dataset) => ( + +
+ {item.table_name && isValidValue(item.table_name) + ? item.table_name + : t('Not defined')} +
+
+
+ {t('Database')}: {item.database.database_name} +
+
+ {t('Schema')}:{' '} + {item.schema && isValidValue(item.schema) + ? item.schema + : t('Not defined')} +
+
+ + } + > + + + {item.table_name && isValidValue(item.table_name) + ? item.table_name + : item.database.database_name} + + + {item.database.database_name} + {item.schema && isValidValue(item.schema) && ( +  - {item.schema} + )} + + +
+); diff --git a/superset-frontend/src/pages/ChartCreation/index.tsx b/superset-frontend/src/pages/ChartCreation/index.tsx index c09172e99..7a1ff43dc 100644 --- a/superset-frontend/src/pages/ChartCreation/index.tsx +++ b/superset-frontend/src/pages/ChartCreation/index.tsx @@ -33,7 +33,6 @@ import { URL_PARAMS } from 'src/constants'; import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; import Button from 'src/components/Button'; import { AsyncSelect, Steps } from 'src/components'; -import { Tooltip } from 'src/components/Tooltip'; import withToasts from 'src/components/MessageToasts/withToasts'; import VizTypeGallery, { @@ -42,13 +41,10 @@ import VizTypeGallery, { import { findPermission } from 'src/utils/findPermission'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; import getBootstrapData from 'src/utils/getBootstrapData'; - -type Dataset = { - id: number; - table_name: string; - description: string; - datasource_type: string; -}; +import { + Dataset, + DatasetSelectLabel, +} from 'src/features/datasets/DatasetSelectLabel'; export interface ChartCreationProps extends RouteComponentProps { user: UserWithPermissionsAndRoles; @@ -169,40 +165,10 @@ const StyledContainer = styled.div` &&&& .ant-select-selection-placeholder { padding-left: ${theme.gridUnit * 3}px; } - `} -`; -const TooltipContent = styled.div<{ hasDescription: boolean }>` - ${({ theme, hasDescription }) => ` - .tooltip-header { - font-size: ${ - hasDescription ? theme.typography.sizes.l : theme.typography.sizes.s - }px; - font-weight: ${ - hasDescription - ? theme.typography.weights.bold - : theme.typography.weights.normal - }; + &&&& .ant-select-selection-item { + padding-left: ${theme.gridUnit * 3}px; } - - .tooltip-description { - margin-top: ${theme.gridUnit * 2}px; - display: -webkit-box; - -webkit-line-clamp: 20; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - } - `} -`; - -const StyledLabel = styled.span` - ${({ theme }) => ` - position: absolute; - left: ${theme.gridUnit * 3}px; - right: ${theme.gridUnit * 3}px; - overflow: hidden; - text-overflow: ellipsis; `} `; @@ -242,7 +208,6 @@ export class ChartCreation extends React.PureComponent< this.changeDatasource = this.changeDatasource.bind(this); this.changeVizType = this.changeVizType.bind(this); this.gotoSlice = this.gotoSlice.bind(this); - this.newLabel = this.newLabel.bind(this); this.loadDatasources = this.loadDatasources.bind(this); this.onVizTypeDoubleClick = this.onVizTypeDoubleClick.bind(this); } @@ -293,28 +258,15 @@ export class ChartCreation extends React.PureComponent< } } - newLabel(item: Dataset) { - return ( - -
{item.table_name}
- {item.description && ( -
{item.description}
- )} - - } - > - {item.table_name} -
- ); - } - loadDatasources(search: string, page: number, pageSize: number) { const query = rison.encode({ - columns: ['id', 'table_name', 'description', 'datasource_type'], + columns: [ + 'id', + 'table_name', + 'datasource_type', + 'database.database_name', + 'schema', + ], filters: [{ col: 'table_name', opr: 'ct', value: search }], page, page_size: pageSize, @@ -332,7 +284,7 @@ export class ChartCreation extends React.PureComponent< }[] = response.json.result.map((item: Dataset) => ({ id: item.id, value: `${item.id}__${item.datasource_type}`, - customLabel: this.newLabel(item), + customLabel: DatasetSelectLabel(item), label: item.table_name, })); return {