mirror of https://github.com/apache/superset.git
chore: Improves the native filters UI/UX - iteration 1 (#14714)
This commit is contained in:
parent
8cfebc0c66
commit
d924223950
|
@ -18,7 +18,12 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { render, screen, fireEvent } from 'spec/helpers/testing-library';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { mockStoreWithChartsInTabsAndRoot } from 'spec/fixtures/mockStore';
|
||||
import { Form, FormInstance } from 'src/common/components';
|
||||
import { NativeFiltersForm } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/types';
|
||||
|
@ -34,7 +39,7 @@ describe('FilterScope', () => {
|
|||
save,
|
||||
};
|
||||
|
||||
const MockModal = ({ scope }: { scope: object | undefined }) => {
|
||||
const MockModal = ({ scope }: { scope?: object }) => {
|
||||
const [newForm] = Form.useForm<NativeFiltersForm>();
|
||||
form = newForm;
|
||||
if (scope) {
|
||||
|
@ -55,56 +60,66 @@ describe('FilterScope', () => {
|
|||
);
|
||||
};
|
||||
|
||||
const getWrapper = (scope?: object) => {
|
||||
render(<MockModal scope={scope} />);
|
||||
};
|
||||
|
||||
const getTreeSwitcher = (order = 0) =>
|
||||
document.querySelectorAll('.ant-tree-switcher')[order];
|
||||
|
||||
it('renders "apply to all" filter scope', () => {
|
||||
getWrapper();
|
||||
expect(screen.queryByRole('tree')).toBe(null);
|
||||
render(<MockModal />);
|
||||
expect(screen.queryByRole('tree')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('select tree values with 1 excluded', () => {
|
||||
getWrapper();
|
||||
it('select tree values with 1 excluded', async () => {
|
||||
render(<MockModal />);
|
||||
fireEvent.click(screen.getByText('Scoping'));
|
||||
fireEvent.click(screen.getByLabelText('Apply to specific panels'));
|
||||
expect(screen.getByRole('tree')).not.toBe(null);
|
||||
fireEvent.click(getTreeSwitcher(2));
|
||||
fireEvent.click(screen.getByText('CHART_ID2'));
|
||||
expect(form.getFieldValue('filters')?.[mockedProps.filterId].scope).toEqual(
|
||||
{
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
form.getFieldValue('filters')?.[mockedProps.filterId].scope,
|
||||
).toEqual({
|
||||
excluded: [20],
|
||||
rootPath: ['ROOT_ID'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('select 1 value only', () => {
|
||||
getWrapper();
|
||||
it('select 1 value only', async () => {
|
||||
render(<MockModal />);
|
||||
fireEvent.click(screen.getByText('Scoping'));
|
||||
fireEvent.click(screen.getByLabelText('Apply to specific panels'));
|
||||
expect(screen.getByRole('tree')).not.toBe(null);
|
||||
fireEvent.click(getTreeSwitcher(2));
|
||||
fireEvent.click(screen.getByText('CHART_ID2'));
|
||||
fireEvent.click(screen.getByText('tab1'));
|
||||
expect(form.getFieldValue('filters')?.[mockedProps.filterId].scope).toEqual(
|
||||
{
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
form.getFieldValue('filters')?.[mockedProps.filterId].scope,
|
||||
).toEqual({
|
||||
excluded: [18, 20],
|
||||
rootPath: ['ROOT_ID'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('correct init tree with values', () => {
|
||||
getWrapper({
|
||||
rootPath: ['TAB_ID'],
|
||||
excluded: [],
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText('Apply to specific panels'));
|
||||
expect(screen.getByRole('tree')).not.toBe(null);
|
||||
expect(document.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(
|
||||
1,
|
||||
it('correct init tree with values', async () => {
|
||||
render(
|
||||
<MockModal
|
||||
scope={{
|
||||
rootPath: ['TAB_ID'],
|
||||
excluded: [],
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByText('Scoping'));
|
||||
fireEvent.click(screen.getByLabelText('Apply to specific panels'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('tree')).toBeInTheDocument();
|
||||
expect(
|
||||
document.querySelectorAll('.ant-tree-checkbox-checked').length,
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -64,7 +64,6 @@ const FilterScope: FC<FilterScopeProps> = ({
|
|||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Typography.Title level={5}>{t('Scoping')}</Typography.Title>
|
||||
<CleanFormItem
|
||||
name={[...pathToFormValue, 'scoping']}
|
||||
initialValue={initialScoping}
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
import { FormInstance } from 'antd/lib/form';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Checkbox, Form, Input, Typography } from 'src/common/components';
|
||||
import { Checkbox, Form, Input } from 'src/common/components';
|
||||
import { Select } from 'src/components/Select';
|
||||
import SupersetResourceSelect, {
|
||||
cachedSupersetGet,
|
||||
|
@ -48,6 +48,7 @@ import Button from 'src/components/Button';
|
|||
import { getChartDataRequest } from 'src/chart/chartAction';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
import Tabs from 'src/components/Tabs';
|
||||
import { ColumnSelect } from './ColumnSelect';
|
||||
import { NativeFiltersForm } from '../types';
|
||||
import {
|
||||
|
@ -67,6 +68,8 @@ import {
|
|||
getFiltersConfigModalTestId,
|
||||
} from '../FiltersConfigModal';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
@ -92,6 +95,17 @@ const CleanFormItem = styled(Form.Item)`
|
|||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
const FilterTabs = {
|
||||
configuration: {
|
||||
key: 'configuration',
|
||||
name: t('Configuration'),
|
||||
},
|
||||
scoping: {
|
||||
key: 'scoping',
|
||||
name: t('Scoping'),
|
||||
},
|
||||
};
|
||||
|
||||
export interface FiltersConfigFormProps {
|
||||
filterId: string;
|
||||
filterToEdit?: Filter;
|
||||
|
@ -264,254 +278,271 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={5}>{t('Settings')}</Typography.Title>
|
||||
<StyledContainer>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'name']}
|
||||
label={<StyledLabel>{t('Filter name')}</StyledLabel>}
|
||||
initialValue={filterToEdit?.name}
|
||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
||||
<Tabs defaultActiveKey={FilterTabs.configuration.key} centered>
|
||||
<TabPane
|
||||
tab={FilterTabs.configuration.name}
|
||||
key={FilterTabs.configuration.key}
|
||||
forceRender
|
||||
>
|
||||
<Input {...getFiltersConfigModalTestId('name-input')} />
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'filterType']}
|
||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
||||
initialValue={filterToEdit?.filterType || 'filter_select'}
|
||||
label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
|
||||
{...getFiltersConfigModalTestId('filter-type')}
|
||||
>
|
||||
<Select
|
||||
options={nativeFilterVizTypes.map(filterType => ({
|
||||
value: filterType,
|
||||
// @ts-ignore
|
||||
label: nativeFilterItems[filterType]?.value.name,
|
||||
}))}
|
||||
onChange={({ value }: { value: string }) => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
filterType: value,
|
||||
defaultDataMask: null,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
</StyledContainer>
|
||||
{hasDataset && (
|
||||
<>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'dataset']}
|
||||
initialValue={{ value: initialDatasetId }}
|
||||
label={<StyledLabel>{t('Dataset')}</StyledLabel>}
|
||||
rules={[{ required: !removed, message: t('Dataset is required') }]}
|
||||
{...getFiltersConfigModalTestId('datasource-input')}
|
||||
>
|
||||
<SupersetResourceSelect
|
||||
initialId={initialDatasetId}
|
||||
resource="dataset"
|
||||
searchColumn="table_name"
|
||||
transformItem={datasetToSelectOption}
|
||||
isMulti={false}
|
||||
onError={onDatasetSelectError}
|
||||
defaultOptions={Object.values(loadedDatasets).map(
|
||||
datasetToSelectOption,
|
||||
)}
|
||||
onChange={e => {
|
||||
// We need reset column when dataset changed
|
||||
if (datasetId && e?.value !== datasetId) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
column: null,
|
||||
});
|
||||
}
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
{hasColumn && (
|
||||
<StyledContainer>
|
||||
<StyledFormItem
|
||||
// don't show the column select unless we have a dataset
|
||||
// style={{ display: datasetId == null ? undefined : 'none' }}
|
||||
name={['filters', filterId, 'column']}
|
||||
initialValue={initColumn}
|
||||
label={<StyledLabel>{t('Column')}</StyledLabel>}
|
||||
rules={[{ required: !removed, message: t('Field is required') }]}
|
||||
data-test="field-input"
|
||||
name={['filters', filterId, 'name']}
|
||||
label={<StyledLabel>{t('Filter name')}</StyledLabel>}
|
||||
initialValue={filterToEdit?.name}
|
||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
||||
>
|
||||
<ColumnSelect
|
||||
form={form}
|
||||
filterId={filterId}
|
||||
datasetId={datasetId}
|
||||
onChange={() => {
|
||||
// We need reset default value when when column changed
|
||||
<Input {...getFiltersConfigModalTestId('name-input')} />
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'filterType']}
|
||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
||||
initialValue={filterToEdit?.filterType || 'filter_select'}
|
||||
label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
|
||||
{...getFiltersConfigModalTestId('filter-type')}
|
||||
>
|
||||
<Select
|
||||
options={nativeFilterVizTypes.map(filterType => ({
|
||||
value: filterType,
|
||||
// @ts-ignore
|
||||
label: nativeFilterItems[filterType]?.value.name,
|
||||
}))}
|
||||
onChange={({ value }: { value: string }) => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
filterType: value,
|
||||
defaultDataMask: null,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
{hasAdditionalFilters && (
|
||||
</StyledContainer>
|
||||
{hasDataset && (
|
||||
<>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'adhoc_filters']}
|
||||
initialValue={filterToEdit?.adhoc_filters}
|
||||
name={['filters', filterId, 'dataset']}
|
||||
initialValue={{ value: initialDatasetId }}
|
||||
label={<StyledLabel>{t('Dataset')}</StyledLabel>}
|
||||
rules={[
|
||||
{ required: !removed, message: t('Dataset is required') },
|
||||
]}
|
||||
{...getFiltersConfigModalTestId('datasource-input')}
|
||||
>
|
||||
<AdhocFilterControl
|
||||
columns={
|
||||
datasetDetails?.columns?.filter(
|
||||
(c: ColumnMeta) => c.filterable,
|
||||
) || []
|
||||
}
|
||||
savedMetrics={datasetDetails?.metrics || []}
|
||||
datasource={datasetDetails}
|
||||
onChange={(filters: AdhocFilter[]) => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
adhoc_filters: filters,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
label={<StyledLabel>{t('Adhoc filters')}</StyledLabel>}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'time_range']}
|
||||
label={<StyledLabel>{t('Time range')}</StyledLabel>}
|
||||
initialValue={filterToEdit?.time_range || 'No filter'}
|
||||
>
|
||||
<DateFilterControl
|
||||
name="time_range"
|
||||
onChange={timeRange => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
time_range: timeRange,
|
||||
});
|
||||
<SupersetResourceSelect
|
||||
initialId={initialDatasetId}
|
||||
resource="dataset"
|
||||
searchColumn="table_name"
|
||||
transformItem={datasetToSelectOption}
|
||||
isMulti={false}
|
||||
onError={onDatasetSelectError}
|
||||
defaultOptions={Object.values(loadedDatasets).map(
|
||||
datasetToSelectOption,
|
||||
)}
|
||||
onChange={e => {
|
||||
// We need reset column when dataset changed
|
||||
if (datasetId && e?.value !== datasetId) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
column: null,
|
||||
});
|
||||
}
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
{hasColumn && (
|
||||
<StyledFormItem
|
||||
// don't show the column select unless we have a dataset
|
||||
// style={{ display: datasetId == null ? undefined : 'none' }}
|
||||
name={['filters', filterId, 'column']}
|
||||
initialValue={initColumn}
|
||||
label={<StyledLabel>{t('Column')}</StyledLabel>}
|
||||
rules={[
|
||||
{ required: !removed, message: t('Field is required') },
|
||||
]}
|
||||
data-test="field-input"
|
||||
>
|
||||
<ColumnSelect
|
||||
form={form}
|
||||
filterId={filterId}
|
||||
datasetId={datasetId}
|
||||
onChange={() => {
|
||||
// We need reset default value when when column changed
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
{hasAdditionalFilters && (
|
||||
<>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'adhoc_filters']}
|
||||
initialValue={filterToEdit?.adhoc_filters}
|
||||
>
|
||||
<AdhocFilterControl
|
||||
columns={
|
||||
datasetDetails?.columns?.filter(
|
||||
(c: ColumnMeta) => c.filterable,
|
||||
) || []
|
||||
}
|
||||
savedMetrics={datasetDetails?.metrics || []}
|
||||
datasource={datasetDetails}
|
||||
onChange={(filters: AdhocFilter[]) => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
adhoc_filters: filters,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
label={<StyledLabel>{t('Adhoc filters')}</StyledLabel>}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'time_range']}
|
||||
label={<StyledLabel>{t('Time range')}</StyledLabel>}
|
||||
initialValue={filterToEdit?.time_range || 'No filter'}
|
||||
>
|
||||
<DateFilterControl
|
||||
name="time_range"
|
||||
onChange={timeRange => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
time_range: timeRange,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{hasFilledDataset && (
|
||||
<CleanFormItem
|
||||
name={['filters', filterId, 'defaultValueFormData']}
|
||||
hidden
|
||||
initialValue={newFormData}
|
||||
/>
|
||||
)}
|
||||
<CleanFormItem
|
||||
name={['filters', filterId, 'defaultValueQueriesData']}
|
||||
hidden
|
||||
initialValue={null}
|
||||
/>
|
||||
{isCascadingFilter && (
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'parentFilter']}
|
||||
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
|
||||
initialValue={parentFilterOptions.find(
|
||||
({ value }) => value === filterToEdit?.cascadeParentIds[0],
|
||||
)}
|
||||
data-test="parent-filter-input"
|
||||
>
|
||||
<Select
|
||||
placeholder={t('None')}
|
||||
options={parentFilterOptions}
|
||||
isClearable
|
||||
/>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
<StyledContainer>
|
||||
<StyledFormItem className="bottom" label={<StyledLabel />}>
|
||||
{hasDataset && hasFilledDataset && (
|
||||
<Button onClick={refreshHandler}>
|
||||
{isDataDirty ? t('Populate') : t('Refresh')}
|
||||
</Button>
|
||||
)}
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'defaultDataMask']}
|
||||
initialValue={filterToEdit?.defaultDataMask}
|
||||
data-test="default-input"
|
||||
label={<StyledLabel>{t('Default Value')}</StyledLabel>}
|
||||
>
|
||||
{showDefaultValue ? (
|
||||
<DefaultValue
|
||||
setDataMask={dataMask => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: dataMask,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
filterId={filterId}
|
||||
hasDataset={hasDataset}
|
||||
form={form}
|
||||
formData={newFormData}
|
||||
{hasFilledDataset && (
|
||||
<CleanFormItem
|
||||
name={['filters', filterId, 'defaultValueFormData']}
|
||||
hidden
|
||||
initialValue={newFormData}
|
||||
/>
|
||||
) : hasFilledDataset ? (
|
||||
t('Click "Populate" to get "Default Value" ->')
|
||||
) : (
|
||||
t('Fill all required fields to enable "Default Value"')
|
||||
)}
|
||||
</StyledFormItem>
|
||||
</StyledContainer>
|
||||
<StyledCheckboxFormItem
|
||||
name={['filters', filterId, 'isInstant']}
|
||||
initialValue={filterToEdit?.isInstant || false}
|
||||
valuePropName="checked"
|
||||
colon={false}
|
||||
>
|
||||
<Checkbox data-test="apply-changes-instantly-checkbox">
|
||||
{t('Apply changes instantly')}
|
||||
</Checkbox>
|
||||
</StyledCheckboxFormItem>
|
||||
<ControlItems
|
||||
disabled={!showDefaultValue}
|
||||
filterToEdit={filterToEdit}
|
||||
formFilter={formFilter}
|
||||
filterId={filterId}
|
||||
form={form}
|
||||
forceUpdate={forceUpdate}
|
||||
/>
|
||||
{hasMetrics && (
|
||||
<StyledFormItem
|
||||
// don't show the column select unless we have a dataset
|
||||
// style={{ display: datasetId == null ? undefined : 'none' }}
|
||||
name={['filters', filterId, 'sortMetric']}
|
||||
initialValue={filterToEdit?.sortMetric}
|
||||
label={<StyledLabel>{t('Sort Metric')}</StyledLabel>}
|
||||
data-test="field-input"
|
||||
>
|
||||
<SelectControl
|
||||
form={form}
|
||||
filterId={filterId}
|
||||
name="sortMetric"
|
||||
options={metrics.map((metric: Metric) => ({
|
||||
value: metric.metric_name,
|
||||
label: metric.verbose_name ?? metric.metric_name,
|
||||
}))}
|
||||
onChange={(value: string | null): void => {
|
||||
if (value !== undefined) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
sortMetric: value,
|
||||
});
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
<CleanFormItem
|
||||
name={['filters', filterId, 'defaultValueQueriesData']}
|
||||
hidden
|
||||
initialValue={null}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
<FilterScope
|
||||
updateFormValues={(values: any) =>
|
||||
setNativeFilterFieldValues(form, filterId, values)
|
||||
}
|
||||
pathToFormValue={['filters', filterId]}
|
||||
forceUpdate={forceUpdate}
|
||||
scope={filterToEdit?.scope}
|
||||
formScope={formFilter?.scope}
|
||||
formScoping={formFilter?.scoping}
|
||||
/>
|
||||
{isCascadingFilter && (
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'parentFilter']}
|
||||
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
|
||||
initialValue={parentFilterOptions.find(
|
||||
({ value }) => value === filterToEdit?.cascadeParentIds[0],
|
||||
)}
|
||||
data-test="parent-filter-input"
|
||||
>
|
||||
<Select
|
||||
placeholder={t('None')}
|
||||
options={parentFilterOptions}
|
||||
isClearable
|
||||
/>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
<StyledContainer>
|
||||
<StyledFormItem className="bottom" label={<StyledLabel />}>
|
||||
{hasDataset && hasFilledDataset && (
|
||||
<Button onClick={refreshHandler}>
|
||||
{isDataDirty ? t('Populate') : t('Refresh')}
|
||||
</Button>
|
||||
)}
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'defaultDataMask']}
|
||||
initialValue={filterToEdit?.defaultDataMask}
|
||||
data-test="default-input"
|
||||
label={<StyledLabel>{t('Default Value')}</StyledLabel>}
|
||||
>
|
||||
{showDefaultValue ? (
|
||||
<DefaultValue
|
||||
setDataMask={dataMask => {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: dataMask,
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
filterId={filterId}
|
||||
hasDataset={hasDataset}
|
||||
form={form}
|
||||
formData={newFormData}
|
||||
/>
|
||||
) : hasFilledDataset ? (
|
||||
t('Click "Populate" to get "Default Value" ->')
|
||||
) : (
|
||||
t('Fill all required fields to enable "Default Value"')
|
||||
)}
|
||||
</StyledFormItem>
|
||||
</StyledContainer>
|
||||
<StyledCheckboxFormItem
|
||||
name={['filters', filterId, 'isInstant']}
|
||||
initialValue={filterToEdit?.isInstant || false}
|
||||
valuePropName="checked"
|
||||
colon={false}
|
||||
>
|
||||
<Checkbox data-test="apply-changes-instantly-checkbox">
|
||||
{t('Apply changes instantly')}
|
||||
</Checkbox>
|
||||
</StyledCheckboxFormItem>
|
||||
<ControlItems
|
||||
disabled={!showDefaultValue}
|
||||
filterToEdit={filterToEdit}
|
||||
formFilter={formFilter}
|
||||
filterId={filterId}
|
||||
form={form}
|
||||
forceUpdate={forceUpdate}
|
||||
/>
|
||||
{hasMetrics && (
|
||||
<StyledFormItem
|
||||
// don't show the column select unless we have a dataset
|
||||
// style={{ display: datasetId == null ? undefined : 'none' }}
|
||||
name={['filters', filterId, 'sortMetric']}
|
||||
initialValue={filterToEdit?.sortMetric}
|
||||
label={<StyledLabel>{t('Sort Metric')}</StyledLabel>}
|
||||
data-test="field-input"
|
||||
>
|
||||
<SelectControl
|
||||
form={form}
|
||||
filterId={filterId}
|
||||
name="sortMetric"
|
||||
options={metrics.map((metric: Metric) => ({
|
||||
value: metric.metric_name,
|
||||
label: metric.verbose_name ?? metric.metric_name,
|
||||
}))}
|
||||
onChange={(value: string | null): void => {
|
||||
if (value !== undefined) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
sortMetric: value,
|
||||
});
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={FilterTabs.scoping.name}
|
||||
key={FilterTabs.scoping.key}
|
||||
forceRender
|
||||
>
|
||||
<FilterScope
|
||||
updateFormValues={(values: any) =>
|
||||
setNativeFilterFieldValues(form, filterId, values)
|
||||
}
|
||||
pathToFormValue={['filters', filterId]}
|
||||
forceUpdate={forceUpdate}
|
||||
scope={filterToEdit?.scope}
|
||||
formScope={formFilter?.scope}
|
||||
formScoping={formFilter?.scoping}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@ import { useOpenModal, useRemoveCurrentFilter } from './state';
|
|||
|
||||
export const StyledModalBody = styled.div`
|
||||
display: flex;
|
||||
height: 500px;
|
||||
flex-direction: row;
|
||||
.filters-list {
|
||||
width: ${({ theme }) => theme.gridUnit * 50}px;
|
||||
|
|
|
@ -36,6 +36,7 @@ export function CancelConfirmationAlert({
|
|||
}: ConfirmationAlertProps) {
|
||||
return (
|
||||
<Alert
|
||||
closable={false}
|
||||
type="warning"
|
||||
key="alert"
|
||||
message={title}
|
||||
|
@ -46,14 +47,6 @@ export function CancelConfirmationAlert({
|
|||
description={children}
|
||||
action={
|
||||
<div css={{ display: 'flex' }}>
|
||||
<Button
|
||||
key="submit"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{t('Yes, cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
key="cancel"
|
||||
buttonSize="small"
|
||||
|
@ -62,6 +55,14 @@ export function CancelConfirmationAlert({
|
|||
>
|
||||
{t('Keep editing')}
|
||||
</Button>
|
||||
<Button
|
||||
key="submit"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{t('Yes, cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue