mirror of https://github.com/apache/superset.git
chore: Shows the dataset description in the gallery dropdown (#16200)
* chore: Shows the dataset description in the gallery dropdown * chore: Adjusts the tooltip positioning, fixes the search and removes unnecessary bootstrap data
This commit is contained in:
parent
c09f6ed15b
commit
720e5b111a
|
@ -28,11 +28,9 @@ import VizTypeGallery from 'src/explore/components/controls/VizTypeControl/VizTy
|
|||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { act } from 'spec/helpers/testing-library';
|
||||
|
||||
const defaultProps = {
|
||||
datasources: [
|
||||
{ label: 'my first table', value: '1__table' },
|
||||
{ label: 'another great table', value: '2__table' },
|
||||
],
|
||||
const datasource = {
|
||||
value: '1',
|
||||
label: 'table',
|
||||
};
|
||||
|
||||
describe('AddSliceContainer', () => {
|
||||
|
@ -43,7 +41,7 @@ describe('AddSliceContainer', () => {
|
|||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(<AddSliceContainer {...defaultProps} />) as ReactWrapper<
|
||||
wrapper = mount(<AddSliceContainer />) as ReactWrapper<
|
||||
AddSliceContainerProps,
|
||||
AddSliceContainerState,
|
||||
AddSliceContainer
|
||||
|
@ -68,11 +66,8 @@ describe('AddSliceContainer', () => {
|
|||
});
|
||||
|
||||
it('renders an enabled button if datasource and viz type is selected', () => {
|
||||
const datasourceValue = defaultProps.datasources[0].value;
|
||||
wrapper.setState({
|
||||
datasourceValue,
|
||||
datasourceId: datasourceValue.split('__')[0],
|
||||
datasourceType: datasourceValue.split('__')[1],
|
||||
datasource,
|
||||
visType: 'table',
|
||||
});
|
||||
expect(
|
||||
|
@ -81,15 +76,12 @@ describe('AddSliceContainer', () => {
|
|||
});
|
||||
|
||||
it('formats explore url', () => {
|
||||
const datasourceValue = defaultProps.datasources[0].value;
|
||||
wrapper.setState({
|
||||
datasourceValue,
|
||||
datasourceId: datasourceValue.split('__')[0],
|
||||
datasourceType: datasourceValue.split('__')[1],
|
||||
datasource,
|
||||
visType: 'table',
|
||||
});
|
||||
const formattedUrl =
|
||||
'/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221__table%22%7D';
|
||||
'/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221%22%7D';
|
||||
expect(wrapper.instance().exploreUrl()).toBe(formattedUrl);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,28 +17,34 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import rison from 'rison';
|
||||
import Button from 'src/components/Button';
|
||||
import { Select } from 'src/components';
|
||||
import { css, styled, t } from '@superset-ui/core';
|
||||
import {
|
||||
css,
|
||||
styled,
|
||||
t,
|
||||
SupersetClient,
|
||||
JsonResponse,
|
||||
} from '@superset-ui/core';
|
||||
import { FormLabel } from 'src/components/Form';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
|
||||
import VizTypeGallery, {
|
||||
MAX_ADVISABLE_VIZ_GALLERY_WIDTH,
|
||||
} from 'src/explore/components/controls/VizTypeControl/VizTypeGallery';
|
||||
|
||||
interface Datasource {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type AddSliceContainerProps = {
|
||||
datasources: Datasource[];
|
||||
type Dataset = {
|
||||
id: number;
|
||||
table_name: string;
|
||||
description: string;
|
||||
datasource_type: string;
|
||||
};
|
||||
|
||||
export type AddSliceContainerProps = {};
|
||||
|
||||
export type AddSliceContainerState = {
|
||||
datasourceId?: string;
|
||||
datasourceType?: string;
|
||||
datasourceValue?: string;
|
||||
datasource?: { label: string; value: string };
|
||||
visType: string | null;
|
||||
};
|
||||
|
||||
|
@ -81,6 +87,42 @@ const StyledContainer = styled.div`
|
|||
margin-top: ${theme.gridUnit * 6}px;
|
||||
}
|
||||
}
|
||||
|
||||
& .ant-tooltip-open {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&&&& .ant-select-selector {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&&&& .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
|
||||
};
|
||||
}
|
||||
|
||||
.tooltip-description {
|
||||
margin-top: ${theme.gridUnit * 2}px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 20;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
|
@ -97,6 +139,16 @@ const StyledVizTypeGallery = styled(VizTypeGallery)`
|
|||
`}
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
${({ theme }) => `
|
||||
position: absolute;
|
||||
left: ${theme.gridUnit * 3}px;
|
||||
right: ${theme.gridUnit * 3}px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default class AddSliceContainer extends React.PureComponent<
|
||||
AddSliceContainerProps,
|
||||
AddSliceContainerState
|
||||
|
@ -110,13 +162,16 @@ export default class AddSliceContainer extends React.PureComponent<
|
|||
this.changeDatasource = this.changeDatasource.bind(this);
|
||||
this.changeVisType = this.changeVisType.bind(this);
|
||||
this.gotoSlice = this.gotoSlice.bind(this);
|
||||
this.newLabel = this.newLabel.bind(this);
|
||||
this.loadDatasources = this.loadDatasources.bind(this);
|
||||
this.handleFilterOption = this.handleFilterOption.bind(this);
|
||||
}
|
||||
|
||||
exploreUrl() {
|
||||
const formData = encodeURIComponent(
|
||||
JSON.stringify({
|
||||
viz_type: this.state.visType,
|
||||
datasource: this.state.datasourceValue,
|
||||
datasource: this.state.datasource?.value,
|
||||
}),
|
||||
);
|
||||
return `/superset/explore/?form_data=${formData}`;
|
||||
|
@ -126,11 +181,8 @@ export default class AddSliceContainer extends React.PureComponent<
|
|||
window.location.href = this.exploreUrl();
|
||||
}
|
||||
|
||||
changeDatasource(value: string) {
|
||||
this.setState({
|
||||
datasourceValue: value,
|
||||
datasourceId: value.split('__')[0],
|
||||
});
|
||||
changeDatasource(datasource: { label: string; value: string }) {
|
||||
this.setState({ datasource });
|
||||
}
|
||||
|
||||
changeVisType(visType: string | null) {
|
||||
|
@ -138,7 +190,57 @@ export default class AddSliceContainer extends React.PureComponent<
|
|||
}
|
||||
|
||||
isBtnDisabled() {
|
||||
return !(this.state.datasourceId && this.state.visType);
|
||||
return !(this.state.datasource?.value && this.state.visType);
|
||||
}
|
||||
|
||||
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) {
|
||||
const query = rison.encode({
|
||||
columns: ['id', 'table_name', 'description', 'datasource_type'],
|
||||
filter: search,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
});
|
||||
return SupersetClient.get({
|
||||
endpoint: `/api/v1/dataset?q=${query}`,
|
||||
}).then((response: JsonResponse) => {
|
||||
const list = response.json.result.map((item: Dataset) => ({
|
||||
value: `${item.id}__${item.datasource_type}`,
|
||||
label: this.newLabel(item),
|
||||
labelText: item.table_name,
|
||||
}));
|
||||
return {
|
||||
data: list,
|
||||
totalCount: response.json.count,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleFilterOption(
|
||||
search: string,
|
||||
option: { label: string; value: number; labelText: string },
|
||||
) {
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
const { labelText } = option;
|
||||
return labelText.toLowerCase().includes(searchValue);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -151,11 +253,12 @@ export default class AddSliceContainer extends React.PureComponent<
|
|||
ariaLabel={t('Dataset')}
|
||||
name="select-datasource"
|
||||
header={<FormLabel required>{t('Choose a dataset')}</FormLabel>}
|
||||
filterOption={this.handleFilterOption}
|
||||
onChange={this.changeDatasource}
|
||||
options={this.props.datasources}
|
||||
options={this.loadDatasources}
|
||||
placeholder={t('Choose a dataset')}
|
||||
showSearch
|
||||
value={this.state.datasourceValue}
|
||||
value={this.state.datasource}
|
||||
/>
|
||||
<span>
|
||||
{t(
|
||||
|
|
|
@ -39,7 +39,7 @@ initFeatureFlags(bootstrapData.common.feature_flags);
|
|||
const App = () => (
|
||||
<ThemeProvider theme={theme}>
|
||||
<DynamicPluginProvider>
|
||||
<AddSliceContainer datasources={bootstrapData.datasources} />
|
||||
<AddSliceContainer />
|
||||
</DynamicPluginProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
|
|
@ -100,6 +100,8 @@ class DatasetRestApi(BaseSupersetModelRestApi):
|
|||
"changed_on_utc",
|
||||
"changed_on_delta_humanized",
|
||||
"default_endpoint",
|
||||
"description",
|
||||
"datasource_type",
|
||||
"explore_url",
|
||||
"extra",
|
||||
"kind",
|
||||
|
|
|
@ -21,7 +21,7 @@ from flask_appbuilder import expose, has_access
|
|||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from flask_babel import lazy_gettext as _
|
||||
|
||||
from superset import is_feature_enabled, security_manager
|
||||
from superset import is_feature_enabled
|
||||
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
|
||||
from superset.models.slice import Slice
|
||||
from superset.typing import FlaskResponse
|
||||
|
@ -62,15 +62,7 @@ class SliceModelView(
|
|||
@expose("/add", methods=["GET", "POST"])
|
||||
@has_access
|
||||
def add(self) -> FlaskResponse:
|
||||
datasources = [
|
||||
{"value": str(d.id) + "__" + d.type, "label": repr(d)}
|
||||
for d in security_manager.get_user_datasources()
|
||||
]
|
||||
payload = {
|
||||
"datasources": sorted(
|
||||
datasources,
|
||||
key=lambda d: d["label"].lower() if isinstance(d["label"], str) else "",
|
||||
),
|
||||
"common": common_bootstrap_payload(),
|
||||
"user": bootstrap_user_data(g.user),
|
||||
}
|
||||
|
|
|
@ -179,7 +179,9 @@ class TestDatasetApi(SupersetTestCase):
|
|||
"changed_on_delta_humanized",
|
||||
"changed_on_utc",
|
||||
"database",
|
||||
"datasource_type",
|
||||
"default_endpoint",
|
||||
"description",
|
||||
"explore_url",
|
||||
"extra",
|
||||
"id",
|
||||
|
|
Loading…
Reference in New Issue