mirror of
https://github.com/apache/superset.git
synced 2026-04-28 04:25:07 +00:00
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:
@@ -132,7 +132,7 @@ const FilterFocusHighlight = React.forwardRef(
|
||||
|
||||
if (focusedNativeFilterId) {
|
||||
if (
|
||||
nativeFilters.filters[focusedNativeFilterId].chartsInScope?.includes(
|
||||
nativeFilters.filters[focusedNativeFilterId]?.chartsInScope?.includes(
|
||||
chartId,
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -44,6 +44,7 @@ import { FilterProps } from './types';
|
||||
import { getFormData } from '../../utils';
|
||||
import { useCascadingFilters } from './state';
|
||||
import { usePreselectNativeFilter } from '../../state';
|
||||
import { checkIsMissingRequiredValue } from '../utils';
|
||||
|
||||
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) {
|
||||
filterState.value = preselection;
|
||||
}
|
||||
|
||||
@@ -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,6 +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" />
|
||||
) : (
|
||||
@@ -74,6 +79,7 @@ const DefaultValue: FC<DefaultValueProps> = ({
|
||||
enableNoResults={enableNoResults}
|
||||
filterState={{
|
||||
...formFilter.defaultDataMask?.filterState,
|
||||
validateMessage: isMissingRequiredValue && t('Value is required'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -345,13 +345,27 @@ const FiltersConfigForm = (
|
||||
const hasDataset = !!nativeFilterItems[formFilter?.filterType]?.value
|
||||
?.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] ?? {};
|
||||
// @ts-ignore
|
||||
const enableNoResults = !!nativeFilterItem.value?.enableNoResults;
|
||||
const datasetId = formFilter?.dataset?.value;
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetId && hasDataset) {
|
||||
if (datasetId && hasColumn) {
|
||||
cachedSupersetGet({
|
||||
endpoint: `/api/v1/dataset/${datasetId}`,
|
||||
})
|
||||
@@ -367,7 +381,7 @@ const FiltersConfigForm = (
|
||||
addDangerToast(response.message);
|
||||
});
|
||||
}
|
||||
}, [datasetId, hasDataset]);
|
||||
}, [datasetId, hasColumn]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
changeTab(tab: 'configuration' | 'scoping') {
|
||||
@@ -375,10 +389,10 @@ const FiltersConfigForm = (
|
||||
},
|
||||
}));
|
||||
|
||||
const hasMetrics = hasDataset && !!metrics.length;
|
||||
const hasMetrics = hasColumn && !!metrics.length;
|
||||
|
||||
const hasFilledDataset =
|
||||
!hasDataset || (datasetId && (formFilter?.column || !hasDataset));
|
||||
!hasDataset || (datasetId && (formFilter?.column || !hasColumn));
|
||||
|
||||
const hasAdditionalFilters = FILTERS_WITH_ADHOC_FILTERS.includes(
|
||||
formFilter?.filterType,
|
||||
@@ -477,7 +491,7 @@ const FiltersConfigForm = (
|
||||
: undefined);
|
||||
const newFormData = getFormData({
|
||||
datasetId,
|
||||
groupby: hasDataset ? formFilter?.column : undefined,
|
||||
groupby: hasColumn ? formFilter?.column : undefined,
|
||||
...formFilter,
|
||||
});
|
||||
|
||||
@@ -534,20 +548,10 @@ const FiltersConfigForm = (
|
||||
const hasSorting =
|
||||
typeof filterToEdit?.controlValues?.sortAscending === 'boolean';
|
||||
|
||||
const showDefaultValue = !hasDataset || (!isDataDirty && hasFilledDataset);
|
||||
|
||||
const { controlItems = {}, mainControlItems = {} } = formFilter
|
||||
? getControlItemsMap({
|
||||
disabled: false,
|
||||
forceUpdate,
|
||||
form,
|
||||
filterId,
|
||||
filterType: formFilter.filterType,
|
||||
filterToEdit,
|
||||
formFilter,
|
||||
removed,
|
||||
})
|
||||
: {};
|
||||
const showDefaultValue =
|
||||
!hasDataset ||
|
||||
(!isDataDirty && hasFilledDataset) ||
|
||||
!mainControlItems.groupby;
|
||||
|
||||
const onSortChanged = (value: boolean | undefined) => {
|
||||
const previous = form.getFieldValue('filters')?.[filterId].controlValues;
|
||||
@@ -683,6 +687,7 @@ const FiltersConfigForm = (
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
filterType: value,
|
||||
defaultDataMask: null,
|
||||
column: null,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
@@ -738,13 +743,6 @@ const FiltersConfigForm = (
|
||||
header={FilterPanels.basic.name}
|
||||
key={FilterPanels.basic.key}
|
||||
>
|
||||
{hasFilledDataset && (
|
||||
<CleanFormItem
|
||||
name={['filters', filterId, 'defaultValueFormData']}
|
||||
hidden
|
||||
initialValue={newFormData}
|
||||
/>
|
||||
)}
|
||||
<CleanFormItem
|
||||
name={['filters', filterId, 'defaultValueQueriesData']}
|
||||
hidden
|
||||
@@ -760,7 +758,11 @@ const FiltersConfigForm = (
|
||||
>
|
||||
<StyledRowSubFormItem
|
||||
name={['filters', filterId, 'defaultDataMask']}
|
||||
initialValue={filterToEdit?.defaultDataMask}
|
||||
initialValue={
|
||||
formFilter.filterType === filterToEdit?.filterType
|
||||
? filterToEdit?.defaultDataMask
|
||||
: null
|
||||
}
|
||||
data-test="default-input"
|
||||
label={<StyledLabel>{t('Default Value')}</StyledLabel>}
|
||||
required={hasDefaultValue}
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
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 { Styles, StyledSelect, StyledFormItem } from '../common';
|
||||
import { PluginFilterGroupByProps } from './types';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -87,7 +86,7 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
|
||||
: tn('%s option', '%s options', columns.length, columns.length);
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<FormItem
|
||||
<StyledFormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
@@ -116,7 +115,7 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
|
||||
},
|
||||
)}
|
||||
</StyledSelect>
|
||||
</FormItem>
|
||||
</StyledFormItem>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ 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 { StyledFormItem, Styles } from '../common';
|
||||
import { getRangeExtraFormData } from '../../utils';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const Error = styled.div`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
@@ -159,7 +158,7 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
||||
{Number.isNaN(Number(min)) || Number.isNaN(Number(max)) ? (
|
||||
<h4>{t('Chosen non-numeric column')}</h4>
|
||||
) : (
|
||||
<FormItem
|
||||
<StyledFormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
@@ -183,7 +182,7 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
|
||||
marks={marks}
|
||||
/>
|
||||
</Wrapper>
|
||||
</FormItem>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
</Styles>
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
GenericDataType,
|
||||
JsonObject,
|
||||
smartDateDetailedFormatter,
|
||||
styled,
|
||||
t,
|
||||
tn,
|
||||
} from '@superset-ui/core';
|
||||
@@ -44,11 +45,15 @@ import { useImmerReducer } from 'use-immer';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { usePrevious } from 'src/common/hooks/usePrevious';
|
||||
import { PluginFilterSelectProps, SelectValue } from './types';
|
||||
import { StyledSelect, Styles } from '../common';
|
||||
import { StyledFormItem, StyledSelect, Styles } from '../common';
|
||||
import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Error = styled.div`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
`;
|
||||
|
||||
type DataMaskAction =
|
||||
| { type: 'ownState'; ownState: JsonObject }
|
||||
| {
|
||||
@@ -273,52 +278,57 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
|
||||
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
// @ts-ignore
|
||||
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" />}
|
||||
<StyledFormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
{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>
|
||||
<StyledSelect
|
||||
allowClear
|
||||
// @ts-ignore
|
||||
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 => {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ import {
|
||||
} from '@superset-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Select } from 'src/common/components';
|
||||
import { Styles, StyledSelect } from '../common';
|
||||
import { Styles, StyledSelect, StyledFormItem } from '../common';
|
||||
import { PluginFilterTimeColumnProps } from './types';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -89,7 +88,7 @@ export default function PluginFilterTimeColumn(
|
||||
: tn('%s option', '%s options', timeColumns.length, timeColumns.length);
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<FormItem
|
||||
<StyledFormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
@@ -117,7 +116,7 @@ export default function PluginFilterTimeColumn(
|
||||
},
|
||||
)}
|
||||
</StyledSelect>
|
||||
</FormItem>
|
||||
</StyledFormItem>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ import {
|
||||
} from '@superset-ui/core';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Select } from 'src/common/components';
|
||||
import { Styles, StyledSelect } from '../common';
|
||||
import { Styles, StyledSelect, StyledFormItem } from '../common';
|
||||
import { PluginFilterTimeGrainProps } from './types';
|
||||
import FormItem from '../../../components/Form/FormItem';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -99,7 +98,7 @@ export default function PluginFilterTimegrain(
|
||||
: tn('%s option', '%s options', data.length, data.length);
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<FormItem
|
||||
<StyledFormItem
|
||||
validateStatus={filterState.validateMessage && 'error'}
|
||||
extra={<Error>{filterState.validateMessage}</Error>}
|
||||
>
|
||||
@@ -122,7 +121,7 @@ export default function PluginFilterTimegrain(
|
||||
);
|
||||
})}
|
||||
</StyledSelect>
|
||||
</FormItem>
|
||||
</StyledFormItem>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { Select } from 'src/common/components';
|
||||
import { PluginFilterStylesProps } from './types';
|
||||
import FormItem from '../../components/Form/FormItem';
|
||||
|
||||
export const Styles = styled.div<PluginFilterStylesProps>`
|
||||
min-height: ${({ height }) => height}px;
|
||||
@@ -28,3 +29,9 @@ export const Styles = styled.div<PluginFilterStylesProps>`
|
||||
export const StyledSelect = styled(Select)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const StyledFormItem = styled(FormItem)`
|
||||
&.ant-row.ant-form-item {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user