mirror of
https://github.com/apache/superset.git
synced 2024-09-16 10:39:55 -04:00
* feat: add single select and inverse selection to numeric range (#16722) * Ignore invalid eslint errors regarding conditionally called hooks. * Add license header to new file. * Flipped the numerical range values for the minimum slider so that the highlighted range value accurately reflects the applied filter. * Resolved linting errors * Remove unnecessary important flag from css
This commit is contained in:
parent
f949c8ed7a
commit
54b56fe12f
@ -16,6 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import {
|
import {
|
||||||
ColumnMeta,
|
ColumnMeta,
|
||||||
InfoTooltipWithTrigger,
|
InfoTooltipWithTrigger,
|
||||||
@ -35,7 +36,7 @@ import {
|
|||||||
t,
|
t,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { FormInstance } from 'antd/lib/form';
|
import { FormInstance } from 'antd/lib/form';
|
||||||
import { isEmpty, isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -73,6 +74,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
NativeFilterType,
|
NativeFilterType,
|
||||||
} from 'src/dashboard/components/nativeFilters/types';
|
} from 'src/dashboard/components/nativeFilters/types';
|
||||||
|
import { SingleValueType } from 'src/filters/components/Range/SingleValueType';
|
||||||
import { getFormData } from 'src/dashboard/components/nativeFilters/utils';
|
import { getFormData } from 'src/dashboard/components/nativeFilters/utils';
|
||||||
import {
|
import {
|
||||||
CASCADING_FILTERS,
|
CASCADING_FILTERS,
|
||||||
@ -543,6 +545,15 @@ const FiltersConfigForm = (
|
|||||||
!!filterToEdit?.adhoc_filters?.length ||
|
!!filterToEdit?.adhoc_filters?.length ||
|
||||||
!!filterToEdit?.time_range;
|
!!filterToEdit?.time_range;
|
||||||
|
|
||||||
|
const hasEnableSingleValue =
|
||||||
|
formFilter?.controlValues?.enableSingleValue !== undefined ||
|
||||||
|
filterToEdit?.controlValues?.enableSingleValue !== undefined;
|
||||||
|
|
||||||
|
let enableSingleValue = filterToEdit?.controlValues?.enableSingleValue;
|
||||||
|
if (formFilter?.controlValues?.enableSingleMaxValue !== undefined) {
|
||||||
|
({ enableSingleValue } = formFilter.controlValues);
|
||||||
|
}
|
||||||
|
|
||||||
const hasSorting =
|
const hasSorting =
|
||||||
typeof formFilter?.controlValues?.sortAscending === 'boolean' ||
|
typeof formFilter?.controlValues?.sortAscending === 'boolean' ||
|
||||||
typeof filterToEdit?.controlValues?.sortAscending === 'boolean';
|
typeof filterToEdit?.controlValues?.sortAscending === 'boolean';
|
||||||
@ -568,6 +579,17 @@ const FiltersConfigForm = (
|
|||||||
forceUpdate();
|
forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEnableSingleValueChanged = (value: SingleValueType | undefined) => {
|
||||||
|
const previous = form.getFieldValue('filters')?.[filterId].controlValues;
|
||||||
|
setNativeFilterFieldValues(form, filterId, {
|
||||||
|
controlValues: {
|
||||||
|
...previous,
|
||||||
|
enableSingleValue: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
const validatePreFilter = () =>
|
const validatePreFilter = () =>
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
@ -669,12 +691,13 @@ const FiltersConfigForm = (
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Run only once when the control items are available
|
// Run only once
|
||||||
if (isActive && !isEmpty(controlItems)) {
|
if (isActive) {
|
||||||
const hasCheckedAdvancedControl =
|
const hasCheckedAdvancedControl =
|
||||||
hasParentFilter ||
|
hasParentFilter ||
|
||||||
hasPreFilter ||
|
hasPreFilter ||
|
||||||
hasSorting ||
|
hasSorting ||
|
||||||
|
hasEnableSingleValue ||
|
||||||
Object.keys(controlItems)
|
Object.keys(controlItems)
|
||||||
.filter(key => !BASIC_CONTROL_ITEMS.includes(key))
|
.filter(key => !BASIC_CONTROL_ITEMS.includes(key))
|
||||||
.some(key => controlItems[key].checked);
|
.some(key => controlItems[key].checked);
|
||||||
@ -1137,7 +1160,7 @@ const FiltersConfigForm = (
|
|||||||
</CollapsibleControl>
|
</CollapsibleControl>
|
||||||
</CleanFormItem>
|
</CleanFormItem>
|
||||||
)}
|
)}
|
||||||
{formFilter?.filterType !== 'filter_range' && (
|
{formFilter?.filterType !== 'filter_range' ? (
|
||||||
<CleanFormItem name={['filters', filterId, 'sortFilter']}>
|
<CleanFormItem name={['filters', filterId, 'sortFilter']}>
|
||||||
<CollapsibleControl
|
<CollapsibleControl
|
||||||
initialValue={hasSorting}
|
initialValue={hasSorting}
|
||||||
@ -1204,6 +1227,48 @@ const FiltersConfigForm = (
|
|||||||
)}
|
)}
|
||||||
</CollapsibleControl>
|
</CollapsibleControl>
|
||||||
</CleanFormItem>
|
</CleanFormItem>
|
||||||
|
) : (
|
||||||
|
<CleanFormItem name={['filters', filterId, 'rangeFilter']}>
|
||||||
|
<CollapsibleControl
|
||||||
|
initialValue={hasEnableSingleValue}
|
||||||
|
title={t('Single Value')}
|
||||||
|
onChange={checked => {
|
||||||
|
onEnableSingleValueChanged(
|
||||||
|
checked ? SingleValueType.Exact : undefined,
|
||||||
|
);
|
||||||
|
formChanged();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledRowFormItem
|
||||||
|
name={[
|
||||||
|
'filters',
|
||||||
|
filterId,
|
||||||
|
'controlValues',
|
||||||
|
'enableSingleValue',
|
||||||
|
]}
|
||||||
|
initialValue={enableSingleValue}
|
||||||
|
label={
|
||||||
|
<StyledLabel>{t('Single value type')}</StyledLabel>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Radio.Group
|
||||||
|
onChange={value =>
|
||||||
|
onEnableSingleValueChanged(value.target.value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Radio value={SingleValueType.Minimum}>
|
||||||
|
{t('Minimum')}
|
||||||
|
</Radio>
|
||||||
|
<Radio value={SingleValueType.Exact}>
|
||||||
|
{t('Exact')}
|
||||||
|
</Radio>
|
||||||
|
<Radio value={SingleValueType.Maximum}>
|
||||||
|
{t('Maximum')}
|
||||||
|
</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</StyledRowFormItem>
|
||||||
|
</CollapsibleControl>
|
||||||
|
</CleanFormItem>
|
||||||
)}
|
)}
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
)}
|
)}
|
||||||
|
@ -125,6 +125,16 @@ test('Should render null empty when "getControlItems" return []', () => {
|
|||||||
expect(container.children).toHaveLength(0);
|
expect(container.children).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should render null empty when "getControlItems" return enableSingleValue', () => {
|
||||||
|
const props = createProps();
|
||||||
|
(getControlItems as jest.Mock).mockReturnValue([
|
||||||
|
{ name: 'enableSingleValue', config: { renderTrigger: true } },
|
||||||
|
]);
|
||||||
|
const controlItemsMap = getControlItemsMap(props);
|
||||||
|
const { container } = renderControlItems(controlItemsMap);
|
||||||
|
expect(container.children).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('Should render null empty when "controlItems" are falsy', () => {
|
test('Should render null empty when "controlItems" are falsy', () => {
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
const controlItems = [null, false, {}, { config: { renderTrigger: false } }];
|
const controlItems = [null, false, {}, { config: { renderTrigger: false } }];
|
||||||
|
@ -145,7 +145,8 @@ export default function getControlItemsMap({
|
|||||||
.filter(
|
.filter(
|
||||||
(controlItem: CustomControlItem) =>
|
(controlItem: CustomControlItem) =>
|
||||||
controlItem?.config?.renderTrigger &&
|
controlItem?.config?.renderTrigger &&
|
||||||
controlItem.name !== 'sortAscending',
|
controlItem.name !== 'sortAscending' &&
|
||||||
|
controlItem.name !== 'enableSingleValue',
|
||||||
)
|
)
|
||||||
.forEach(controlItem => {
|
.forEach(controlItem => {
|
||||||
const initialValue =
|
const initialValue =
|
||||||
|
@ -20,6 +20,7 @@ import { AppSection, GenericDataType } from '@superset-ui/core';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'spec/helpers/testing-library';
|
import { render } from 'spec/helpers/testing-library';
|
||||||
import RangeFilterPlugin from './RangeFilterPlugin';
|
import RangeFilterPlugin from './RangeFilterPlugin';
|
||||||
|
import { SingleValueType } from './SingleValueType';
|
||||||
import transformProps from './transformProps';
|
import transformProps from './transformProps';
|
||||||
|
|
||||||
const rangeProps = {
|
const rangeProps = {
|
||||||
@ -118,4 +119,61 @@ describe('RangeFilterPlugin', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call setDataMask with correct greater than filter', () => {
|
||||||
|
getWrapper({ enableSingleValue: SingleValueType.Minimum });
|
||||||
|
expect(setDataMask).toHaveBeenCalledWith({
|
||||||
|
extraFormData: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'SP_POP_TOTL',
|
||||||
|
op: '>=',
|
||||||
|
val: 70,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
filterState: {
|
||||||
|
label: 'x ≥ 70',
|
||||||
|
value: [70, 100],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call setDataMask with correct less than filter', () => {
|
||||||
|
getWrapper({ enableSingleValue: SingleValueType.Maximum });
|
||||||
|
expect(setDataMask).toHaveBeenCalledWith({
|
||||||
|
extraFormData: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'SP_POP_TOTL',
|
||||||
|
op: '<=',
|
||||||
|
val: 70,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
filterState: {
|
||||||
|
label: 'x ≤ 70',
|
||||||
|
value: [10, 70],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call setDataMask with correct exact filter', () => {
|
||||||
|
getWrapper({ enableSingleValue: SingleValueType.Exact });
|
||||||
|
expect(setDataMask).toHaveBeenCalledWith({
|
||||||
|
extraFormData: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'SP_POP_TOTL',
|
||||||
|
op: '==',
|
||||||
|
val: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
filterState: {
|
||||||
|
label: 'x = 10',
|
||||||
|
value: [10, 10],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -30,6 +30,40 @@ import { rgba } from 'emotion-rgba';
|
|||||||
import { PluginFilterRangeProps } from './types';
|
import { PluginFilterRangeProps } from './types';
|
||||||
import { StatusMessage, StyledFormItem, FilterPluginStyle } from '../common';
|
import { StatusMessage, StyledFormItem, FilterPluginStyle } from '../common';
|
||||||
import { getRangeExtraFormData } from '../../utils';
|
import { getRangeExtraFormData } from '../../utils';
|
||||||
|
import { SingleValueType } from './SingleValueType';
|
||||||
|
|
||||||
|
const LIGHT_BLUE = '#99e7f0';
|
||||||
|
const DARK_BLUE = '#6dd3e3';
|
||||||
|
const LIGHT_GRAY = '#f5f5f5';
|
||||||
|
const DARK_GRAY = '#e1e1e1';
|
||||||
|
|
||||||
|
const StyledMinSlider = styled(Slider)<{
|
||||||
|
validateStatus?: 'error' | 'warning' | 'info';
|
||||||
|
}>`
|
||||||
|
${({ theme, validateStatus }) => `
|
||||||
|
.ant-slider-rail {
|
||||||
|
background-color: ${
|
||||||
|
validateStatus ? theme.colors[validateStatus]?.light1 : LIGHT_BLUE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-slider-track {
|
||||||
|
background-color: ${LIGHT_GRAY};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.ant-slider-rail {
|
||||||
|
background-color: ${
|
||||||
|
validateStatus ? theme.colors[validateStatus]?.base : DARK_BLUE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-slider-track {
|
||||||
|
background-color: ${DARK_GRAY};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
const Wrapper = styled.div<{ validateStatus?: 'error' | 'warning' | 'info' }>`
|
const Wrapper = styled.div<{ validateStatus?: 'error' | 'warning' | 'info' }>`
|
||||||
${({ theme, validateStatus }) => `
|
${({ theme, validateStatus }) => `
|
||||||
@ -80,6 +114,9 @@ const numberFormatter = getNumberFormatter(NumberFormats.SMART_NUMBER);
|
|||||||
const tipFormatter = (value: number) => numberFormatter(value);
|
const tipFormatter = (value: number) => numberFormatter(value);
|
||||||
|
|
||||||
const getLabel = (lower: number | null, upper: number | null): string => {
|
const getLabel = (lower: number | null, upper: number | null): string => {
|
||||||
|
if (lower !== null && upper !== null && lower === upper) {
|
||||||
|
return `x = ${numberFormatter(lower)}`;
|
||||||
|
}
|
||||||
if (lower !== null && upper !== null) {
|
if (lower !== null && upper !== null) {
|
||||||
return `${numberFormatter(lower)} ≤ x ≤ ${numberFormatter(upper)}`;
|
return `${numberFormatter(lower)} ≤ x ≤ ${numberFormatter(upper)}`;
|
||||||
}
|
}
|
||||||
@ -120,24 +157,38 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||||||
const [row] = data;
|
const [row] = data;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const { min, max }: { min: number; max: number } = row;
|
const { min, max }: { min: number; max: number } = row;
|
||||||
const { groupby, defaultValue, inputRef } = formData;
|
const { groupby, defaultValue, inputRef, enableSingleValue } = formData;
|
||||||
|
|
||||||
|
const enableSingleMinValue = enableSingleValue === SingleValueType.Minimum;
|
||||||
|
const enableSingleMaxValue = enableSingleValue === SingleValueType.Maximum;
|
||||||
|
const enableSingleExactValue = enableSingleValue === SingleValueType.Exact;
|
||||||
|
const rangeValue = enableSingleValue === undefined;
|
||||||
|
|
||||||
const [col = ''] = ensureIsArray(groupby).map(getColumnLabel);
|
const [col = ''] = ensureIsArray(groupby).map(getColumnLabel);
|
||||||
const [value, setValue] = useState<[number, number]>(
|
const [value, setValue] = useState<[number, number]>(
|
||||||
defaultValue ?? [min, max],
|
defaultValue ?? [min, enableSingleExactValue ? min : max],
|
||||||
);
|
);
|
||||||
const [marks, setMarks] = useState<{ [key: number]: string }>({});
|
const [marks, setMarks] = useState<{ [key: number]: string }>({});
|
||||||
|
const minIndex = 0;
|
||||||
|
const maxIndex = 1;
|
||||||
|
const minMax = value ?? [min, max];
|
||||||
|
|
||||||
const getBounds = useCallback(
|
const getBounds = useCallback(
|
||||||
(
|
(
|
||||||
value: [number, number],
|
value: [number, number],
|
||||||
): { lower: number | null; upper: number | null } => {
|
): { lower: number | null; upper: number | null } => {
|
||||||
const [lowerRaw, upperRaw] = value;
|
const [lowerRaw, upperRaw] = value;
|
||||||
|
|
||||||
|
if (enableSingleExactValue) {
|
||||||
|
return { lower: lowerRaw, upper: upperRaw };
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lower: lowerRaw > min ? lowerRaw : null,
|
lower: lowerRaw > min ? lowerRaw : null,
|
||||||
upper: upperRaw < max ? upperRaw : null,
|
upper: upperRaw < max ? upperRaw : null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[max, min],
|
[max, min, enableSingleExactValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAfterChange = useCallback(
|
const handleAfterChange = useCallback(
|
||||||
@ -166,8 +217,34 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||||||
if (row?.min === undefined && row?.max === undefined) {
|
if (row?.min === undefined && row?.max === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleAfterChange(filterState.value ?? [min, max]);
|
|
||||||
}, [JSON.stringify(filterState.value), JSON.stringify(data)]);
|
let filterStateValue = filterState.value ?? [min, max];
|
||||||
|
if (enableSingleMaxValue) {
|
||||||
|
const filterStateMax =
|
||||||
|
filterStateValue[maxIndex] <= minMax[maxIndex]
|
||||||
|
? filterStateValue[maxIndex]
|
||||||
|
: minMax[maxIndex];
|
||||||
|
|
||||||
|
filterStateValue = [min, filterStateMax];
|
||||||
|
} else if (enableSingleMinValue) {
|
||||||
|
const filterStateMin =
|
||||||
|
filterStateValue[minIndex] >= minMax[minIndex]
|
||||||
|
? filterStateValue[minIndex]
|
||||||
|
: minMax[minIndex];
|
||||||
|
|
||||||
|
filterStateValue = [filterStateMin, max];
|
||||||
|
} else if (enableSingleExactValue) {
|
||||||
|
filterStateValue = [minMax[minIndex], minMax[minIndex]];
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAfterChange(filterStateValue);
|
||||||
|
}, [
|
||||||
|
enableSingleMaxValue,
|
||||||
|
enableSingleMinValue,
|
||||||
|
enableSingleExactValue,
|
||||||
|
JSON.stringify(filterState.value),
|
||||||
|
JSON.stringify(data),
|
||||||
|
]);
|
||||||
|
|
||||||
const formItemExtra = useMemo(() => {
|
const formItemExtra = useMemo(() => {
|
||||||
if (filterState.validateMessage) {
|
if (filterState.validateMessage) {
|
||||||
@ -180,7 +257,23 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [filterState.validateMessage, filterState.validateStatus]);
|
}, [filterState.validateMessage, filterState.validateStatus]);
|
||||||
|
|
||||||
const minMax = useMemo(() => value ?? [min, max], [max, min, value]);
|
useEffect(() => {
|
||||||
|
if (enableSingleMaxValue) {
|
||||||
|
handleAfterChange([min, minMax[minIndex]]);
|
||||||
|
}
|
||||||
|
}, [enableSingleMaxValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enableSingleMinValue) {
|
||||||
|
handleAfterChange([minMax[maxIndex], max]);
|
||||||
|
}
|
||||||
|
}, [enableSingleMinValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enableSingleExactValue) {
|
||||||
|
handleAfterChange([minMax[minIndex], minMax[minIndex]]);
|
||||||
|
}
|
||||||
|
}, [enableSingleExactValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterPluginStyle height={height} width={width}>
|
<FilterPluginStyle height={height} width={width}>
|
||||||
@ -197,16 +290,53 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||||||
onMouseEnter={setFocusedFilter}
|
onMouseEnter={setFocusedFilter}
|
||||||
onMouseLeave={unsetFocusedFilter}
|
onMouseLeave={unsetFocusedFilter}
|
||||||
>
|
>
|
||||||
<Slider
|
{enableSingleMaxValue && (
|
||||||
range
|
<Slider
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
value={minMax}
|
value={minMax[maxIndex]}
|
||||||
onAfterChange={handleAfterChange}
|
tipFormatter={tipFormatter}
|
||||||
onChange={handleChange}
|
marks={marks}
|
||||||
tipFormatter={tipFormatter}
|
onAfterChange={value => handleAfterChange([min, value])}
|
||||||
marks={marks}
|
onChange={value => handleChange([min, value])}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{enableSingleMinValue && (
|
||||||
|
<StyledMinSlider
|
||||||
|
validateStatus={filterState.validateStatus}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
value={minMax[minIndex]}
|
||||||
|
tipFormatter={tipFormatter}
|
||||||
|
marks={marks}
|
||||||
|
onAfterChange={value => handleAfterChange([value, max])}
|
||||||
|
onChange={value => handleChange([value, max])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{enableSingleExactValue && (
|
||||||
|
<Slider
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
included={false}
|
||||||
|
value={minMax[minIndex]}
|
||||||
|
tipFormatter={tipFormatter}
|
||||||
|
marks={marks}
|
||||||
|
onAfterChange={value => handleAfterChange([value, value])}
|
||||||
|
onChange={value => handleChange([value, value])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{rangeValue && (
|
||||||
|
<Slider
|
||||||
|
range
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
value={minMax}
|
||||||
|
onAfterChange={handleAfterChange}
|
||||||
|
onChange={handleChange}
|
||||||
|
tipFormatter={tipFormatter}
|
||||||
|
marks={marks}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</StyledFormItem>
|
</StyledFormItem>
|
||||||
)}
|
)}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum SingleValueType {
|
||||||
|
Minimum,
|
||||||
|
Exact,
|
||||||
|
Maximum,
|
||||||
|
}
|
@ -22,6 +22,7 @@ import {
|
|||||||
sections,
|
sections,
|
||||||
sharedControls,
|
sharedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
import { SingleValueType } from './SingleValueType';
|
||||||
|
|
||||||
const config: ControlPanelConfig = {
|
const config: ControlPanelConfig = {
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
@ -57,6 +58,16 @@ const config: ControlPanelConfig = {
|
|||||||
description: t('User must select a value for this filter.'),
|
description: t('User must select a value for this filter.'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'enableSingleValue',
|
||||||
|
config: {
|
||||||
|
type: 'CheckboxControl',
|
||||||
|
label: t('Single value'),
|
||||||
|
default: SingleValueType.Exact,
|
||||||
|
renderTrigger: true,
|
||||||
|
description: t('Use only a single value.'),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -54,12 +54,7 @@ describe('Filter utils', () => {
|
|||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
col: 'testCol',
|
col: 'testCol',
|
||||||
op: '>=',
|
op: '==',
|
||||||
val: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
col: 'testCol',
|
|
||||||
op: '<=',
|
|
||||||
val: 0,
|
val: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -60,12 +60,21 @@ export const getRangeExtraFormData = (
|
|||||||
upper?: number | null,
|
upper?: number | null,
|
||||||
) => {
|
) => {
|
||||||
const filters: QueryObjectFilterClause[] = [];
|
const filters: QueryObjectFilterClause[] = [];
|
||||||
if (lower !== undefined && lower !== null) {
|
if (lower !== undefined && lower !== null && lower !== upper) {
|
||||||
filters.push({ col, op: '>=', val: lower });
|
filters.push({ col, op: '>=', val: lower });
|
||||||
}
|
}
|
||||||
if (upper !== undefined && upper !== null) {
|
if (upper !== undefined && upper !== null && upper !== lower) {
|
||||||
filters.push({ col, op: '<=', val: upper });
|
filters.push({ col, op: '<=', val: upper });
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
upper !== undefined &&
|
||||||
|
upper !== null &&
|
||||||
|
lower !== undefined &&
|
||||||
|
lower !== null &&
|
||||||
|
upper === lower
|
||||||
|
) {
|
||||||
|
filters.push({ col, op: '==', val: upper });
|
||||||
|
}
|
||||||
|
|
||||||
return filters.length
|
return filters.length
|
||||||
? {
|
? {
|
||||||
|
Loading…
Reference in New Issue
Block a user