feat: add database and schema names to dataset option (#25569)
Co-authored-by: Sonia <sonia.gautam@agoda.com>
This commit is contained in:
parent
dfff3c1cba
commit
39ad3226c7
|
|
@ -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')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = (
|
||||||
|
|
|
||||||
|
|
@ -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> - {item.schema}</StyledLabelDetail>
|
||||||
|
)}
|
||||||
|
</StyledDetailWrapper>
|
||||||
|
</StyledLabelContainer>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue