feat(filter): adding inputs to Numerical Range Filter (#31726)

Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
This commit is contained in:
Alexandru Soare
2025-02-28 16:42:24 +02:00
committed by GitHub
parent 789049d386
commit 4d6b4f8343
8 changed files with 406 additions and 281 deletions

View File

@@ -1,4 +1,5 @@
/**
*
* 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
@@ -192,23 +193,34 @@ describe('Native filters', () => {
testItems.filterNumericalColumn,
);
saveNativeFilterSettings([]);
// assertions
cy.get(nativeFilters.slider.slider).should('be.visible').click('center');
// Assertions
cy.get('[data-test="range-filter-from-input"]')
.should('be.visible')
.click();
cy.get('[data-test="range-filter-from-input"]').type('{selectall}5');
cy.get('[data-test="range-filter-to-input"]')
.should('be.visible')
.click();
cy.get('[data-test="range-filter-to-input"]').type('{selectall}50');
cy.get(nativeFilters.applyFilter).click();
// assert that the url contains 'native_filters' in the url
// Assert that the URL contains 'native_filters'
cy.url().then(u => {
const ur = new URL(u);
expect(ur.search).to.include('native_filters');
// assert that the start handle has a value
cy.get(nativeFilters.slider.startHandle)
.invoke('attr', 'aria-valuenow')
.should('exist');
// assert that the end handle has a value
cy.get(nativeFilters.slider.endHandle)
.invoke('attr', 'aria-valuenow')
.should('exist');
// assert slider text matches what we should have
cy.get(nativeFilters.slider.sliderText).should('have.text', '49');
cy.get('[data-test="range-filter-from-input"]')
.invoke('val')
.should('equal', '5');
// Assert that the "To" input has the correct value
cy.get('[data-test="range-filter-to-input"]')
.invoke('val')
.should('equal', '50');
});
});

View File

@@ -489,7 +489,7 @@ export function inputNativeFilterDefaultValue(
) {
if (!multiple) {
cy.contains('Filter has default value').click();
cy.contains('Default value is required').should('be.visible');
cy.contains('Please choose a valid value').should('be.visible');
cy.get(nativeFilters.modal.container).within(() => {
cy.get(
nativeFilters.filterConfigurationSections.filterPlaceholder,

View File

@@ -0,0 +1,47 @@
/**
* 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 { styled } from '@superset-ui/core';
const MetadataWrapper = styled.div`
display: flex;
width: 100%;
position: absolute;
left: 0;
top: 100%;
margin-top: ${({ theme }) => theme.gridUnit}px;
`;
const MetadataText = styled.span`
font-size: ${({ theme }) => theme.typography.sizes.xs}px;
color: ${({ theme }) => theme.colors.grayscale.light1};
font-weight: ${({ theme }) => theme.typography.weights.medium};
`;
export type MetadataProps = {
value: string;
};
const Metadata: React.FC<MetadataProps> = ({ value }) => (
<MetadataWrapper>
<MetadataText>{value}</MetadataText>
</MetadataWrapper>
);
export default Metadata;

View File

@@ -288,8 +288,8 @@ const FilterValue: FC<FilterControlProps> = ({
const filterState = useMemo(
() => ({
...filter.dataMask?.filterState,
validateStatus,
...filter.dataMask?.filterState,
}),
[filter.dataMask?.filterState, validateStatus],
);

View File

@@ -42,11 +42,19 @@ export const checkIsMissingRequiredValue = (
);
};
export const checkIsValidateError = (dataMask: DataMaskStateWithId) => {
const values = Object.values(dataMask);
return values.every(value => value.filterState?.validateStatus !== 'error');
};
export const checkIsApplyDisabled = (
dataMaskSelected: DataMaskStateWithId,
dataMaskApplied: DataMaskStateWithId,
filters: Filter[],
) => {
if (!checkIsValidateError(dataMaskSelected)) {
return true;
}
const dataSelectedValues = Object.values(dataMaskSelected);
const dataAppliedValues = Object.values(dataMaskApplied);
return (

View File

@@ -1275,7 +1275,7 @@ const FiltersConfigForm = (
return [...prevErroredFilters, filterId];
});
return Promise.reject(
new Error(t('Default value is required')),
new Error(t('Please choose a valid value')),
);
},
},

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { AppSection, GenericDataType } from '@superset-ui/core';
import { render, screen } from 'spec/helpers/testing-library';
import { fireEvent, render, screen } from 'spec/helpers/testing-library';
import RangeFilterPlugin from './RangeFilterPlugin';
import { SingleValueType } from './SingleValueType';
import transformProps from './transformProps';
@@ -83,14 +83,15 @@ const rangeProps = {
describe('RangeFilterPlugin', () => {
const setDataMask = jest.fn();
const getWrapper = (props = {}) =>
const getWrapper = (props: any = {}) =>
render(
// @ts-ignore
<RangeFilterPlugin
// @ts-ignore
{...transformProps({
...rangeProps,
formData: { ...rangeProps.formData, ...props },
...props,
formData: { ...rangeProps.formData, ...props.formData },
})}
setDataMask={setDataMask}
/>,
@@ -100,11 +101,50 @@ describe('RangeFilterPlugin', () => {
jest.clearAllMocks();
});
it('should render two numerical inputs', () => {
getWrapper();
const inputs = screen.getAllByRole('spinbutton');
expect(inputs).toHaveLength(2);
expect(inputs[0]).toHaveValue('10');
expect(inputs[1]).toHaveValue('70');
});
it('should set the data mask to error when the range is incorrect', () => {
getWrapper({ filterState: { value: [null, null] } });
const inputs = screen.getAllByRole('spinbutton');
const fromInput = inputs[0];
const toInput = inputs[1];
fireEvent.change(fromInput, { target: { value: 20 } });
fireEvent.change(toInput, { target: { value: 10 } });
fireEvent.blur(toInput);
expect(setDataMask).toHaveBeenCalledWith({
extraFormData: {},
filterState: {
label: '',
validateMessage: 'Please provide a valid range',
validateStatus: 'error',
value: null,
},
});
});
it('should call setDataMask with correct filter', () => {
getWrapper();
expect(setDataMask).toHaveBeenCalledWith({
extraFormData: {
filters: [
{
col: 'SP_POP_TOTL',
op: '>=',
val: 10,
},
{
col: 'SP_POP_TOTL',
op: '<=',
@@ -113,16 +153,21 @@ describe('RangeFilterPlugin', () => {
],
},
filterState: {
label: 'x ≤ 70',
label: '10 ≤ x ≤ 70',
value: [10, 70],
validateMessage: '',
validateStatus: undefined,
},
});
});
it('should call setDataMask with correct greater than filter', () => {
getWrapper({
enableSingleValue: SingleValueType.Minimum,
defaultValue: [20, 60],
filterState: { value: [20, null] },
formData: {
enableSingleValue: SingleValueType.Minimum,
defaultValue: undefined,
},
});
expect(setDataMask).toHaveBeenCalledWith({
extraFormData: {
@@ -135,17 +180,22 @@ describe('RangeFilterPlugin', () => {
],
},
filterState: {
validateStatus: undefined,
validateMessage: '',
label: 'x ≥ 20',
value: [20, 100],
value: [20, null],
},
});
expect(screen.getByRole('slider')).toHaveAttribute('aria-valuenow', '20');
const input = screen.getByRole('spinbutton');
expect(input).toHaveValue('20');
});
it('should call setDataMask with correct less than filter', () => {
getWrapper({
enableSingleValue: SingleValueType.Maximum,
defaultValue: [20, 60],
filterState: { value: [null, 60] },
formData: {
enableSingleValue: SingleValueType.Maximum,
},
});
expect(setDataMask).toHaveBeenCalledWith({
extraFormData: {
@@ -159,14 +209,22 @@ describe('RangeFilterPlugin', () => {
},
filterState: {
label: 'x ≤ 60',
value: [10, 60],
value: [null, 60],
validateMessage: '',
validateStatus: undefined,
},
});
expect(screen.getByRole('slider')).toHaveAttribute('aria-valuenow', '60');
const input = screen.getByRole('spinbutton');
expect(input).toHaveValue('60');
});
it('should call setDataMask with correct exact filter', () => {
getWrapper({ enableSingleValue: SingleValueType.Exact });
getWrapper({
formData: {
enableSingleValue: SingleValueType.Exact,
},
filterState: { value: [10, 10] },
});
expect(setDataMask).toHaveBeenCalledWith({
extraFormData: {
filters: [
@@ -180,6 +238,8 @@ describe('RangeFilterPlugin', () => {
filterState: {
label: 'x = 10',
value: [10, 10],
validateStatus: undefined,
validateMessage: '',
},
});
});

View File

@@ -20,120 +20,53 @@ import {
ensureIsArray,
getColumnLabel,
getNumberFormatter,
isEqualArray,
NumberFormats,
styled,
t,
} from '@superset-ui/core';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { rgba } from 'emotion-rgba';
import { AntdSlider } from 'src/components';
import { InputNumber } from 'src/components/Input';
import { FilterBarOrientation } from 'src/dashboard/types';
import Metadata from 'src/components/Metadata';
import { isNumber } from 'lodash';
import { PluginFilterRangeProps } from './types';
import { StatusMessage, StyledFormItem, FilterPluginStyle } from '../common';
import { getRangeExtraFormData } from '../../utils';
import { SingleValueType } from './SingleValueType';
const LIGHT_BLUE = '#99e7f0';
const DARK_BLUE = '#6dd3e3';
const LIGHT_GRAY = '#f5f5f5';
const DARK_GRAY = '#e1e1e1';
type InputValue = number | null;
type RangeValue = [InputValue, InputValue];
const StyledMinSlider = styled(AntdSlider)<{
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 StyledDivider = styled.span`
margin: 0 ${({ theme }) => theme.gridUnit * 3}px;
color: ${({ theme }) => theme.colors.grayscale.light1};
font-weight: ${({ theme }) => theme.typography.weights.bold};
font-size: ${({ theme }) => theme.typography.sizes.m}px;
align-content: center;
`;
const Wrapper = styled.div<{
validateStatus?: 'error' | 'warning' | 'info';
orientation?: FilterBarOrientation;
isOverflowing?: boolean;
}>`
${({ theme, validateStatus, orientation, isOverflowing }) => `
border: 1px solid transparent;
&:focus {
border: 1px solid
${theme.colors[validateStatus || 'primary']?.base};
outline: 0;
box-shadow: 0 0 0 3px
${rgba(theme.colors[validateStatus || 'primary']?.base, 0.2)};
}
& .ant-slider {
margin-top: ${
orientation === FilterBarOrientation.Horizontal ? 0 : theme.gridUnit
}px;
margin-bottom: ${
orientation === FilterBarOrientation.Horizontal ? 0 : theme.gridUnit * 5
}px;
const Wrapper = styled.div`
display: flex;
justify-content: space-between;
${
orientation === FilterBarOrientation.Horizontal &&
!isOverflowing &&
`line-height: 1.2;`
}
& .ant-slider-track {
background-color: ${
validateStatus && theme.colors[validateStatus]?.light1
};
}
& .ant-slider-handle {
border: ${
validateStatus && `2px solid ${theme.colors[validateStatus]?.light1}`
};
&:focus {
box-shadow: 0 0 0 3px
${rgba(theme.colors[validateStatus || 'primary']?.base, 0.2)};
}
}
& .ant-slider-mark {
font-size: ${theme.typography.sizes.s}px;
}
&:hover {
& .ant-slider-track {
background-color: ${
validateStatus && theme.colors[validateStatus]?.base
};
}
& .ant-slider-handle {
border: ${
validateStatus && `2px solid ${theme.colors[validateStatus]?.base}`
};
}
}
}
`}
.antd5-input-number {
width: 100%;
position: relative;
}
`;
const numberFormatter = getNumberFormatter(NumberFormats.SMART_NUMBER);
const tipFormatter = (value: number) => numberFormatter(value);
const getLabel = (lower: number | null, upper: number | null): string => {
if (lower !== null && upper !== null && lower === upper) {
const getLabel = (
lower: number | null,
upper: number | null,
enableSingleExactValue = false,
): string => {
if (
(enableSingleExactValue && lower !== null) ||
(lower !== null && lower === upper)
) {
return `x = ${numberFormatter(lower)}`;
}
if (lower !== null && upper !== null) {
@@ -148,18 +81,58 @@ const getLabel = (lower: number | null, upper: number | null): string => {
return '';
};
const getMarks = (
lower: number | null,
upper: number | null,
): { [key: number]: string } => {
const newMarks: { [key: number]: string } = {};
if (lower !== null) {
newMarks[lower] = numberFormatter(lower);
const validateRange = (
values: RangeValue,
min: number,
max: number,
enableEmptyFilter: boolean,
enableSingleValue?: SingleValueType,
): { isValid: boolean; errorMessage: string | null } => {
const [inputMin, inputMax] = values;
const requiredError = t('Filter value is required');
const rangeError = t('Please provide a value within range');
if (enableSingleValue !== undefined) {
const isSingleMin =
enableSingleValue === SingleValueType.Minimum ||
enableSingleValue === SingleValueType.Exact;
const value = isSingleMin ? inputMin : inputMax;
if (!value && enableEmptyFilter) {
return { isValid: false, errorMessage: requiredError };
}
if (isNumber(value) && (value < min || value > max)) {
return { isValid: false, errorMessage: rangeError };
}
return { isValid: true, errorMessage: null };
}
if (upper !== null) {
newMarks[upper] = numberFormatter(upper);
// Range validation
if (enableEmptyFilter && (inputMin === null || inputMax === null)) {
return { isValid: false, errorMessage: t('Please provide a valid range') };
}
return newMarks;
if (!enableEmptyFilter && (inputMin !== null) !== (inputMax !== null)) {
return { isValid: false, errorMessage: t('Please provide a valid range') };
}
if (inputMin !== null && inputMax !== null) {
if (inputMin > inputMax) {
return {
isValid: false,
errorMessage: t('Minimum value cannot be higher than maximum value'),
};
}
if (inputMin < min || inputMax > max) {
return {
isValid: false,
errorMessage: t('Your range is not within the dataset range'),
};
}
}
return { isValid: true, errorMessage: null };
};
export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
@@ -176,139 +149,191 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
setFilterActive,
filterState,
inputRef,
filterBarOrientation,
isOverflowingFilterBar,
filterBarOrientation = FilterBarOrientation.Vertical,
} = props;
const [row] = data;
// @ts-ignore
const { min, max }: { min: number; max: number } = row;
const { groupby, defaultValue, 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 [value, setValue] = useState<[number, number]>(
defaultValue ?? [min, enableSingleExactValue ? min : max],
);
const [marks, setMarks] = useState<{ [key: number]: string }>({});
const { groupby, enableSingleValue, enableEmptyFilter, defaultValue } =
formData;
const minIndex = 0;
const maxIndex = 1;
const minMax = value ?? [min, max];
const enableSingleExactValue = enableSingleValue === SingleValueType.Exact;
const rangeInput = enableSingleValue === undefined;
const getBounds = useCallback(
(
value: [number, number],
): { lower: number | null; upper: number | null } => {
const [lowerRaw, upperRaw] = value;
const [col = ''] = ensureIsArray(groupby).map(getColumnLabel);
if (enableSingleExactValue) {
return { lower: lowerRaw, upper: upperRaw };
}
return {
lower: lowerRaw > min ? lowerRaw : null,
upper: upperRaw < max ? upperRaw : null,
};
},
[max, min, enableSingleExactValue],
const [inputValue, setInputValue] = useState<RangeValue>(
filterState.value || defaultValue || [null, null],
);
const [error, setError] = useState<string | null>(null);
const handleAfterChange = useCallback(
(value: [number, number]): void => {
setValue(value);
const { lower, upper } = getBounds(value);
setMarks(getMarks(lower, upper));
const updateDataMaskError = useCallback(
(errorMessage: string | null) => {
setDataMask({
extraFormData: getRangeExtraFormData(col, lower, upper),
extraFormData: {},
filterState: {
value: lower !== null || upper !== null ? value : null,
label: getLabel(lower, upper),
value: null,
label: '',
validateStatus: 'error',
validateMessage: errorMessage || '',
},
});
},
[col, getBounds, setDataMask],
[setDataMask],
);
const handleChange = useCallback((value: [number, number]) => {
setValue(value);
}, []);
const updateDataMaskValue = useCallback(
(value: RangeValue) => {
const [inputMin, inputMax] = value;
setDataMask({
extraFormData: getRangeExtraFormData(col, inputMin, inputMax),
filterState: {
value: enableSingleExactValue
? [inputMin, inputMin]
: [inputMin, inputMax],
label: getLabel(inputMin, inputMax, enableSingleExactValue),
validateStatus: undefined,
validateMessage: '',
},
});
},
[setDataMask],
);
useEffect(() => {
// when switch filter type and queriesData still not updated we need ignore this case (in FilterBar)
if (row?.min === undefined && row?.max === undefined) {
return;
}
let filterStateValue = filterState.value ?? [min, max];
if (enableSingleMaxValue) {
const filterStateMax =
filterStateValue[maxIndex] <= minMax[maxIndex]
? filterStateValue[maxIndex]
: minMax[maxIndex];
if (
filterState.validateStatus === 'error' &&
error !== filterState.validateMessage
) {
setError(filterState.validateMessage);
filterStateValue = [min, filterStateMax];
} else if (enableSingleMinValue) {
const filterStateMin =
filterStateValue[minIndex] >= minMax[minIndex]
? filterStateValue[minIndex]
: minMax[minIndex];
const [inputMin, inputMax] = inputValue;
filterStateValue = [filterStateMin, max];
} else if (enableSingleExactValue) {
filterStateValue = [minMax[minIndex], minMax[minIndex]];
const { isValid, errorMessage } = validateRange(
inputValue,
min,
max,
enableEmptyFilter,
enableSingleValue,
);
const isDefaultError =
inputMin === null &&
inputMax === null &&
filterState.validateStatus === 'error';
if (!isValid || isDefaultError) {
setError(errorMessage);
updateDataMaskError(errorMessage);
return;
}
setError(null);
updateDataMaskValue(inputValue);
return;
}
if (filterState.validateStatus === 'error') {
setError(filterState.validateMessage);
return;
}
handleAfterChange(filterStateValue);
}, [
enableSingleMaxValue,
enableSingleMinValue,
enableSingleExactValue,
JSON.stringify(filterState.value),
JSON.stringify(data),
]);
// Clear all case
if (filterState.value === undefined && !filterState.validateStatus) {
setInputValue([null, null]);
updateDataMaskValue([null, null]);
return;
}
if (isEqualArray(defaultValue, inputValue)) {
updateDataMaskValue(defaultValue);
return;
}
// Filter state is pre-set case
if (filterState.value && !filterState.validateStatus) {
setInputValue(filterState.value);
updateDataMaskValue(filterState.value);
}
}, [JSON.stringify(filterState.value)]);
const metadataText = useMemo(() => {
switch (enableSingleValue) {
case SingleValueType.Minimum:
return t('Filters for values greater than or equal.');
case SingleValueType.Maximum:
return t('Filters for values less than or equal.');
case SingleValueType.Exact:
return t('Filters for values equal to this exact value.');
default:
return '';
}
}, [enableSingleValue]);
const handleChange = useCallback(
(newValue: number | null, index: 0 | 1) => {
if (row?.min === undefined && row?.max === undefined) {
return;
}
const newInputValue: [number | null, number | null] =
index === minIndex
? [newValue, inputValue[maxIndex]]
: [inputValue[minIndex], newValue];
setInputValue(newInputValue);
const { isValid, errorMessage } = validateRange(
newInputValue,
min,
max,
enableEmptyFilter,
enableSingleValue,
);
if (!isValid) {
setError(errorMessage);
updateDataMaskError(errorMessage);
return;
}
setError(null);
updateDataMaskValue(newInputValue);
},
[col, min, max, enableEmptyFilter, enableSingleValue, setDataMask],
);
const formItemExtra = useMemo(() => {
if (filterState.validateMessage) {
return (
<StatusMessage status={filterState.validateStatus}>
{filterState.validateMessage}
</StatusMessage>
);
if (error) {
return <StatusMessage status="error">{error}</StatusMessage>;
}
return undefined;
}, [filterState.validateMessage, filterState.validateStatus]);
}, [error]);
useEffect(() => {
if (enableSingleMaxValue) {
handleAfterChange([min, minMax[maxIndex]]);
switch (enableSingleValue) {
case SingleValueType.Minimum:
case SingleValueType.Exact:
handleChange(null, maxIndex);
break;
case SingleValueType.Maximum:
handleChange(null, minIndex);
break;
default:
break;
}
}, [enableSingleMaxValue]);
useEffect(() => {
if (enableSingleMinValue) {
handleAfterChange([minMax[minIndex], max]);
}
}, [enableSingleMinValue]);
useEffect(() => {
if (enableSingleExactValue) {
handleAfterChange([minMax[minIndex], minMax[minIndex]]);
}
}, [enableSingleExactValue]);
const MIN_NUM_STEPS = 20;
const stepHeuristic = (min: number, max: number) => {
const maxStepSize = (max - min) / MIN_NUM_STEPS;
// normalizedStepSize: .06 -> .01, .003 -> .001
const normalizedStepSize = `1E${Math.floor(Math.log10(maxStepSize))}`;
return Math.min(1, parseFloat(normalizedStepSize));
};
const step = max - min <= 1 ? stepHeuristic(min, max) : 1;
setDataMask({
extraFormData: {},
filterState: {
value: null,
label: '',
},
});
}, [enableSingleValue]);
return (
<FilterPluginStyle height={height} width={width}>
@@ -322,9 +347,6 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
<Wrapper
tabIndex={-1}
ref={inputRef}
validateStatus={filterState.validateStatus}
orientation={filterBarOrientation}
isOverflowing={isOverflowingFilterBar}
onFocus={setFocusedFilter}
onBlur={unsetFocusedFilter}
onMouseEnter={setHoveredFilter}
@@ -332,58 +354,34 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
onMouseDown={() => setFilterActive(true)}
onMouseUp={() => setFilterActive(false)}
>
{enableSingleMaxValue && (
<AntdSlider
min={min}
max={max}
step={step}
value={minMax[maxIndex]}
tipFormatter={tipFormatter}
marks={marks}
onAfterChange={value => handleAfterChange([min, value])}
onChange={value => handleChange([min, value])}
{(enableSingleValue === SingleValueType.Minimum ||
enableSingleValue === SingleValueType.Exact ||
enableSingleValue === undefined) && (
<InputNumber
value={inputValue[minIndex]}
onChange={val => handleChange(val, minIndex)}
placeholder={`${min}`}
status={filterState.validateStatus}
data-test="range-filter-from-input"
/>
)}
{enableSingleMinValue && (
<StyledMinSlider
validateStatus={filterState.validateStatus}
min={min}
max={max}
step={step}
value={minMax[minIndex]}
tipFormatter={tipFormatter}
marks={marks}
onAfterChange={value => handleAfterChange([value, max])}
onChange={value => handleChange([value, max])}
/>
{enableSingleValue === undefined && (
<StyledDivider>-</StyledDivider>
)}
{enableSingleExactValue && (
<AntdSlider
min={min}
max={max}
step={step}
included={false}
value={minMax[minIndex]}
tipFormatter={tipFormatter}
marks={marks}
onAfterChange={value => handleAfterChange([value, value])}
onChange={value => handleChange([value, value])}
/>
)}
{rangeValue && (
<AntdSlider
range
min={min}
max={max}
step={step}
value={minMax}
onAfterChange={handleAfterChange}
onChange={handleChange}
tipFormatter={tipFormatter}
marks={marks}
{(enableSingleValue === SingleValueType.Maximum ||
enableSingleValue === undefined) && (
<InputNumber
value={inputValue[maxIndex]}
onChange={val => handleChange(val, maxIndex)}
placeholder={`${max}`}
data-test="range-filter-to-input"
status={filterState.validateStatus}
/>
)}
</Wrapper>
{(rangeInput ||
filterBarOrientation === FilterBarOrientation.Vertical) &&
!filterState.validateStatus && <Metadata value={metadataText} />}
</StyledFormItem>
)}
</FilterPluginStyle>