feat(native-filters): Show/Highlight errored/focused status (#15276)
* fix:fix get permission function * feat: show error status for required filters * test: fix tests * refactor: fix CR notes
This commit is contained in:
parent
19486780a2
commit
048609d120
|
|
@ -58,6 +58,7 @@
|
|||
"d3-color": "^1.2.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"emotion-rgba": "0.0.9",
|
||||
"fontsource-fira-code": "^3.0.5",
|
||||
"fontsource-inter": "^3.0.5",
|
||||
"geolib": "^2.0.24",
|
||||
|
|
@ -25709,6 +25710,11 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/emotion-rgba": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.9.tgz",
|
||||
"integrity": "sha512-fSt51Lh4a1fppXY3nQrMUC00p1jIYMSaRRkUhPiOJ3s9oumae1tY41AJytRK9d4YmJDP9njJBndgdDn9j7CbsA=="
|
||||
},
|
||||
"node_modules/emotion-theming": {
|
||||
"version": "10.0.27",
|
||||
"resolved": "https://registry.npmjs.org/emotion-theming/-/emotion-theming-10.0.27.tgz",
|
||||
|
|
@ -76525,6 +76531,11 @@
|
|||
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
|
||||
"dev": true
|
||||
},
|
||||
"emotion-rgba": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.9.tgz",
|
||||
"integrity": "sha512-fSt51Lh4a1fppXY3nQrMUC00p1jIYMSaRRkUhPiOJ3s9oumae1tY41AJytRK9d4YmJDP9njJBndgdDn9j7CbsA=="
|
||||
},
|
||||
"emotion-theming": {
|
||||
"version": "10.0.27",
|
||||
"resolved": "https://registry.npmjs.org/emotion-theming/-/emotion-theming-10.0.27.tgz",
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@
|
|||
"d3-color": "^1.2.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"emotion-rgba": "0.0.9",
|
||||
"fontsource-fira-code": "^3.0.5",
|
||||
"fontsource-inter": "^3.0.5",
|
||||
"geolib": "^2.0.24",
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ const FilterValue: React.FC<FilterProps> = ({
|
|||
<Loading position="inline-centered" />
|
||||
) : (
|
||||
<SuperChart
|
||||
height={20}
|
||||
height={50}
|
||||
width="100%"
|
||||
formData={formData}
|
||||
// For charts that don't have datasource we need workaround for empty placeholder
|
||||
|
|
@ -203,7 +203,6 @@ const FilterValue: React.FC<FilterProps> = ({
|
|||
filterState={{
|
||||
...filter.dataMask?.filterState,
|
||||
validateMessage: isMissingRequiredValue && t('Value is required'),
|
||||
validateStatus: isMissingRequiredValue && 'error',
|
||||
}}
|
||||
ownState={filter.dataMask?.ownState}
|
||||
enableNoResults={metadata?.enableNoResults}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
SetDataMaskHook,
|
||||
SuperChart,
|
||||
AppSection,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { FormInstance } from 'antd/lib/form';
|
||||
import Loading from 'src/components/Loading';
|
||||
|
|
@ -56,7 +57,10 @@ const DefaultValue: FC<DefaultValueProps> = ({
|
|||
setLoading(true);
|
||||
}
|
||||
}, [hasDataset, queriesData]);
|
||||
|
||||
const value = formFilter.defaultDataMask?.filterState.value;
|
||||
const isMissingRequiredValue =
|
||||
(value === null || value === undefined) &&
|
||||
formFilter?.controlValues?.enableEmptyFilter;
|
||||
return loading ? (
|
||||
<Loading position="inline-centered" />
|
||||
) : (
|
||||
|
|
@ -73,7 +77,10 @@ const DefaultValue: FC<DefaultValueProps> = ({
|
|||
chartType={formFilter?.filterType}
|
||||
hooks={{ setDataMask }}
|
||||
enableNoResults={enableNoResults}
|
||||
filterState={formFilter.defaultDataMask?.filterState}
|
||||
filterState={{
|
||||
...formFilter.defaultDataMask?.filterState,
|
||||
validateMessage: isMissingRequiredValue && t('Value is required'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -749,9 +749,7 @@ const FiltersConfigForm = (
|
|||
if (hasValue) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(t('Default value is required')),
|
||||
);
|
||||
return Promise.reject();
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -191,13 +191,13 @@ test('renders a numerical range filter type', () => {
|
|||
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(DATASET_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(COLUMN_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(REQUIRED_REGEX)).toBeInTheDocument();
|
||||
|
||||
expect(getCheckbox(DEFAULT_VALUE_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(APPLY_INSTANTLY_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(PRE_FILTER_REGEX)).not.toBeChecked();
|
||||
|
||||
expect(queryCheckbox(MULTIPLE_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(REQUIRED_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(HIERARCHICAL_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(FIRST_ITEM_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(INVERSE_SELECTION_REGEX)).not.toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -16,14 +16,19 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ensureIsArray, ExtraFormData, t, tn } from '@superset-ui/core';
|
||||
import { ensureIsArray, ExtraFormData, styled, t, tn } from '@superset-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Select } from 'src/common/components';
|
||||
import { Styles, StyledSelect } from '../common';
|
||||
import { PluginFilterGroupByProps } from './types';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Error = styled.div`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
`;
|
||||
|
||||
export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
|
||||
const {
|
||||
data,
|
||||
|
|
@ -70,28 +75,36 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
|
|||
: tn('%s option', '%s options', columns.length, columns.length);
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
value={value}
|
||||
placeholder={placeholderText}
|
||||
mode={multiSelect ? 'multiple' : undefined}
|
||||
// @ts-ignore
|
||||
onChange={handleChange}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onFocus={setFocusedFilter}
|
||||
ref={inputRef}
|
||||
<FormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
{columns.map(
|
||||
(row: { column_name: string; verbose_name: string | null }) => {
|
||||
const { column_name: columnName, verbose_name: verboseName } = row;
|
||||
return (
|
||||
<Option key={columnName} value={columnName}>
|
||||
{verboseName ?? columnName}
|
||||
</Option>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</StyledSelect>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
value={value}
|
||||
placeholder={placeholderText}
|
||||
mode={multiSelect ? 'multiple' : undefined}
|
||||
// @ts-ignore
|
||||
onChange={handleChange}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onFocus={setFocusedFilter}
|
||||
ref={inputRef}
|
||||
>
|
||||
{columns.map(
|
||||
(row: { column_name: string; verbose_name: string | null }) => {
|
||||
const {
|
||||
column_name: columnName,
|
||||
verbose_name: verboseName,
|
||||
} = row;
|
||||
return (
|
||||
<Option key={columnName} value={columnName}>
|
||||
{verboseName ?? columnName}
|
||||
</Option>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</StyledSelect>
|
||||
</FormItem>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,18 @@ const config: ControlPanelConfig = {
|
|||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'enableEmptyFilter',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Required'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t('User must select a value for this filter.'),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -16,12 +16,61 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { getNumberFormatter, NumberFormats, t } from '@superset-ui/core';
|
||||
import {
|
||||
getNumberFormatter,
|
||||
NumberFormats,
|
||||
styled,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Slider } from 'src/common/components';
|
||||
import { rgba } from 'emotion-rgba';
|
||||
import { PluginFilterRangeProps } from './types';
|
||||
import { Styles } from '../common';
|
||||
import { getRangeExtraFormData } from '../../utils';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const Error = styled.div`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div<{ validateStatus?: string }>`
|
||||
border: 1px solid transparent;
|
||||
&:focus {
|
||||
border: 1px solid
|
||||
${({ theme, validateStatus }) =>
|
||||
theme.colors[validateStatus ? 'error' : 'primary'].base};
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 3px
|
||||
${({ theme, validateStatus }) =>
|
||||
rgba(theme.colors[validateStatus ? 'error' : 'primary'].base, 0.2)};
|
||||
}
|
||||
& .ant-slider {
|
||||
& .ant-slider-track {
|
||||
background-color: ${({ theme, validateStatus }) =>
|
||||
validateStatus && theme.colors.error.light1};
|
||||
}
|
||||
& .ant-slider-handle {
|
||||
border: ${({ theme, validateStatus }) =>
|
||||
validateStatus && `2px solid ${theme.colors.error.light1}`};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px
|
||||
${({ theme, validateStatus }) =>
|
||||
rgba(theme.colors[validateStatus ? 'error' : 'primary'].base, 0.2)};
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
& .ant-slider-track {
|
||||
background-color: ${({ theme, validateStatus }) =>
|
||||
validateStatus && theme.colors.error.base};
|
||||
}
|
||||
& .ant-slider-handle {
|
||||
border: ${({ theme, validateStatus }) =>
|
||||
validateStatus && `2px solid ${theme.colors.error.base}`};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
||||
const {
|
||||
|
|
@ -32,7 +81,6 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||
setDataMask,
|
||||
setFocusedFilter,
|
||||
unsetFocusedFilter,
|
||||
inputRef,
|
||||
filterState,
|
||||
} = props;
|
||||
const numberFormatter = getNumberFormatter(NumberFormats.SMART_NUMBER);
|
||||
|
|
@ -40,7 +88,7 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||
const [row] = data;
|
||||
// @ts-ignore
|
||||
const { min, max }: { min: number; max: number } = row;
|
||||
const { groupby, defaultValue } = formData;
|
||||
const { groupby, defaultValue, inputRef } = formData;
|
||||
const [col = ''] = groupby || [];
|
||||
const [value, setValue] = useState<[number, number]>(
|
||||
defaultValue ?? [min, max],
|
||||
|
|
@ -111,19 +159,31 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
|||
{Number.isNaN(Number(min)) || Number.isNaN(Number(max)) ? (
|
||||
<h4>{t('Chosen non-numeric column')}</h4>
|
||||
) : (
|
||||
<div onMouseEnter={setFocusedFilter} onMouseLeave={unsetFocusedFilter}>
|
||||
<Slider
|
||||
range
|
||||
min={min}
|
||||
max={max}
|
||||
value={value ?? [min, max]}
|
||||
onAfterChange={handleAfterChange}
|
||||
onChange={handleChange}
|
||||
tipFormatter={value => numberFormatter(value)}
|
||||
<FormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
<Wrapper
|
||||
tabIndex={-1}
|
||||
ref={inputRef}
|
||||
marks={marks}
|
||||
/>
|
||||
</div>
|
||||
validateStatus={filterState.validateMessage}
|
||||
onFocus={setFocusedFilter}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onMouseEnter={setFocusedFilter}
|
||||
onMouseLeave={unsetFocusedFilter}
|
||||
>
|
||||
<Slider
|
||||
range
|
||||
min={min}
|
||||
max={max}
|
||||
value={value ?? [min, max]}
|
||||
onAfterChange={handleAfterChange}
|
||||
onChange={handleChange}
|
||||
tipFormatter={value => numberFormatter(value)}
|
||||
marks={marks}
|
||||
/>
|
||||
</Wrapper>
|
||||
</FormItem>
|
||||
)}
|
||||
</Styles>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,24 @@ const config: ControlPanelConfig = {
|
|||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('UI Configuration'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
name: 'enableEmptyFilter',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Required'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t('User must select a value for this filter.'),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
|
|||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<FormItem
|
||||
validateStatus={filterState.validateStatus}
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
<StyledSelect
|
||||
|
|
|
|||
|
|
@ -27,8 +27,27 @@ const TimeFilterStyles = styled(Styles)`
|
|||
overflow-x: auto;
|
||||
`;
|
||||
|
||||
const ControlContainer = styled.div`
|
||||
display: inline-block;
|
||||
const ControlContainer = styled.div<{ validateStatus?: string }>`
|
||||
padding: 2px;
|
||||
& > span {
|
||||
border: 2px solid transparent;
|
||||
display: inline-block;
|
||||
border: ${({ theme, validateStatus }) =>
|
||||
validateStatus && `2px solid ${theme.colors.error.base}`};
|
||||
}
|
||||
&:focus {
|
||||
& > span {
|
||||
border: 2px solid
|
||||
${({ theme, validateStatus }) =>
|
||||
validateStatus ? theme.colors.error.base : theme.colors.primary.base};
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 2px
|
||||
${({ validateStatus }) =>
|
||||
validateStatus
|
||||
? 'rgba(224, 67, 85, 12%)'
|
||||
: 'rgba(32, 167, 201, 0.2)'};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
|
||||
|
|
@ -37,7 +56,9 @@ export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
|
|||
setFocusedFilter,
|
||||
unsetFocusedFilter,
|
||||
width,
|
||||
height,
|
||||
filterState,
|
||||
formData: { inputRef },
|
||||
} = props;
|
||||
|
||||
const handleTimeRangeChange = (timeRange?: string): void => {
|
||||
|
|
@ -60,8 +81,13 @@ export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
|
|||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<TimeFilterStyles width={width}>
|
||||
<TimeFilterStyles width={width} height={height}>
|
||||
<ControlContainer
|
||||
tabIndex={-1}
|
||||
ref={inputRef}
|
||||
validateStatus={filterState.validateMessage}
|
||||
onFocus={setFocusedFilter}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onMouseEnter={setFocusedFilter}
|
||||
onMouseLeave={unsetFocusedFilter}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,30 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { ControlPanelConfig } from '@superset-ui/chart-controls';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
const config: ControlPanelConfig = {
|
||||
// For control input types, see: superset-frontend/src/explore/components/controls/index.js
|
||||
controlPanelSections: [],
|
||||
controlPanelSections: [
|
||||
{
|
||||
label: t('UI Configuration'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
name: 'enableEmptyFilter',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Required'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t('User must select a value for this filter.'),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
ensureIsArray,
|
||||
ExtraFormData,
|
||||
GenericDataType,
|
||||
styled,
|
||||
t,
|
||||
tn,
|
||||
} from '@superset-ui/core';
|
||||
|
|
@ -27,9 +28,14 @@ import React, { useEffect, useState } from 'react';
|
|||
import { Select } from 'src/common/components';
|
||||
import { Styles, StyledSelect } from '../common';
|
||||
import { PluginFilterTimeColumnProps } from './types';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Error = styled.div`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
`;
|
||||
|
||||
export default function PluginFilterTimeColumn(
|
||||
props: PluginFilterTimeColumnProps,
|
||||
) {
|
||||
|
|
@ -83,27 +89,35 @@ export default function PluginFilterTimeColumn(
|
|||
: tn('%s option', '%s options', timeColumns.length, timeColumns.length);
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
value={value}
|
||||
placeholder={placeholderText}
|
||||
// @ts-ignore
|
||||
onChange={handleChange}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onFocus={setFocusedFilter}
|
||||
ref={inputRef}
|
||||
<FormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
{timeColumns.map(
|
||||
(row: { column_name: string; verbose_name: string | null }) => {
|
||||
const { column_name: columnName, verbose_name: verboseName } = row;
|
||||
return (
|
||||
<Option key={columnName} value={columnName}>
|
||||
{verboseName ?? columnName}
|
||||
</Option>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</StyledSelect>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
value={value}
|
||||
placeholder={placeholderText}
|
||||
// @ts-ignore
|
||||
onChange={handleChange}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onFocus={setFocusedFilter}
|
||||
ref={inputRef}
|
||||
>
|
||||
{timeColumns.map(
|
||||
(row: { column_name: string; verbose_name: string | null }) => {
|
||||
const {
|
||||
column_name: columnName,
|
||||
verbose_name: verboseName,
|
||||
} = row;
|
||||
return (
|
||||
<Option key={columnName} value={columnName}>
|
||||
{verboseName ?? columnName}
|
||||
</Option>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</StyledSelect>
|
||||
</FormItem>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,29 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { ControlPanelConfig } from '@superset-ui/chart-controls';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
const config: ControlPanelConfig = {
|
||||
controlPanelSections: [],
|
||||
controlPanelSections: [
|
||||
{
|
||||
label: t('UI Configuration'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
name: 'enableEmptyFilter',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Required'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t('User must select a value for this filter.'),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import {
|
||||
ensureIsArray,
|
||||
ExtraFormData,
|
||||
styled,
|
||||
t,
|
||||
TimeGranularity,
|
||||
tn,
|
||||
|
|
@ -27,9 +28,14 @@ import React, { useEffect, useState } from 'react';
|
|||
import { Select } from 'src/common/components';
|
||||
import { Styles, StyledSelect } from '../common';
|
||||
import { PluginFilterTimeGrainProps } from './types';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Error = styled.div`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
`;
|
||||
|
||||
export default function PluginFilterTimegrain(
|
||||
props: PluginFilterTimeGrainProps,
|
||||
) {
|
||||
|
|
@ -80,25 +86,30 @@ export default function PluginFilterTimegrain(
|
|||
: tn('%s option', '%s options', data.length, data.length);
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
value={value}
|
||||
placeholder={placeholderText}
|
||||
// @ts-ignore
|
||||
onChange={handleChange}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onFocus={setFocusedFilter}
|
||||
ref={inputRef}
|
||||
<FormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
{(data || []).map((row: { name: string; duration: string }) => {
|
||||
const { name, duration } = row;
|
||||
return (
|
||||
<Option key={duration} value={duration}>
|
||||
{name}
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</StyledSelect>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
value={value}
|
||||
placeholder={placeholderText}
|
||||
// @ts-ignore
|
||||
onChange={handleChange}
|
||||
onBlur={unsetFocusedFilter}
|
||||
onFocus={setFocusedFilter}
|
||||
ref={inputRef}
|
||||
>
|
||||
{(data || []).map((row: { name: string; duration: string }) => {
|
||||
const { name, duration } = row;
|
||||
return (
|
||||
<Option key={duration} value={duration}>
|
||||
{name}
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</StyledSelect>
|
||||
</FormItem>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,29 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { ControlPanelConfig } from '@superset-ui/chart-controls';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
const config: ControlPanelConfig = {
|
||||
controlPanelSections: [],
|
||||
controlPanelSections: [
|
||||
{
|
||||
label: t('UI Configuration'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
name: 'enableEmptyFilter',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Required'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t('User must select a value for this filter.'),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
Loading…
Reference in New Issue