feat: add database and schema names to dataset option (#25569)

Co-authored-by: Sonia <sonia.gautam@agoda.com>
This commit is contained in:
Sonia Gautam 2023-10-20 01:05:58 +07:00 committed by GitHub
parent dfff3c1cba
commit 39ad3226c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 85 deletions

View File

@ -16,16 +16,19 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo, ReactNode } from 'react';
import rison from 'rison'; import rison from 'rison';
import { t } from '@superset-ui/core'; import { t, JsonResponse } from '@superset-ui/core';
import { AsyncSelect } from 'src/components'; import { AsyncSelect } from 'src/components';
import { import {
ClientErrorObject, ClientErrorObject,
getClientErrorObject, getClientErrorObject,
} from 'src/utils/getClientErrorObject'; } from 'src/utils/getClientErrorObject';
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet'; import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
import { datasetToSelectOption } from './utils'; import {
Dataset,
DatasetSelectLabel,
} from 'src/features/datasets/DatasetSelectLabel';
interface DatasetSelectProps { interface DatasetSelectProps {
onChange: (value: { label: string; value: number }) => void; onChange: (value: { label: string; value: number }) => void;
@ -49,24 +52,29 @@ const DatasetSelect = ({ onChange, value }: DatasetSelectProps) => {
page: number, page: number,
pageSize: number, pageSize: number,
) => { ) => {
const searchColumn = 'table_name';
const query = rison.encode({ 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,
page_size: pageSize, page_size: pageSize,
order_column: searchColumn, order_column: 'table_name',
order_direction: 'asc', order_direction: 'asc',
}); });
return cachedSupersetGet({ return cachedSupersetGet({
endpoint: `/api/v1/dataset/?q=${query}`, endpoint: `/api/v1/dataset/?q=${query}`,
}) })
.then(response => { .then((response: JsonResponse) => {
const data: { const list: {
customLabel: ReactNode;
label: string; label: string;
value: string | number; 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 { return {
data, data: list,
totalCount: response.json.count, totalCount: response.json.count,
}; };
}) })
@ -83,6 +91,7 @@ const DatasetSelect = ({ onChange, value }: DatasetSelectProps) => {
options={loadDatasetOptions} options={loadDatasetOptions}
onChange={onChange} onChange={onChange}
notFoundContent={t('No compatible datasets found')} notFoundContent={t('No compatible datasets found')}
placeholder={t('Select a dataset')}
/> />
); );
}; };

View File

@ -82,6 +82,7 @@ import {
getFormData, getFormData,
mergeExtraFormData, mergeExtraFormData,
} from 'src/dashboard/components/nativeFilters/utils'; } from 'src/dashboard/components/nativeFilters/utils';
import { DatasetSelectLabel } from 'src/features/datasets/DatasetSelectLabel';
import { import {
ALLOW_DEPENDENCIES as TYPES_SUPPORT_DEPENDENCIES, ALLOW_DEPENDENCIES as TYPES_SUPPORT_DEPENDENCIES,
getFiltersConfigModalTestId, getFiltersConfigModalTestId,
@ -883,7 +884,15 @@ const FiltersConfigForm = (
initialValue={ initialValue={
datasetDetails 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, value: datasetDetails.id,
} }
: undefined : undefined

View File

@ -66,18 +66,6 @@ export const getControlItems = (
[], [],
) as CustomControlItem[]) ?? []; ) 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 // 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 // We return true if column_types is undefined or empty as a precaution against backend failing to return column_types
export const hasTemporalColumns = ( export const hasTemporalColumns = (

View File

@ -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) => (
<Tooltip
mouseEnterDelay={0.2}
placement="right"
title={
<TooltipContent>
<div className="tooltip-header">
{item.table_name && isValidValue(item.table_name)
? item.table_name
: t('Not defined')}
</div>
<div className="tooltip-description">
<div>
{t('Database')}: {item.database.database_name}
</div>
<div>
{t('Schema')}:{' '}
{item.schema && isValidValue(item.schema)
? item.schema
: t('Not defined')}
</div>
</div>
</TooltipContent>
}
>
<StyledLabelContainer>
<StyledLabel>
{item.table_name && isValidValue(item.table_name)
? item.table_name
: item.database.database_name}
</StyledLabel>
<StyledDetailWrapper>
<StyledLabelDetail>{item.database.database_name}</StyledLabelDetail>
{item.schema && isValidValue(item.schema) && (
<StyledLabelDetail>&nbsp;- {item.schema}</StyledLabelDetail>
)}
</StyledDetailWrapper>
</StyledLabelContainer>
</Tooltip>
);

View File

@ -33,7 +33,6 @@ import { URL_PARAMS } from 'src/constants';
import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import { AsyncSelect, Steps } from 'src/components'; import { AsyncSelect, Steps } from 'src/components';
import { Tooltip } from 'src/components/Tooltip';
import withToasts from 'src/components/MessageToasts/withToasts'; import withToasts from 'src/components/MessageToasts/withToasts';
import VizTypeGallery, { import VizTypeGallery, {
@ -42,13 +41,10 @@ import VizTypeGallery, {
import { findPermission } from 'src/utils/findPermission'; import { findPermission } from 'src/utils/findPermission';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import getBootstrapData from 'src/utils/getBootstrapData'; import getBootstrapData from 'src/utils/getBootstrapData';
import {
type Dataset = { Dataset,
id: number; DatasetSelectLabel,
table_name: string; } from 'src/features/datasets/DatasetSelectLabel';
description: string;
datasource_type: string;
};
export interface ChartCreationProps extends RouteComponentProps { export interface ChartCreationProps extends RouteComponentProps {
user: UserWithPermissionsAndRoles; user: UserWithPermissionsAndRoles;
@ -169,40 +165,10 @@ const StyledContainer = styled.div`
&&&& .ant-select-selection-placeholder { &&&& .ant-select-selection-placeholder {
padding-left: ${theme.gridUnit * 3}px; padding-left: ${theme.gridUnit * 3}px;
} }
`}
`;
const TooltipContent = styled.div<{ hasDescription: boolean }>` &&&& .ant-select-selection-item {
${({ theme, hasDescription }) => ` padding-left: ${theme.gridUnit * 3}px;
.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
};
} }
.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.changeDatasource = this.changeDatasource.bind(this);
this.changeVizType = this.changeVizType.bind(this); this.changeVizType = this.changeVizType.bind(this);
this.gotoSlice = this.gotoSlice.bind(this); this.gotoSlice = this.gotoSlice.bind(this);
this.newLabel = this.newLabel.bind(this);
this.loadDatasources = this.loadDatasources.bind(this); this.loadDatasources = this.loadDatasources.bind(this);
this.onVizTypeDoubleClick = this.onVizTypeDoubleClick.bind(this); this.onVizTypeDoubleClick = this.onVizTypeDoubleClick.bind(this);
} }
@ -293,28 +258,15 @@ export class ChartCreation extends React.PureComponent<
} }
} }
newLabel(item: Dataset) {
return (
<Tooltip
mouseEnterDelay={1}
placement="right"
title={
<TooltipContent hasDescription={!!item.description}>
<div className="tooltip-header">{item.table_name}</div>
{item.description && (
<div className="tooltip-description">{item.description}</div>
)}
</TooltipContent>
}
>
<StyledLabel>{item.table_name}</StyledLabel>
</Tooltip>
);
}
loadDatasources(search: string, page: number, pageSize: number) { loadDatasources(search: string, page: number, pageSize: number) {
const query = rison.encode({ 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 }], filters: [{ col: 'table_name', opr: 'ct', value: search }],
page, page,
page_size: pageSize, page_size: pageSize,
@ -332,7 +284,7 @@ export class ChartCreation extends React.PureComponent<
}[] = response.json.result.map((item: Dataset) => ({ }[] = response.json.result.map((item: Dataset) => ({
id: item.id, id: item.id,
value: `${item.id}__${item.datasource_type}`, value: `${item.id}__${item.datasource_type}`,
customLabel: this.newLabel(item), customLabel: DatasetSelectLabel(item),
label: item.table_name, label: item.table_name,
})); }));
return { return {