fix(native-filters): Fix native filters config modal (#15506)

* fix:fix get permission function

* fix: native filters

* fix: remove unneccesary space fo filters / fix some crashes
This commit is contained in:
simcha90 2021-07-04 11:26:59 +03:00 committed by GitHub
parent dd16468d44
commit 2cb13e695e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 120 additions and 91 deletions

View File

@ -132,7 +132,7 @@ const FilterFocusHighlight = React.forwardRef(
if (focusedNativeFilterId) { if (focusedNativeFilterId) {
if ( if (
nativeFilters.filters[focusedNativeFilterId].chartsInScope?.includes( nativeFilters.filters[focusedNativeFilterId]?.chartsInScope?.includes(
chartId, chartId,
) )
) { ) {

View File

@ -44,6 +44,7 @@ import { FilterProps } from './types';
import { getFormData } from '../../utils'; import { getFormData } from '../../utils';
import { useCascadingFilters } from './state'; import { useCascadingFilters } from './state';
import { usePreselectNativeFilter } from '../../state'; import { usePreselectNativeFilter } from '../../state';
import { checkIsMissingRequiredValue } from '../utils';
const HEIGHT = 32; const HEIGHT = 32;
@ -197,7 +198,14 @@ const FilterValue: React.FC<FilterProps> = ({
/> />
); );
} }
const filterState = { ...filter.dataMask?.filterState }; const isMissingRequiredValue = checkIsMissingRequiredValue(
filter,
filter.dataMask?.filterState,
);
const filterState = {
...filter.dataMask?.filterState,
validateMessage: isMissingRequiredValue && t('Value is required'),
};
if (filterState.value === undefined && preselection) { if (filterState.value === undefined && preselection) {
filterState.value = preselection; filterState.value = preselection;
} }

View File

@ -22,6 +22,7 @@ import {
SetDataMaskHook, SetDataMaskHook,
SuperChart, SuperChart,
AppSection, AppSection,
t,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { FormInstance } from 'antd/lib/form'; import { FormInstance } from 'antd/lib/form';
import Loading from 'src/components/Loading'; import Loading from 'src/components/Loading';
@ -56,6 +57,10 @@ const DefaultValue: FC<DefaultValueProps> = ({
setLoading(true); setLoading(true);
} }
}, [hasDataset, queriesData]); }, [hasDataset, queriesData]);
const value = formFilter.defaultDataMask?.filterState.value;
const isMissingRequiredValue =
(value === null || value === undefined) &&
formFilter?.controlValues?.enableEmptyFilter;
return loading ? ( return loading ? (
<Loading position="inline-centered" /> <Loading position="inline-centered" />
) : ( ) : (
@ -74,6 +79,7 @@ const DefaultValue: FC<DefaultValueProps> = ({
enableNoResults={enableNoResults} enableNoResults={enableNoResults}
filterState={{ filterState={{
...formFilter.defaultDataMask?.filterState, ...formFilter.defaultDataMask?.filterState,
validateMessage: isMissingRequiredValue && t('Value is required'),
}} }}
/> />
); );

View File

@ -345,13 +345,27 @@ const FiltersConfigForm = (
const hasDataset = !!nativeFilterItems[formFilter?.filterType]?.value const hasDataset = !!nativeFilterItems[formFilter?.filterType]?.value
?.datasourceCount; ?.datasourceCount;
const { controlItems = {}, mainControlItems = {} } = formFilter
? getControlItemsMap({
disabled: false,
forceUpdate,
form,
filterId,
filterType: formFilter.filterType,
filterToEdit,
formFilter,
removed,
})
: {};
const hasColumn = !!mainControlItems.groupby;
const nativeFilterItem = nativeFilterItems[formFilter?.filterType] ?? {}; const nativeFilterItem = nativeFilterItems[formFilter?.filterType] ?? {};
// @ts-ignore // @ts-ignore
const enableNoResults = !!nativeFilterItem.value?.enableNoResults; const enableNoResults = !!nativeFilterItem.value?.enableNoResults;
const datasetId = formFilter?.dataset?.value; const datasetId = formFilter?.dataset?.value;
useEffect(() => { useEffect(() => {
if (datasetId && hasDataset) { if (datasetId && hasColumn) {
cachedSupersetGet({ cachedSupersetGet({
endpoint: `/api/v1/dataset/${datasetId}`, endpoint: `/api/v1/dataset/${datasetId}`,
}) })
@ -367,7 +381,7 @@ const FiltersConfigForm = (
addDangerToast(response.message); addDangerToast(response.message);
}); });
} }
}, [datasetId, hasDataset]); }, [datasetId, hasColumn]);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeTab(tab: 'configuration' | 'scoping') { changeTab(tab: 'configuration' | 'scoping') {
@ -375,10 +389,10 @@ const FiltersConfigForm = (
}, },
})); }));
const hasMetrics = hasDataset && !!metrics.length; const hasMetrics = hasColumn && !!metrics.length;
const hasFilledDataset = const hasFilledDataset =
!hasDataset || (datasetId && (formFilter?.column || !hasDataset)); !hasDataset || (datasetId && (formFilter?.column || !hasColumn));
const hasAdditionalFilters = FILTERS_WITH_ADHOC_FILTERS.includes( const hasAdditionalFilters = FILTERS_WITH_ADHOC_FILTERS.includes(
formFilter?.filterType, formFilter?.filterType,
@ -477,7 +491,7 @@ const FiltersConfigForm = (
: undefined); : undefined);
const newFormData = getFormData({ const newFormData = getFormData({
datasetId, datasetId,
groupby: hasDataset ? formFilter?.column : undefined, groupby: hasColumn ? formFilter?.column : undefined,
...formFilter, ...formFilter,
}); });
@ -534,20 +548,10 @@ const FiltersConfigForm = (
const hasSorting = const hasSorting =
typeof filterToEdit?.controlValues?.sortAscending === 'boolean'; typeof filterToEdit?.controlValues?.sortAscending === 'boolean';
const showDefaultValue = !hasDataset || (!isDataDirty && hasFilledDataset); const showDefaultValue =
!hasDataset ||
const { controlItems = {}, mainControlItems = {} } = formFilter (!isDataDirty && hasFilledDataset) ||
? getControlItemsMap({ !mainControlItems.groupby;
disabled: false,
forceUpdate,
form,
filterId,
filterType: formFilter.filterType,
filterToEdit,
formFilter,
removed,
})
: {};
const onSortChanged = (value: boolean | undefined) => { const onSortChanged = (value: boolean | undefined) => {
const previous = form.getFieldValue('filters')?.[filterId].controlValues; const previous = form.getFieldValue('filters')?.[filterId].controlValues;
@ -683,6 +687,7 @@ const FiltersConfigForm = (
setNativeFilterFieldValues(form, filterId, { setNativeFilterFieldValues(form, filterId, {
filterType: value, filterType: value,
defaultDataMask: null, defaultDataMask: null,
column: null,
}); });
forceUpdate(); forceUpdate();
}} }}
@ -738,13 +743,6 @@ const FiltersConfigForm = (
header={FilterPanels.basic.name} header={FilterPanels.basic.name}
key={FilterPanels.basic.key} key={FilterPanels.basic.key}
> >
{hasFilledDataset && (
<CleanFormItem
name={['filters', filterId, 'defaultValueFormData']}
hidden
initialValue={newFormData}
/>
)}
<CleanFormItem <CleanFormItem
name={['filters', filterId, 'defaultValueQueriesData']} name={['filters', filterId, 'defaultValueQueriesData']}
hidden hidden
@ -760,7 +758,11 @@ const FiltersConfigForm = (
> >
<StyledRowSubFormItem <StyledRowSubFormItem
name={['filters', filterId, 'defaultDataMask']} name={['filters', filterId, 'defaultDataMask']}
initialValue={filterToEdit?.defaultDataMask} initialValue={
formFilter.filterType === filterToEdit?.filterType
? filterToEdit?.defaultDataMask
: null
}
data-test="default-input" data-test="default-input"
label={<StyledLabel>{t('Default Value')}</StyledLabel>} label={<StyledLabel>{t('Default Value')}</StyledLabel>}
required={hasDefaultValue} required={hasDefaultValue}

View File

@ -19,9 +19,8 @@
import { ensureIsArray, ExtraFormData, styled, t, tn } from '@superset-ui/core'; import { ensureIsArray, ExtraFormData, styled, t, tn } from '@superset-ui/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Select } from 'src/common/components'; import { Select } from 'src/common/components';
import { Styles, StyledSelect } from '../common'; import { Styles, StyledSelect, StyledFormItem } from '../common';
import { PluginFilterGroupByProps } from './types'; import { PluginFilterGroupByProps } from './types';
import FormItem from '../../../components/Form/FormItem';
const { Option } = Select; const { Option } = Select;
@ -87,7 +86,7 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
: tn('%s option', '%s options', columns.length, columns.length); : tn('%s option', '%s options', columns.length, columns.length);
return ( return (
<Styles height={height} width={width}> <Styles height={height} width={width}>
<FormItem <StyledFormItem
validateStatus={filterState.validateMessage && 'error'} validateStatus={filterState.validateMessage && 'error'}
extra={<Error>{filterState.validateMessage}</Error>} extra={<Error>{filterState.validateMessage}</Error>}
> >
@ -116,7 +115,7 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
}, },
)} )}
</StyledSelect> </StyledSelect>
</FormItem> </StyledFormItem>
</Styles> </Styles>
); );
} }

View File

@ -26,9 +26,8 @@ import React, { useEffect, useState } from 'react';
import { Slider } from 'src/common/components'; import { Slider } from 'src/common/components';
import { rgba } from 'emotion-rgba'; import { rgba } from 'emotion-rgba';
import { PluginFilterRangeProps } from './types'; import { PluginFilterRangeProps } from './types';
import { Styles } from '../common'; import { StyledFormItem, Styles } from '../common';
import { getRangeExtraFormData } from '../../utils'; import { getRangeExtraFormData } from '../../utils';
import FormItem from '../../../components/Form/FormItem';
const Error = styled.div` const Error = styled.div`
color: ${({ theme }) => theme.colors.error.base}; color: ${({ theme }) => theme.colors.error.base};
@ -159,7 +158,7 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
{Number.isNaN(Number(min)) || Number.isNaN(Number(max)) ? ( {Number.isNaN(Number(min)) || Number.isNaN(Number(max)) ? (
<h4>{t('Chosen non-numeric column')}</h4> <h4>{t('Chosen non-numeric column')}</h4>
) : ( ) : (
<FormItem <StyledFormItem
validateStatus={filterState.validateMessage && 'error'} validateStatus={filterState.validateMessage && 'error'}
extra={<Error>{filterState.validateMessage}</Error>} extra={<Error>{filterState.validateMessage}</Error>}
> >
@ -183,7 +182,7 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
marks={marks} marks={marks}
/> />
</Wrapper> </Wrapper>
</FormItem> </StyledFormItem>
)} )}
</Styles> </Styles>
); );

View File

@ -26,6 +26,7 @@ import {
GenericDataType, GenericDataType,
JsonObject, JsonObject,
smartDateDetailedFormatter, smartDateDetailedFormatter,
styled,
t, t,
tn, tn,
} from '@superset-ui/core'; } from '@superset-ui/core';
@ -44,11 +45,15 @@ import { useImmerReducer } from 'use-immer';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import { usePrevious } from 'src/common/hooks/usePrevious'; import { usePrevious } from 'src/common/hooks/usePrevious';
import { PluginFilterSelectProps, SelectValue } from './types'; import { PluginFilterSelectProps, SelectValue } from './types';
import { StyledSelect, Styles } from '../common'; import { StyledFormItem, StyledSelect, Styles } from '../common';
import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils'; import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils';
const { Option } = Select; const { Option } = Select;
const Error = styled.div`
color: ${({ theme }) => theme.colors.error.base};
`;
type DataMaskAction = type DataMaskAction =
| { type: 'ownState'; ownState: JsonObject } | { type: 'ownState'; ownState: JsonObject }
| { | {
@ -273,52 +278,57 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
return ( return (
<Styles height={height} width={width}> <Styles height={height} width={width}>
<StyledSelect <StyledFormItem
allowClear validateStatus={filterState.validateMessage && 'error'}
// @ts-ignore extra={<Error>{filterState.validateMessage}</Error>}
value={filterState.value || []}
disabled={isDisabled}
showSearch={showSearch}
mode={multiSelect ? 'multiple' : undefined}
placeholder={placeholderText}
onSearch={searchWrapper}
onSelect={clearSuggestionSearch}
onBlur={handleBlur}
onDropdownVisibleChange={setIsDropdownVisible}
dropdownRender={(
originNode: ReactElement & { ref?: RefObject<HTMLElement> },
) => {
if (isDropdownVisible && !wasDropdownVisible) {
originNode.ref?.current?.scrollTo({ top: 0 });
}
return originNode;
}}
onFocus={setFocusedFilter}
// @ts-ignore
onChange={handleChange}
ref={inputRef}
loading={isRefreshing}
maxTagCount={5}
menuItemSelectedIcon={<Icon iconSize="m" />}
> >
{sortedData.map(row => { <StyledSelect
const [value] = groupby.map(col => row[col]); allowClear
return ( // @ts-ignore
// @ts-ignore value={filterState.value || []}
<Option key={`${value}`} value={value}> disabled={isDisabled}
{labelFormatter(value, datatype)} showSearch={showSearch}
</Option> mode={multiSelect ? 'multiple' : undefined}
); placeholder={placeholderText}
})} onSearch={searchWrapper}
{currentSuggestionSearch && onSelect={clearSuggestionSearch}
!ensureIsArray(filterState.value).some( onBlur={handleBlur}
suggestion => suggestion === currentSuggestionSearch, onDropdownVisibleChange={setIsDropdownVisible}
) && ( dropdownRender={(
<Option value={currentSuggestionSearch}> originNode: ReactElement & { ref?: RefObject<HTMLElement> },
{`${t('Create "%s"', currentSuggestionSearch)}`} ) => {
</Option> if (isDropdownVisible && !wasDropdownVisible) {
)} originNode.ref?.current?.scrollTo({ top: 0 });
</StyledSelect> }
return originNode;
}}
onFocus={setFocusedFilter}
// @ts-ignore
onChange={handleChange}
ref={inputRef}
loading={isRefreshing}
maxTagCount={5}
menuItemSelectedIcon={<Icon iconSize="m" />}
>
{sortedData.map(row => {
const [value] = groupby.map(col => row[col]);
return (
// @ts-ignore
<Option key={`${value}`} value={value}>
{labelFormatter(value, datatype)}
</Option>
);
})}
{currentSuggestionSearch &&
!ensureIsArray(filterState.value).some(
suggestion => suggestion === currentSuggestionSearch,
) && (
<Option value={currentSuggestionSearch}>
{`${t('Create "%s"', currentSuggestionSearch)}`}
</Option>
)}
</StyledSelect>
</StyledFormItem>
</Styles> </Styles>
); );
} }

View File

@ -26,9 +26,8 @@ import {
} from '@superset-ui/core'; } from '@superset-ui/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Select } from 'src/common/components'; import { Select } from 'src/common/components';
import { Styles, StyledSelect } from '../common'; import { Styles, StyledSelect, StyledFormItem } from '../common';
import { PluginFilterTimeColumnProps } from './types'; import { PluginFilterTimeColumnProps } from './types';
import FormItem from '../../../components/Form/FormItem';
const { Option } = Select; const { Option } = Select;
@ -89,7 +88,7 @@ export default function PluginFilterTimeColumn(
: tn('%s option', '%s options', timeColumns.length, timeColumns.length); : tn('%s option', '%s options', timeColumns.length, timeColumns.length);
return ( return (
<Styles height={height} width={width}> <Styles height={height} width={width}>
<FormItem <StyledFormItem
validateStatus={filterState.validateMessage && 'error'} validateStatus={filterState.validateMessage && 'error'}
extra={<Error>{filterState.validateMessage}</Error>} extra={<Error>{filterState.validateMessage}</Error>}
> >
@ -117,7 +116,7 @@ export default function PluginFilterTimeColumn(
}, },
)} )}
</StyledSelect> </StyledSelect>
</FormItem> </StyledFormItem>
</Styles> </Styles>
); );
} }

View File

@ -26,9 +26,8 @@ import {
} from '@superset-ui/core'; } from '@superset-ui/core';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { Select } from 'src/common/components'; import { Select } from 'src/common/components';
import { Styles, StyledSelect } from '../common'; import { Styles, StyledSelect, StyledFormItem } from '../common';
import { PluginFilterTimeGrainProps } from './types'; import { PluginFilterTimeGrainProps } from './types';
import FormItem from '../../../components/Form/FormItem';
const { Option } = Select; const { Option } = Select;
@ -99,7 +98,7 @@ export default function PluginFilterTimegrain(
: tn('%s option', '%s options', data.length, data.length); : tn('%s option', '%s options', data.length, data.length);
return ( return (
<Styles height={height} width={width}> <Styles height={height} width={width}>
<FormItem <StyledFormItem
validateStatus={filterState.validateMessage && 'error'} validateStatus={filterState.validateMessage && 'error'}
extra={<Error>{filterState.validateMessage}</Error>} extra={<Error>{filterState.validateMessage}</Error>}
> >
@ -122,7 +121,7 @@ export default function PluginFilterTimegrain(
); );
})} })}
</StyledSelect> </StyledSelect>
</FormItem> </StyledFormItem>
</Styles> </Styles>
); );
} }

View File

@ -19,6 +19,7 @@
import { styled } from '@superset-ui/core'; import { styled } from '@superset-ui/core';
import { Select } from 'src/common/components'; import { Select } from 'src/common/components';
import { PluginFilterStylesProps } from './types'; import { PluginFilterStylesProps } from './types';
import FormItem from '../../components/Form/FormItem';
export const Styles = styled.div<PluginFilterStylesProps>` export const Styles = styled.div<PluginFilterStylesProps>`
min-height: ${({ height }) => height}px; min-height: ${({ height }) => height}px;
@ -28,3 +29,9 @@ export const Styles = styled.div<PluginFilterStylesProps>`
export const StyledSelect = styled(Select)` export const StyledSelect = styled(Select)`
width: 100%; width: 100%;
`; `;
export const StyledFormItem = styled(FormItem)`
&.ant-row.ant-form-item {
margin: 0;
}
`;