mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
refactor: Migrate react-select to Antd Select in Metrics and Filters popovers (#12042)
* Migrate react-select to Antd select in Metrics popover * Fix tests * Migrate react-select to Antd select in Filters popover * Migrate SelectControl to Antd Select * Add label with number of options left
This commit is contained in:
parent
8bda6b0bd9
commit
794d318989
@ -59,10 +59,10 @@ const simpleCustomFilter = new AdhocFilter({
|
||||
});
|
||||
|
||||
const options = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
{ saved_metric_name: 'my_custom_metric' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'source', id: 1 },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target', id: 2 },
|
||||
{ type: 'DOUBLE', column_name: 'value', id: 3 },
|
||||
{ saved_metric_name: 'my_custom_metric', id: 4 },
|
||||
sumValueAdhocMetric,
|
||||
];
|
||||
|
||||
@ -91,9 +91,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
|
||||
|
||||
it('passes the new adhocFilter to onChange after onSubjectChange', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper
|
||||
.instance()
|
||||
.onSubjectChange({ type: 'VARCHAR(255)', column_name: 'source' });
|
||||
wrapper.instance().onSubjectChange(1);
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(onChange.lastCall.args[0]).toEqual(
|
||||
simpleAdhocFilter.duplicateWith({ subject: 'source' }),
|
||||
@ -102,7 +100,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
|
||||
|
||||
it('may alter the clause in onSubjectChange if the old clause is not appropriate', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper.instance().onSubjectChange(sumValueAdhocMetric);
|
||||
wrapper.instance().onSubjectChange(sumValueAdhocMetric.optionName);
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(onChange.lastCall.args[0]).toEqual(
|
||||
simpleAdhocFilter.duplicateWith({
|
||||
|
@ -28,9 +28,9 @@ import AdhocMetricEditPopover from 'src/explore/components/AdhocMetricEditPopove
|
||||
import { AGGREGATES } from 'src/explore/constants';
|
||||
|
||||
const columns = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'source', id: 1 },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target', id: 2 },
|
||||
{ type: 'DOUBLE', column_name: 'value', id: 3 },
|
||||
];
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
@ -68,7 +68,7 @@ describe('AdhocMetricEditPopover', () => {
|
||||
|
||||
it('overwrites the adhocMetric in state with onColumnChange', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onColumnChange(columns[0]);
|
||||
wrapper.instance().onColumnChange(columns[0].id);
|
||||
expect(wrapper.state('adhocMetric')).toEqual(
|
||||
sumValueAdhocMetric.duplicateWith({ column: columns[0] }),
|
||||
);
|
||||
@ -95,7 +95,7 @@ describe('AdhocMetricEditPopover', () => {
|
||||
expect(wrapper.find(Button).find({ disabled: true })).not.toExist();
|
||||
wrapper.instance().onColumnChange(null);
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toExist();
|
||||
wrapper.instance().onColumnChange({ column: columns[0] });
|
||||
wrapper.instance().onColumnChange(columns[0].id);
|
||||
expect(wrapper.find(Button).find({ disabled: true })).not.toExist();
|
||||
wrapper.instance().onAggregateChange(null);
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toExist();
|
||||
@ -104,7 +104,7 @@ describe('AdhocMetricEditPopover', () => {
|
||||
it('highlights save if changes are present', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).not.toExist();
|
||||
wrapper.instance().onColumnChange({ column: columns[1] });
|
||||
wrapper.instance().onColumnChange(columns[1].id);
|
||||
expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).toExist();
|
||||
});
|
||||
|
||||
|
@ -20,6 +20,10 @@ import { styled } from '@superset-ui/core';
|
||||
import { Select as BaseSelect } from 'src/common/components';
|
||||
|
||||
const StyledSelect = styled(BaseSelect)`
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const StyledGraySelect = styled(StyledSelect)`
|
||||
&.ant-select-single {
|
||||
.ant-select-selector {
|
||||
height: 36px;
|
||||
@ -44,3 +48,7 @@ const StyledOption = BaseSelect.Option;
|
||||
export const Select = Object.assign(StyledSelect, {
|
||||
Option: StyledOption,
|
||||
});
|
||||
|
||||
export const GraySelect = Object.assign(StyledGraySelect, {
|
||||
Option: StyledOption,
|
||||
});
|
||||
|
@ -19,8 +19,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormGroup } from 'react-bootstrap';
|
||||
import { Select } from 'src/components/Select';
|
||||
import { t, SupersetClient } from '@superset-ui/core';
|
||||
import { Select } from 'src/common/components/Select';
|
||||
import { t, SupersetClient, styled } from '@superset-ui/core';
|
||||
|
||||
import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../AdhocFilter';
|
||||
import adhocMetricType from '../propTypes/adhocMetricType';
|
||||
@ -36,7 +36,15 @@ import {
|
||||
DISABLE_INPUT_OPERATORS,
|
||||
} from '../constants';
|
||||
import FilterDefinitionOption from './FilterDefinitionOption';
|
||||
import SelectControl from './controls/SelectControl';
|
||||
|
||||
const SelectWithLabel = styled(Select)`
|
||||
.ant-select-selector::after {
|
||||
content: '${({ labelText }) => labelText || '\\A0'}';
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
color: ${({ theme }) => theme.colors.grayscale.light1};
|
||||
}
|
||||
`;
|
||||
|
||||
const propTypes = {
|
||||
adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired,
|
||||
@ -92,11 +100,8 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
||||
};
|
||||
|
||||
this.selectProps = {
|
||||
isMulti: false,
|
||||
name: 'select-column',
|
||||
labelKey: 'label',
|
||||
autosize: false,
|
||||
clearable: false,
|
||||
showSearch: true,
|
||||
};
|
||||
|
||||
this.menuPortalProps = {
|
||||
@ -116,7 +121,11 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
||||
}
|
||||
}
|
||||
|
||||
onSubjectChange(option) {
|
||||
onSubjectChange(id) {
|
||||
const option = this.props.options.find(
|
||||
option => option.id === id || option.optionName === id,
|
||||
);
|
||||
|
||||
let subject;
|
||||
let clause;
|
||||
// infer the new clause based on what subject was selected.
|
||||
@ -247,27 +256,38 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
||||
}
|
||||
}
|
||||
|
||||
optionsRemaining() {
|
||||
const { suggestions } = this.state;
|
||||
const { comparator } = this.props.adhocFilter;
|
||||
// if select is multi/value is array, we show the options not selected
|
||||
const valuesFromSuggestionsLength = Array.isArray(comparator)
|
||||
? comparator.filter(v => suggestions.includes(v)).length
|
||||
: 0;
|
||||
return suggestions?.length - valuesFromSuggestionsLength ?? 0;
|
||||
}
|
||||
|
||||
createSuggestionsPlaceholder() {
|
||||
const optionsRemaining = this.optionsRemaining();
|
||||
const placeholder = t('%s option(s)', optionsRemaining);
|
||||
return optionsRemaining ? placeholder : '';
|
||||
}
|
||||
|
||||
renderSubjectOptionLabel(option) {
|
||||
return <FilterDefinitionOption option={option} />;
|
||||
}
|
||||
|
||||
renderSubjectOptionValue({ value }) {
|
||||
return <span>{value}</span>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { adhocFilter, options: columns, datasource } = this.props;
|
||||
const { adhocFilter, options, datasource } = this.props;
|
||||
let columns = options;
|
||||
const { subject, operator, comparator } = adhocFilter;
|
||||
const subjectSelectProps = {
|
||||
options: columns,
|
||||
value: subject ? { value: subject } : undefined,
|
||||
value: subject ?? undefined,
|
||||
onChange: this.onSubjectChange,
|
||||
optionRenderer: this.renderSubjectOptionLabel,
|
||||
valueRenderer: this.renderSubjectOptionValue,
|
||||
valueKey: 'filterOptionName',
|
||||
noResultsText: t(
|
||||
notFoundContent: t(
|
||||
'No such column found. To filter on a metric, try the Custom SQL tab.',
|
||||
),
|
||||
filterOption: (input, option) =>
|
||||
option.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0,
|
||||
};
|
||||
|
||||
if (datasource.type === 'druid') {
|
||||
@ -283,19 +303,16 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
||||
adhocFilter.clause === CLAUSES.WHERE
|
||||
? t('%s column(s)', columns.length)
|
||||
: t('To filter on a metric, use Custom SQL tab.');
|
||||
// make sure options have `column_name`
|
||||
subjectSelectProps.options = columns.filter(option => option.column_name);
|
||||
columns = options.filter(option => option.column_name);
|
||||
}
|
||||
|
||||
const operatorSelectProps = {
|
||||
placeholder: t('%s operators(s)', OPERATORS_OPTIONS.length),
|
||||
// like AGGREGTES_OPTIONS, operator options are string
|
||||
options: OPERATORS_OPTIONS.filter(op =>
|
||||
this.isOperatorRelevant(op, subject),
|
||||
),
|
||||
value: operator,
|
||||
onChange: this.onOperatorChange,
|
||||
getOptionLabel: translateOperator,
|
||||
filterOption: (input, option) =>
|
||||
option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0,
|
||||
};
|
||||
|
||||
return (
|
||||
@ -303,36 +320,63 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
||||
<FormGroup className="adhoc-filter-simple-column-dropdown">
|
||||
<Select
|
||||
{...this.selectProps}
|
||||
{...this.menuPortalProps}
|
||||
{...subjectSelectProps}
|
||||
name="filter-column"
|
||||
/>
|
||||
>
|
||||
{columns.map(column => (
|
||||
<Select.Option
|
||||
value={column.id || column.optionName}
|
||||
filterBy={
|
||||
column.saved_metric_name || column.column_name || column.label
|
||||
}
|
||||
key={column.id}
|
||||
>
|
||||
{this.renderSubjectOptionLabel(column)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Select
|
||||
{...this.selectProps}
|
||||
{...this.menuPortalProps}
|
||||
{...operatorSelectProps}
|
||||
name="filter-operator"
|
||||
/>
|
||||
>
|
||||
{OPERATORS_OPTIONS.filter(op =>
|
||||
this.isOperatorRelevant(op, subject),
|
||||
).map(option => (
|
||||
<Select.Option value={option} key={option}>
|
||||
{translateOperator(option)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
<FormGroup data-test="adhoc-filter-simple-value">
|
||||
{MULTI_OPERATORS.has(operator) ||
|
||||
this.state.suggestions.length > 0 ? (
|
||||
<SelectControl
|
||||
{...this.menuPortalProps}
|
||||
<SelectWithLabel
|
||||
name="filter-value"
|
||||
autoFocus
|
||||
freeForm
|
||||
multi={MULTI_OPERATORS.has(operator)}
|
||||
allowClear
|
||||
showSearch
|
||||
mode={MULTI_OPERATORS.has(operator) && 'tags'}
|
||||
tokenSeparators={[',', ' ', ';']}
|
||||
loading={this.state.loading}
|
||||
value={comparator}
|
||||
isLoading={this.state.loading}
|
||||
choices={this.state.suggestions}
|
||||
onChange={this.onComparatorChange}
|
||||
showHeader={false}
|
||||
noResultsText={t('type a value here')}
|
||||
notFoundContent={t('type a value here')}
|
||||
disabled={DISABLE_INPUT_OPERATORS.includes(operator)}
|
||||
/>
|
||||
placeholder={this.createSuggestionsPlaceholder()}
|
||||
labelText={
|
||||
comparator?.length > 0 && this.createSuggestionsPlaceholder()
|
||||
}
|
||||
>
|
||||
{this.state.suggestions.map(suggestion => (
|
||||
<Select.Option value={suggestion} key={suggestion}>
|
||||
{suggestion}
|
||||
</Select.Option>
|
||||
))}
|
||||
</SelectWithLabel>
|
||||
) : (
|
||||
<input
|
||||
name="filter-value"
|
||||
|
@ -19,7 +19,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormGroup } from 'react-bootstrap';
|
||||
import Select from 'src/components/Select';
|
||||
import { Select } from 'src/common/components/Select';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { SQLEditor } from 'src/components/AsyncAceEditor';
|
||||
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
|
||||
@ -51,11 +51,7 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
|
||||
this.handleAceEditorRef = this.handleAceEditorRef.bind(this);
|
||||
|
||||
this.selectProps = {
|
||||
isMulti: false,
|
||||
name: 'select-column',
|
||||
labelKey: 'label',
|
||||
autosize: false,
|
||||
clearable: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -94,7 +90,6 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
|
||||
|
||||
const clauseSelectProps = {
|
||||
placeholder: t('choose WHERE or HAVING...'),
|
||||
options: Object.keys(CLAUSES),
|
||||
value: adhocFilter.clause,
|
||||
onChange: this.onSqlExpressionClauseChange,
|
||||
};
|
||||
@ -121,7 +116,13 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
|
||||
{...this.selectProps}
|
||||
{...clauseSelectProps}
|
||||
className="filter-edit-clause-dropdown"
|
||||
/>
|
||||
>
|
||||
{Object.keys(CLAUSES).map(clause => (
|
||||
<Select.Option value={clause} key={clause}>
|
||||
{clause}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<span className="filter-edit-clause-info">
|
||||
<strong>WHERE</strong> {t('filters by columns')}
|
||||
<br />
|
||||
|
@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
|
||||
import { FormGroup } from 'react-bootstrap';
|
||||
import Tabs from 'src/common/components/Tabs';
|
||||
import Button from 'src/components/Button';
|
||||
import Select from 'src/components/Select';
|
||||
import { Select } from 'src/common/components/Select';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { ColumnOption } from '@superset-ui/chart-controls';
|
||||
|
||||
@ -70,27 +70,12 @@ export default class AdhocMetricEditPopover extends React.Component {
|
||||
this.handleAceEditorRef = this.handleAceEditorRef.bind(this);
|
||||
this.refreshAceEditor = this.refreshAceEditor.bind(this);
|
||||
|
||||
this.popoverRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
adhocMetric: this.props.adhocMetric,
|
||||
width: startingWidth,
|
||||
height: startingHeight,
|
||||
};
|
||||
|
||||
this.selectProps = {
|
||||
labelKey: 'label',
|
||||
isMulti: false,
|
||||
autosize: false,
|
||||
clearable: true,
|
||||
};
|
||||
|
||||
this.menuPortalProps = {
|
||||
menuPosition: 'fixed',
|
||||
menuPlacement: 'bottom',
|
||||
menuPortalTarget: this.popoverRef.current,
|
||||
};
|
||||
|
||||
document.addEventListener('mouseup', this.onMouseUp);
|
||||
}
|
||||
|
||||
@ -118,7 +103,8 @@ export default class AdhocMetricEditPopover extends React.Component {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
onColumnChange(column) {
|
||||
onColumnChange(columnId) {
|
||||
const column = this.props.columns.find(column => column.id === columnId);
|
||||
this.setState(prevState => ({
|
||||
adhocMetric: prevState.adhocMetric.duplicateWith({
|
||||
column,
|
||||
@ -213,20 +199,23 @@ export default class AdhocMetricEditPopover extends React.Component {
|
||||
|
||||
const columnSelectProps = {
|
||||
placeholder: t('%s column(s)', columns.length),
|
||||
options: columns,
|
||||
value:
|
||||
(adhocMetric.column && adhocMetric.column.column_name) ||
|
||||
adhocMetric.inferSqlExpressionColumn(),
|
||||
onChange: this.onColumnChange,
|
||||
optionRenderer: this.renderColumnOption,
|
||||
valueKey: 'column_name',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
filterOption: (input, option) =>
|
||||
option.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0,
|
||||
};
|
||||
|
||||
const aggregateSelectProps = {
|
||||
placeholder: t('%s aggregates(s)', AGGREGATES_OPTIONS.length),
|
||||
options: AGGREGATES_OPTIONS,
|
||||
value: adhocMetric.aggregate || adhocMetric.inferSqlExpressionAggregate(),
|
||||
onChange: this.onAggregateChange,
|
||||
allowClear: true,
|
||||
autoFocus: true,
|
||||
showSearch: true,
|
||||
};
|
||||
|
||||
if (this.props.datasourceType === 'druid') {
|
||||
@ -241,7 +230,6 @@ export default class AdhocMetricEditPopover extends React.Component {
|
||||
<div
|
||||
id="metrics-edit-popover"
|
||||
data-test="metrics-edit-popover"
|
||||
ref={this.popoverRef}
|
||||
{...popoverProps}
|
||||
>
|
||||
<Tabs
|
||||
@ -261,24 +249,29 @@ export default class AdhocMetricEditPopover extends React.Component {
|
||||
<FormLabel>
|
||||
<strong>column</strong>
|
||||
</FormLabel>
|
||||
<Select
|
||||
name="select-column"
|
||||
{...this.selectProps}
|
||||
{...this.menuPortalProps}
|
||||
{...columnSelectProps}
|
||||
/>
|
||||
<Select name="select-column" {...columnSelectProps}>
|
||||
{columns.map(column => (
|
||||
<Select.Option
|
||||
value={column.id}
|
||||
filterBy={column.verbose_name || column.column_name}
|
||||
key={column.id}
|
||||
>
|
||||
{this.renderColumnOption(column)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
<strong>aggregate</strong>
|
||||
</FormLabel>
|
||||
<Select
|
||||
name="select-aggregate"
|
||||
{...this.selectProps}
|
||||
{...this.menuPortalProps}
|
||||
{...aggregateSelectProps}
|
||||
autoFocus
|
||||
/>
|
||||
<Select name="select-aggregate" {...aggregateSelectProps}>
|
||||
{AGGREGATES_OPTIONS.map(option => (
|
||||
<Select.Option value={option} key={option}>
|
||||
{option}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
|
@ -24,7 +24,7 @@ import { useSingleViewResource } from 'src/views/CRUD/hooks';
|
||||
import Icon from 'src/components/Icon';
|
||||
import Modal from 'src/common/components/Modal';
|
||||
import { Switch } from 'src/common/components/Switch';
|
||||
import { Select } from 'src/common/components/Select';
|
||||
import { GraySelect as Select } from 'src/common/components/Select';
|
||||
import { Radio } from 'src/common/components/Radio';
|
||||
import { AsyncSelect } from 'src/components/Select';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
|
Loading…
Reference in New Issue
Block a user