mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
feat: support mulitple temporal filters in AdhocFilter and move the Time Section away (#21767)
This commit is contained in:
@@ -21,6 +21,7 @@ import { css, styled, t, useTheme, NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import Button from 'src/components/Button';
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
import Label, { Type } from 'src/components/Label';
|
||||
import Modal from 'src/components/Modal';
|
||||
import { Divider } from 'src/components';
|
||||
import Icons from 'src/components/Icons';
|
||||
import Select from 'src/components/Select/Select';
|
||||
@@ -32,10 +33,12 @@ import ControlPopover from '../ControlPopover/ControlPopover';
|
||||
|
||||
import { DateFilterControlProps, FrameType } from './types';
|
||||
import {
|
||||
DATE_FILTER_TEST_KEY,
|
||||
fetchTimeRange,
|
||||
FRAME_OPTIONS,
|
||||
getDateFilterControlTestId,
|
||||
guessFrame,
|
||||
useDefaultTimeFilter,
|
||||
} from './utils';
|
||||
import {
|
||||
CommonFrame,
|
||||
@@ -44,7 +47,6 @@ import {
|
||||
AdvancedFrame,
|
||||
} from './components';
|
||||
|
||||
const StyledPopover = styled(ControlPopover)``;
|
||||
const StyledRangeType = styled(Select)`
|
||||
width: 272px;
|
||||
`;
|
||||
@@ -121,12 +123,15 @@ const IconWrapper = styled.span`
|
||||
|
||||
export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
const {
|
||||
value = NO_TIME_RANGE,
|
||||
onChange,
|
||||
type,
|
||||
onOpenPopover = noOp,
|
||||
onClosePopover = noOp,
|
||||
overlayStyle = 'Popover',
|
||||
} = props;
|
||||
const defaultTimeFilter = useDefaultTimeFilter();
|
||||
|
||||
const value = props.value ?? defaultTimeFilter;
|
||||
const [actualTimeRange, setActualTimeRange] = useState<string>(value);
|
||||
|
||||
const [show, setShow] = useState<boolean>(false);
|
||||
@@ -137,6 +142,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
const [validTimeRange, setValidTimeRange] = useState<boolean>(false);
|
||||
const [evalResponse, setEvalResponse] = useState<string>(value);
|
||||
const [tooltipTitle, setTooltipTitle] = useState<string>(value);
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (value === NO_TIME_RANGE) {
|
||||
@@ -180,6 +186,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
setValidTimeRange(true);
|
||||
}
|
||||
setLastFetchedTimeRange(value);
|
||||
setEvalResponse(actualRange || value);
|
||||
});
|
||||
}, [value]);
|
||||
|
||||
@@ -225,7 +232,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
setShow(false);
|
||||
}
|
||||
|
||||
const togglePopover = () => {
|
||||
const toggleOverlay = () => {
|
||||
if (show) {
|
||||
onHide();
|
||||
onClosePopover();
|
||||
@@ -242,8 +249,6 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
setFrame(value);
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const overlayContent = (
|
||||
<ContentStyleWrapper>
|
||||
<div className="control-label">{t('RANGE TYPE')}</div>
|
||||
@@ -266,7 +271,9 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
{frame === 'Custom' && (
|
||||
<CustomFrame value={timeRangeValue} onChange={setTimeRangeValue} />
|
||||
)}
|
||||
{frame === 'No filter' && <div data-test="no-filter" />}
|
||||
{frame === 'No filter' && (
|
||||
<div data-test={DATE_FILTER_TEST_KEY.noFilter} />
|
||||
)}
|
||||
<Divider />
|
||||
<div>
|
||||
<div className="section-title">{t('Actual time range')}</div>
|
||||
@@ -285,7 +292,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
cta
|
||||
key="cancel"
|
||||
onClick={onHide}
|
||||
data-test="cancel-button"
|
||||
data-test={DATE_FILTER_TEST_KEY.cancelButton}
|
||||
>
|
||||
{t('CANCEL')}
|
||||
</Button>
|
||||
@@ -310,25 +317,56 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
</IconWrapper>
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
<ControlPopover
|
||||
placement="right"
|
||||
trigger="click"
|
||||
content={overlayContent}
|
||||
title={title}
|
||||
defaultVisible={show}
|
||||
visible={show}
|
||||
onVisibleChange={toggleOverlay}
|
||||
overlayStyle={{ width: '600px' }}
|
||||
>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<Label
|
||||
className="pointer"
|
||||
data-test={DATE_FILTER_TEST_KEY.popoverOverlay}
|
||||
>
|
||||
{actualTimeRange}
|
||||
</Label>
|
||||
</Tooltip>
|
||||
</ControlPopover>
|
||||
);
|
||||
|
||||
const modalContent = (
|
||||
<>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<Label
|
||||
className="pointer"
|
||||
onClick={toggleOverlay}
|
||||
data-test={DATE_FILTER_TEST_KEY.modalOverlay}
|
||||
>
|
||||
{actualTimeRange}
|
||||
</Label>
|
||||
</Tooltip>
|
||||
<Modal
|
||||
title={title}
|
||||
show={show}
|
||||
onHide={toggleOverlay}
|
||||
width="600px"
|
||||
hideFooter
|
||||
zIndex={Number.MAX_SAFE_INTEGER}
|
||||
>
|
||||
{overlayContent}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ControlHeader {...props} />
|
||||
<StyledPopover
|
||||
placement="right"
|
||||
trigger="click"
|
||||
content={overlayContent}
|
||||
title={title}
|
||||
defaultVisible={show}
|
||||
visible={show}
|
||||
onVisibleChange={togglePopover}
|
||||
overlayStyle={{ width: '600px' }}
|
||||
>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<Label className="pointer" data-test="time-range-trigger">
|
||||
{actualTimeRange}
|
||||
</Label>
|
||||
</Tooltip>
|
||||
</StyledPopover>
|
||||
{overlayStyle === 'Modal' ? modalContent : popoverContent}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Radio } from 'src/components/Radio';
|
||||
import {
|
||||
COMMON_RANGE_OPTIONS,
|
||||
COMMON_RANGE_SET,
|
||||
DATE_FILTER_TEST_KEY,
|
||||
} from 'src/explore/components/controls/DateFilterControl/utils';
|
||||
import {
|
||||
CommonRangeType,
|
||||
@@ -38,7 +39,12 @@ export function CommonFrame(props: FrameComponentProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section-title">{t('Configure Time Range: Last...')}</div>
|
||||
<div
|
||||
className="section-title"
|
||||
data-test={DATE_FILTER_TEST_KEY.commonFrame}
|
||||
>
|
||||
{t('Configure Time Range: Last...')}
|
||||
</div>
|
||||
<Radio.Group
|
||||
value={commonRange}
|
||||
onChange={(e: any) => props.onChange(e.target.value)}
|
||||
|
||||
@@ -21,4 +21,5 @@ export {
|
||||
DATE_FILTER_CONTROL_TEST_ID,
|
||||
fetchTimeRange,
|
||||
guessFrame,
|
||||
DATE_FILTER_TEST_KEY,
|
||||
} from './utils';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AdvancedFrame } from '.';
|
||||
import { AdvancedFrame } from '../components';
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<AdvancedFrame onChange={jest.fn()} value="Last week" />);
|
||||
@@ -22,7 +22,7 @@ import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CustomFrame } from '.';
|
||||
import { CustomFrame } from '../components';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import DateFilterLabel from '..';
|
||||
import { DateFilterControlProps } from '../types';
|
||||
import { DATE_FILTER_TEST_KEY } from '../utils';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
|
||||
function setup(
|
||||
props: Omit<DateFilterControlProps, 'name'>,
|
||||
store: any = mockStore({}),
|
||||
) {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<DateFilterLabel
|
||||
name="time_range"
|
||||
onChange={props.onChange}
|
||||
overlayStyle={props.overlayStyle}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
test('DateFilter with default props', () => {
|
||||
render(setup({ onChange: () => {} }));
|
||||
// label
|
||||
expect(screen.getByText(NO_TIME_RANGE)).toBeInTheDocument();
|
||||
|
||||
// should be popover by default
|
||||
userEvent.click(screen.getByText(NO_TIME_RANGE));
|
||||
expect(
|
||||
screen.getByTestId(DATE_FILTER_TEST_KEY.popoverOverlay),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('DateFilter shoule be applied the overlayStyle props', () => {
|
||||
render(setup({ onChange: () => {}, overlayStyle: 'Modal' }));
|
||||
// should be Modal as overlay
|
||||
userEvent.click(screen.getByText(NO_TIME_RANGE));
|
||||
expect(
|
||||
screen.getByTestId(DATE_FILTER_TEST_KEY.modalOverlay),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('DateFilter shoule be applied the global config time_filter from the store', () => {
|
||||
render(
|
||||
setup(
|
||||
{ onChange: () => {} },
|
||||
mockStore({
|
||||
common: { conf: { DEFAULT_TIME_FILTER: 'Last week' } },
|
||||
}),
|
||||
),
|
||||
);
|
||||
// the label should be 'Last week'
|
||||
expect(screen.getByText('Last week')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(screen.getByText('Last week'));
|
||||
expect(
|
||||
screen.getByTestId(DATE_FILTER_TEST_KEY.commonFrame),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
@@ -99,4 +99,5 @@ export interface DateFilterControlProps {
|
||||
type?: Type;
|
||||
onOpenPopover?: () => void;
|
||||
onClosePopover?: () => void;
|
||||
overlayStyle?: 'Modal' | 'Popover';
|
||||
}
|
||||
|
||||
@@ -137,3 +137,11 @@ export const DATE_FILTER_CONTROL_TEST_ID = 'date-filter-control';
|
||||
export const getDateFilterControlTestId = testWithId(
|
||||
DATE_FILTER_CONTROL_TEST_ID,
|
||||
);
|
||||
|
||||
export enum DATE_FILTER_TEST_KEY {
|
||||
commonFrame = 'common-frame',
|
||||
modalOverlay = 'modal-overlay',
|
||||
popoverOverlay = 'time-range-trigger',
|
||||
noFilter = 'no-filter',
|
||||
cancelButton = 'cancel-button',
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import { SupersetClient, NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import { SupersetClient, NO_TIME_RANGE, JsonObject } from '@superset-ui/core';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
COMMON_RANGE_VALUES_SET,
|
||||
CALENDAR_RANGE_VALUES_SET,
|
||||
@@ -84,3 +85,11 @@ export const fetchTimeRange = async (
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function useDefaultTimeFilter() {
|
||||
return (
|
||||
useSelector(
|
||||
(state: JsonObject) => state?.common?.conf?.DEFAULT_TIME_FILTER,
|
||||
) ?? NO_TIME_RANGE
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
||||
import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||
import { OptionSortType } from 'src/explore/types';
|
||||
import { useGetTimeRangeLabel } from 'src/explore/components/controls/FilterControl/utils';
|
||||
import OptionWrapper from './OptionWrapper';
|
||||
|
||||
export interface DndAdhocFilterOptionProps {
|
||||
@@ -45,6 +46,8 @@ export default function DndAdhocFilterOption({
|
||||
partitionColumn,
|
||||
index,
|
||||
}: DndAdhocFilterOptionProps) {
|
||||
const { actualTimeRange, title } = useGetTimeRangeLabel(adhocFilter);
|
||||
|
||||
return (
|
||||
<AdhocFilterPopoverTrigger
|
||||
key={index}
|
||||
@@ -57,8 +60,8 @@ export default function DndAdhocFilterOption({
|
||||
<OptionWrapper
|
||||
key={index}
|
||||
index={index}
|
||||
label={adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={adhocFilter.getTooltipTitle()}
|
||||
label={actualTimeRange ?? adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={title ?? adhocFilter.getTooltipTitle()}
|
||||
clickClose={onClickClose}
|
||||
onShiftOptions={onShiftOptions}
|
||||
type={DndItemType.FilterOption}
|
||||
|
||||
@@ -17,7 +17,19 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FeatureFlag, GenericDataType } from '@superset-ui/core';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
GenericDataType,
|
||||
QueryFormData,
|
||||
} from '@superset-ui/core';
|
||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||
import { TimeseriesDefaultFormData } from '@superset-ui/plugin-chart-echarts';
|
||||
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
|
||||
import AdhocFilter, {
|
||||
@@ -28,7 +40,6 @@ import {
|
||||
DndFilterSelectProps,
|
||||
} from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect';
|
||||
import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
|
||||
import { TimeseriesDefaultFormData } from '@superset-ui/plugin-chart-echarts';
|
||||
|
||||
const defaultProps: DndFilterSelectProps = {
|
||||
type: 'DndFilterSelect',
|
||||
@@ -56,8 +67,32 @@ afterAll(() => {
|
||||
window.featureFlags = {};
|
||||
});
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
function setup({
|
||||
value = undefined,
|
||||
formData = baseFormData,
|
||||
columns = [],
|
||||
}: {
|
||||
value?: AdhocFilter;
|
||||
formData?: QueryFormData;
|
||||
columns?: ColumnMeta[];
|
||||
} = {}) {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<DndFilterSelect
|
||||
{...defaultProps}
|
||||
value={ensureIsArray(value)}
|
||||
formData={formData}
|
||||
columns={columns}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
test('renders with default props', async () => {
|
||||
render(<DndFilterSelect {...defaultProps} />, { useDnd: true });
|
||||
render(setup(), { useDnd: true });
|
||||
expect(
|
||||
await screen.findByText('Drop columns or metrics here'),
|
||||
).toBeInTheDocument();
|
||||
@@ -68,7 +103,7 @@ test('renders with value', async () => {
|
||||
sqlExpression: 'COUNT(*)',
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
});
|
||||
render(<DndFilterSelect {...defaultProps} value={[value]} />, {
|
||||
render(setup({ value }), {
|
||||
useDnd: true,
|
||||
});
|
||||
expect(await screen.findByText('COUNT(*)')).toBeInTheDocument();
|
||||
@@ -76,14 +111,13 @@ test('renders with value', async () => {
|
||||
|
||||
test('renders options with saved metric', async () => {
|
||||
render(
|
||||
<DndFilterSelect
|
||||
{...defaultProps}
|
||||
formData={{
|
||||
setup({
|
||||
formData: {
|
||||
...baseFormData,
|
||||
...TimeseriesDefaultFormData,
|
||||
metrics: ['saved_metric'],
|
||||
}}
|
||||
/>,
|
||||
},
|
||||
}),
|
||||
{
|
||||
useDnd: true,
|
||||
},
|
||||
@@ -95,17 +129,16 @@ test('renders options with saved metric', async () => {
|
||||
|
||||
test('renders options with column', async () => {
|
||||
render(
|
||||
<DndFilterSelect
|
||||
{...defaultProps}
|
||||
columns={[
|
||||
setup({
|
||||
columns: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'VARCHAR',
|
||||
type_generic: GenericDataType.STRING,
|
||||
column_name: 'Column',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
],
|
||||
}),
|
||||
{
|
||||
useDnd: true,
|
||||
},
|
||||
@@ -121,14 +154,13 @@ test('renders options with adhoc metric', async () => {
|
||||
metric_name: 'avg__num',
|
||||
});
|
||||
render(
|
||||
<DndFilterSelect
|
||||
{...defaultProps}
|
||||
formData={{
|
||||
setup({
|
||||
formData: {
|
||||
...baseFormData,
|
||||
...TimeseriesDefaultFormData,
|
||||
metrics: [adhocMetric],
|
||||
}}
|
||||
/>,
|
||||
},
|
||||
}),
|
||||
{
|
||||
useDnd: true,
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
FeatureFlag,
|
||||
hasGenericChartAxes,
|
||||
isFeatureEnabled,
|
||||
logging,
|
||||
Metric,
|
||||
@@ -27,7 +28,12 @@ import {
|
||||
SupersetClient,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { ColumnMeta, withDndFallback } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
ColumnMeta,
|
||||
isColumnMeta,
|
||||
isTemporalColumn,
|
||||
withDndFallback,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import {
|
||||
OPERATOR_ENUM_TO_OPERATOR_TYPE,
|
||||
Operators,
|
||||
@@ -50,6 +56,7 @@ import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import { ControlComponentProps } from 'src/explore/components/Control';
|
||||
import AdhocFilterControl from '../FilterControl/AdhocFilterControl';
|
||||
import DndAdhocFilterOption from './DndAdhocFilterOption';
|
||||
import { useDefaultTimeFilter } from '../DateFilterControl/utils';
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
const DND_ACCEPTED_TYPES = [
|
||||
@@ -324,6 +331,7 @@ const DndFilterSelect = (props: DndFilterSelectProps) => {
|
||||
togglePopover(true);
|
||||
}, [togglePopover]);
|
||||
|
||||
const defaultTimeFilter = useDefaultTimeFilter();
|
||||
const adhocFilter = useMemo(() => {
|
||||
if (isSavedMetric(droppedItem)) {
|
||||
return new AdhocFilter({
|
||||
@@ -346,6 +354,15 @@ const DndFilterSelect = (props: DndFilterSelectProps) => {
|
||||
config.operator = OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.IN].operation;
|
||||
config.operatorId = Operators.IN;
|
||||
}
|
||||
if (
|
||||
hasGenericChartAxes &&
|
||||
isColumnMeta(droppedItem) &&
|
||||
isTemporalColumn(droppedItem?.column_name, props.datasource)
|
||||
) {
|
||||
config.operator = Operators.TEMPORAL_RANGE;
|
||||
config.operatorId = Operators.TEMPORAL_RANGE;
|
||||
config.comparator = defaultTimeFilter;
|
||||
}
|
||||
return new AdhocFilter(config);
|
||||
}, [droppedItem]);
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ function translateToSql(adhocMetric, { useSimple } = {}) {
|
||||
const { subject, comparator } = adhocMetric;
|
||||
const operator =
|
||||
adhocMetric.operator &&
|
||||
CUSTOM_OPERATIONS.indexOf(adhocMetric.operator) >= 0
|
||||
// 'LATEST PARTITION' supported callback only
|
||||
adhocMetric.operator ===
|
||||
OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.LATEST_PARTITION].operation
|
||||
? OPERATORS_TO_SQL[adhocMetric.operator](adhocMetric)
|
||||
: OPERATORS_TO_SQL[adhocMetric.operator];
|
||||
return getSimpleSQLExpression(subject, operator, comparator);
|
||||
|
||||
@@ -18,8 +18,12 @@
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import * as redux from 'react-redux';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import AdhocFilter, {
|
||||
EXPRESSION_TYPES,
|
||||
@@ -37,6 +41,7 @@ import * as featureFlags from 'src/featureFlags';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
import { TestDataset } from '@superset-ui/chart-controls';
|
||||
import AdhocFilterEditPopoverSimpleTabContent, {
|
||||
useSimpleTabFilterProps,
|
||||
Props,
|
||||
@@ -99,10 +104,11 @@ const getAdvancedDataTypeTestProps = (overrides?: Record<string, any>) => {
|
||||
onChange,
|
||||
options: [{ type: 'DOUBLE', column_name: 'advancedDataType', id: 5 }],
|
||||
datasource: {
|
||||
id: 'test-id',
|
||||
columns: [],
|
||||
type: 'postgres',
|
||||
filter_select: false,
|
||||
...TestDataset,
|
||||
...{
|
||||
columns: [],
|
||||
filter_select: false,
|
||||
},
|
||||
},
|
||||
partitionColumn: 'test',
|
||||
...overrides,
|
||||
@@ -114,15 +120,18 @@ const getAdvancedDataTypeTestProps = (overrides?: Record<string, any>) => {
|
||||
function setup(overrides?: Record<string, any>) {
|
||||
const onChange = sinon.spy();
|
||||
const validHandler = sinon.spy();
|
||||
const spy = jest.spyOn(redux, 'useSelector');
|
||||
spy.mockReturnValue({});
|
||||
const props = {
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
onChange,
|
||||
options,
|
||||
datasource: {
|
||||
id: 'test-id',
|
||||
columns: [],
|
||||
type: 'postgres',
|
||||
filter_select: false,
|
||||
...TestDataset,
|
||||
...{
|
||||
columns: [],
|
||||
filter_select: false,
|
||||
},
|
||||
},
|
||||
partitionColumn: 'test',
|
||||
...overrides,
|
||||
@@ -372,14 +381,19 @@ fetchMock.get(ADVANCED_DATA_TYPE_ENDPOINT_INVALID, {
|
||||
values: [],
|
||||
},
|
||||
});
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
describe('AdhocFilterEditPopoverSimpleTabContent Advanced data Type Test', () => {
|
||||
const setupFilter = async (props: Props) => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<AdhocFilterEditPopoverSimpleTabContent {...props} />
|
||||
</ThemeProvider>,
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<AdhocFilterEditPopoverSimpleTabContent {...props} />
|
||||
</ThemeProvider>
|
||||
,
|
||||
</Provider>,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -19,7 +19,14 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import FormItem from 'src/components/Form/FormItem';
|
||||
import { Select } from 'src/components';
|
||||
import { t, SupersetClient, SupersetTheme, styled } from '@superset-ui/core';
|
||||
import {
|
||||
t,
|
||||
SupersetClient,
|
||||
SupersetTheme,
|
||||
styled,
|
||||
hasGenericChartAxes,
|
||||
isDefined,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
Operators,
|
||||
OPERATORS_OPTIONS,
|
||||
@@ -39,8 +46,14 @@ import { Tooltip } from 'src/components/Tooltip';
|
||||
import { Input } from 'src/components/Input';
|
||||
import { optionLabel } from 'src/utils/common';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
ColumnMeta,
|
||||
Dataset,
|
||||
isTemporalColumn,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import useAdvancedDataTypes from './useAdvancedDataTypes';
|
||||
import { useDatePickerInAdhocFilter } from '../utils';
|
||||
import { useDefaultTimeFilter } from '../../DateFilterControl/utils';
|
||||
|
||||
const StyledInput = styled(Input)`
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
@@ -88,12 +101,7 @@ export interface Props {
|
||||
adhocFilter: AdhocFilter;
|
||||
onChange: (filter: AdhocFilter) => void;
|
||||
options: ColumnType[];
|
||||
datasource: {
|
||||
id: string;
|
||||
columns: ColumnMeta[];
|
||||
type: string;
|
||||
filter_select: boolean;
|
||||
};
|
||||
datasource: Dataset;
|
||||
partitionColumn: string;
|
||||
operators?: Operators[];
|
||||
validHandler: (isValid: boolean) => void;
|
||||
@@ -106,6 +114,8 @@ export interface AdvancedDataTypesState {
|
||||
}
|
||||
|
||||
export const useSimpleTabFilterProps = (props: Props) => {
|
||||
const defaultTimeFilter = useDefaultTimeFilter();
|
||||
|
||||
const isOperatorRelevant = (operator: Operators, subject: string) => {
|
||||
const column = props.datasource.columns?.find(
|
||||
col => col.column_name === subject,
|
||||
@@ -116,10 +126,14 @@ export const useSimpleTabFilterProps = (props: Props) => {
|
||||
!!column && (column.type === 'INT' || column.type === 'INTEGER');
|
||||
const isColumnFunction = !!column && !!column.expression;
|
||||
|
||||
if (operator && CUSTOM_OPERATORS.has(operator)) {
|
||||
if (operator && operator === Operators.LATEST_PARTITION) {
|
||||
const { partitionColumn } = props;
|
||||
return partitionColumn && subject && subject === partitionColumn;
|
||||
}
|
||||
if (operator && operator === Operators.TEMPORAL_RANGE) {
|
||||
// hide the TEMPORAL_RANGE operator
|
||||
return false;
|
||||
}
|
||||
if (operator === Operators.IS_TRUE || operator === Operators.IS_FALSE) {
|
||||
return isColumnBoolean || isColumnNumber || isColumnFunction;
|
||||
}
|
||||
@@ -152,17 +166,33 @@ export const useSimpleTabFilterProps = (props: Props) => {
|
||||
subject = option.label;
|
||||
clause = CLAUSES.HAVING;
|
||||
}
|
||||
const { operator, operatorId } = props.adhocFilter;
|
||||
let { operator, operatorId, comparator } = props.adhocFilter;
|
||||
operator =
|
||||
operator && operatorId && isOperatorRelevant(operatorId, subject)
|
||||
? OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId].operation
|
||||
: null;
|
||||
if (!isDefined(operator)) {
|
||||
// if operator is `null`, use the `IN` and reset the comparator.
|
||||
operator = Operators.IN;
|
||||
operatorId = Operators.IN;
|
||||
comparator = undefined;
|
||||
}
|
||||
|
||||
if (hasGenericChartAxes && isTemporalColumn(id, props.datasource)) {
|
||||
subject = id;
|
||||
operator = Operators.TEMPORAL_RANGE;
|
||||
operatorId = Operators.TEMPORAL_RANGE;
|
||||
comparator = defaultTimeFilter;
|
||||
}
|
||||
|
||||
props.onChange(
|
||||
props.adhocFilter.duplicateWith({
|
||||
subject,
|
||||
clause,
|
||||
operator:
|
||||
operator && operatorId && isOperatorRelevant(operatorId, subject)
|
||||
? OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId].operation
|
||||
: null,
|
||||
operator,
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
operatorId,
|
||||
comparator,
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -221,12 +251,23 @@ export const useSimpleTabFilterProps = (props: Props) => {
|
||||
}),
|
||||
);
|
||||
};
|
||||
const onDatePickerChange = (columnName: string, timeRange: string) => {
|
||||
props.onChange(
|
||||
props.adhocFilter.duplicateWith({
|
||||
subject: columnName,
|
||||
operator: Operators.TEMPORAL_RANGE,
|
||||
comparator: timeRange,
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
}),
|
||||
);
|
||||
};
|
||||
return {
|
||||
onSubjectChange,
|
||||
onOperatorChange,
|
||||
onComparatorChange,
|
||||
isOperatorRelevant,
|
||||
clearOperator,
|
||||
onDatePickerChange,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -236,6 +277,7 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
|
||||
onOperatorChange,
|
||||
isOperatorRelevant,
|
||||
onComparatorChange,
|
||||
onDatePickerChange,
|
||||
} = useSimpleTabFilterProps(props);
|
||||
const [suggestions, setSuggestions] = useState<
|
||||
Record<'label' | 'value', any>[]
|
||||
@@ -343,6 +385,16 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
|
||||
const labelText =
|
||||
comparator && comparator.length > 0 && createSuggestionsPlaceholder();
|
||||
|
||||
const datePicker = useDatePickerInAdhocFilter({
|
||||
columnName: props.adhocFilter.subject,
|
||||
timeRange:
|
||||
props.adhocFilter.operator === Operators.TEMPORAL_RANGE
|
||||
? props.adhocFilter.comparator
|
||||
: undefined,
|
||||
datasource: props.datasource,
|
||||
onChange: onDatePickerChange,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const refreshComparatorSuggestions = () => {
|
||||
const { datasource } = props;
|
||||
@@ -375,7 +427,9 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
|
||||
});
|
||||
}
|
||||
};
|
||||
refreshComparatorSuggestions();
|
||||
if (!datePicker) {
|
||||
refreshComparatorSuggestions();
|
||||
}
|
||||
}, [props.adhocFilter.subject]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -481,7 +535,7 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
|
||||
return (
|
||||
<>
|
||||
{subjectComponent}
|
||||
{operatorsAndOperandComponent}
|
||||
{datePicker ?? operatorsAndOperandComponent}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterCon
|
||||
import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||
import { OptionSortType } from 'src/explore/types';
|
||||
import { Operators } from 'src/explore/constants';
|
||||
import { useGetTimeRangeLabel } from '../utils';
|
||||
|
||||
export interface AdhocFilterOptionProps {
|
||||
adhocFilter: AdhocFilter;
|
||||
@@ -51,6 +52,8 @@ export default function AdhocFilterOption({
|
||||
sections,
|
||||
operators,
|
||||
}: AdhocFilterOptionProps) {
|
||||
const { actualTimeRange, title } = useGetTimeRangeLabel(adhocFilter);
|
||||
|
||||
return (
|
||||
<AdhocFilterPopoverTrigger
|
||||
sections={sections}
|
||||
@@ -62,8 +65,8 @@ export default function AdhocFilterOption({
|
||||
partitionColumn={partitionColumn}
|
||||
>
|
||||
<OptionControlLabel
|
||||
label={adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={adhocFilter.getTooltipTitle()}
|
||||
label={actualTimeRange ?? adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={title ?? adhocFilter.getTooltipTitle()}
|
||||
onRemove={onRemoveFilter}
|
||||
onMoveLabel={onMoveLabel}
|
||||
onDropLabel={onDropLabel}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export { useGetTimeRangeLabel } from './useGetTimeRangeLabel';
|
||||
export { useDatePickerInAdhocFilter } from './useDatePickerInAdhocFilter';
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
|
||||
import { hasGenericChartAxes, t } from '@superset-ui/core';
|
||||
import { Dataset, isTemporalColumn } from '@superset-ui/chart-controls';
|
||||
import DateFilterControl from 'src/explore/components/controls/DateFilterControl/DateFilterLabel';
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
|
||||
interface DatePickerInFilterProps {
|
||||
columnName: string;
|
||||
timeRange?: string;
|
||||
datasource: Dataset;
|
||||
onChange: (columnName: string, timeRange: string) => void;
|
||||
}
|
||||
|
||||
export const useDatePickerInAdhocFilter = ({
|
||||
columnName,
|
||||
timeRange,
|
||||
datasource,
|
||||
onChange,
|
||||
}: DatePickerInFilterProps): React.ReactElement | undefined => {
|
||||
const onTimeRangeChange = (val: string) => onChange(columnName, val);
|
||||
|
||||
return hasGenericChartAxes && isTemporalColumn(columnName, datasource) ? (
|
||||
<>
|
||||
<ControlHeader label={t('Time Range')} />
|
||||
<DateFilterControl
|
||||
value={timeRange}
|
||||
name="time_range"
|
||||
onChange={onTimeRangeChange}
|
||||
overlayStyle="Modal"
|
||||
/>
|
||||
</>
|
||||
) : undefined;
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import { TestDataset } from '@superset-ui/chart-controls';
|
||||
import * as supersetCoreModule from '@superset-ui/core';
|
||||
import { useDatePickerInAdhocFilter } from './useDatePickerInAdhocFilter';
|
||||
|
||||
test('should return undefined if Generic Axis is disabled', () => {
|
||||
Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', {
|
||||
value: false,
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
useDatePickerInAdhocFilter({
|
||||
columnName: 'ds',
|
||||
datasource: TestDataset,
|
||||
onChange: jest.fn(),
|
||||
}),
|
||||
);
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return undefined if column is not temporal', () => {
|
||||
Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', {
|
||||
value: true,
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
useDatePickerInAdhocFilter({
|
||||
columnName: 'gender',
|
||||
datasource: TestDataset,
|
||||
onChange: jest.fn(),
|
||||
}),
|
||||
);
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return JSX', () => {
|
||||
Object.defineProperty(supersetCoreModule, 'hasGenericChartAxes', {
|
||||
value: true,
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
useDatePickerInAdhocFilter({
|
||||
columnName: 'ds',
|
||||
datasource: TestDataset,
|
||||
onChange: jest.fn(),
|
||||
}),
|
||||
);
|
||||
expect(result.current).not.toBeUndefined();
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import { Operators } from 'src/explore/constants';
|
||||
import * as FetchTimeRangeModule from 'src/explore/components/controls/DateFilterControl';
|
||||
import { useGetTimeRangeLabel } from './useGetTimeRangeLabel';
|
||||
import AdhocFilter, { CLAUSES, EXPRESSION_TYPES } from '../AdhocFilter';
|
||||
|
||||
test('should return empty object if operator is not TEMPORAL_RANGE', () => {
|
||||
const adhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'value',
|
||||
operator: '>',
|
||||
comparator: '10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
const { result } = renderHook(() => useGetTimeRangeLabel(adhocFilter));
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
|
||||
test('should return empty object if expressionType is SQL', () => {
|
||||
const adhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
subject: 'temporal column',
|
||||
operator: Operators.TEMPORAL_RANGE,
|
||||
comparator: 'Last week',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
const { result } = renderHook(() => useGetTimeRangeLabel(adhocFilter));
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
|
||||
test('should get "No filter" label', () => {
|
||||
const adhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'temporal column',
|
||||
operator: Operators.TEMPORAL_RANGE,
|
||||
comparator: NO_TIME_RANGE,
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
const { result } = renderHook(() => useGetTimeRangeLabel(adhocFilter));
|
||||
expect(result.current).toEqual({
|
||||
actualTimeRange: 'temporal column (No filter)',
|
||||
title: 'No filter',
|
||||
});
|
||||
});
|
||||
|
||||
test('should get actualTimeRange and title', async () => {
|
||||
jest
|
||||
.spyOn(FetchTimeRangeModule, 'fetchTimeRange')
|
||||
.mockResolvedValue({ value: 'MOCK TIME' });
|
||||
|
||||
const adhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'temporal column',
|
||||
operator: Operators.TEMPORAL_RANGE,
|
||||
comparator: 'Last week',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const { result } = await renderHook(() => useGetTimeRangeLabel(adhocFilter));
|
||||
expect(result.current).toEqual({
|
||||
actualTimeRange: 'MOCK TIME',
|
||||
title: 'Last week',
|
||||
});
|
||||
});
|
||||
|
||||
test('should get actualTimeRange and title when gets an error', async () => {
|
||||
jest
|
||||
.spyOn(FetchTimeRangeModule, 'fetchTimeRange')
|
||||
.mockResolvedValue({ error: 'MOCK ERROR' });
|
||||
|
||||
const adhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'temporal column',
|
||||
operator: Operators.TEMPORAL_RANGE,
|
||||
comparator: 'Last week',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const { result } = await renderHook(() => useGetTimeRangeLabel(adhocFilter));
|
||||
expect(result.current).toEqual({
|
||||
actualTimeRange: 'temporal column (Last week)',
|
||||
title: 'MOCK ERROR',
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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 { useEffect, useState } from 'react';
|
||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import { fetchTimeRange } from 'src/explore/components/controls/DateFilterControl';
|
||||
import { Operators } from 'src/explore/constants';
|
||||
import AdhocFilter, { EXPRESSION_TYPES } from '../AdhocFilter';
|
||||
|
||||
interface Results {
|
||||
actualTimeRange?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const useGetTimeRangeLabel = (adhocFilter: AdhocFilter): Results => {
|
||||
const [actualTimeRange, setActualTimeRange] = useState<Results>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
adhocFilter.operator !== Operators.TEMPORAL_RANGE ||
|
||||
adhocFilter.expressionType !== EXPRESSION_TYPES.SIMPLE
|
||||
) {
|
||||
setActualTimeRange({});
|
||||
}
|
||||
if (
|
||||
adhocFilter.operator === Operators.TEMPORAL_RANGE &&
|
||||
adhocFilter.comparator === NO_TIME_RANGE
|
||||
) {
|
||||
setActualTimeRange({
|
||||
actualTimeRange: `${adhocFilter.subject} (${NO_TIME_RANGE})`,
|
||||
title: NO_TIME_RANGE,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
adhocFilter.operator === Operators.TEMPORAL_RANGE &&
|
||||
adhocFilter.expressionType === EXPRESSION_TYPES.SIMPLE &&
|
||||
adhocFilter.comparator !== NO_TIME_RANGE &&
|
||||
actualTimeRange.title !== adhocFilter.comparator
|
||||
) {
|
||||
fetchTimeRange(adhocFilter.comparator, adhocFilter.subject).then(
|
||||
({ value, error }) => {
|
||||
if (error) {
|
||||
setActualTimeRange({
|
||||
actualTimeRange: `${adhocFilter.subject} (${adhocFilter.comparator})`,
|
||||
title: error,
|
||||
});
|
||||
} else {
|
||||
setActualTimeRange({
|
||||
actualTimeRange: value ?? '',
|
||||
title: adhocFilter.comparator,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}, [adhocFilter]);
|
||||
|
||||
return actualTimeRange;
|
||||
};
|
||||
Reference in New Issue
Block a user