feat: add Current time-range options for time filter (#28637)
Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
parent
f2e020e398
commit
066f6b1f8a
|
|
@ -53,6 +53,7 @@ import {
|
||||||
AdvancedFrame,
|
AdvancedFrame,
|
||||||
DateLabel,
|
DateLabel,
|
||||||
} from './components';
|
} from './components';
|
||||||
|
import { CurrentCalendarFrame } from './components/CurrentCalendarFrame';
|
||||||
|
|
||||||
const StyledRangeType = styled(Select)`
|
const StyledRangeType = styled(Select)`
|
||||||
width: 272px;
|
width: 272px;
|
||||||
|
|
@ -201,6 +202,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||||
if (
|
if (
|
||||||
guessedFrame === 'Common' ||
|
guessedFrame === 'Common' ||
|
||||||
guessedFrame === 'Calendar' ||
|
guessedFrame === 'Calendar' ||
|
||||||
|
guessedFrame === 'Current' ||
|
||||||
guessedFrame === 'No filter'
|
guessedFrame === 'No filter'
|
||||||
) {
|
) {
|
||||||
setActualTimeRange(value);
|
setActualTimeRange(value);
|
||||||
|
|
@ -296,6 +298,12 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||||
{frame === 'Calendar' && (
|
{frame === 'Calendar' && (
|
||||||
<CalendarFrame value={timeRangeValue} onChange={setTimeRangeValue} />
|
<CalendarFrame value={timeRangeValue} onChange={setTimeRangeValue} />
|
||||||
)}
|
)}
|
||||||
|
{frame === 'Current' && (
|
||||||
|
<CurrentCalendarFrame
|
||||||
|
value={timeRangeValue}
|
||||||
|
onChange={setTimeRangeValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{frame === 'Advanced' && (
|
{frame === 'Advanced' && (
|
||||||
<AdvancedFrame value={timeRangeValue} onChange={setTimeRangeValue} />
|
<AdvancedFrame value={timeRangeValue} onChange={setTimeRangeValue} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* 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, { useEffect } from 'react';
|
||||||
|
import { t } from '@superset-ui/core';
|
||||||
|
import { Radio } from 'src/components/Radio';
|
||||||
|
import {
|
||||||
|
CURRENT_RANGE_OPTIONS,
|
||||||
|
CURRENT_CALENDAR_RANGE_SET,
|
||||||
|
} from 'src/explore/components/controls/DateFilterControl/utils';
|
||||||
|
import { CurrentRangeType, CurrentWeek, FrameComponentProps } from '../types';
|
||||||
|
|
||||||
|
export function CurrentCalendarFrame({ onChange, value }: FrameComponentProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!CURRENT_CALENDAR_RANGE_SET.has(value as CurrentRangeType)) {
|
||||||
|
onChange(CurrentWeek);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
if (!CURRENT_CALENDAR_RANGE_SET.has(value as CurrentRangeType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="section-title">
|
||||||
|
{t('Configure Time Range: Current...')}
|
||||||
|
</div>
|
||||||
|
<Radio.Group
|
||||||
|
value={value}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
let newValue = e.target.value;
|
||||||
|
// Sanitization: Trim whitespace
|
||||||
|
newValue = newValue.trim();
|
||||||
|
// Validation: Check if the value is non-empty
|
||||||
|
if (newValue === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange(newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{CURRENT_RANGE_OPTIONS.map(({ value, label }) => (
|
||||||
|
<Radio key={value} value={value} className="vertical-radio">
|
||||||
|
{label}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
export { CommonFrame } from './CommonFrame';
|
export { CommonFrame } from './CommonFrame';
|
||||||
export { CalendarFrame } from './CalendarFrame';
|
export { CalendarFrame } from './CalendarFrame';
|
||||||
|
export { CurrentCalendarFrame } from './CurrentCalendarFrame';
|
||||||
export { CustomFrame } from './CustomFrame';
|
export { CustomFrame } from './CustomFrame';
|
||||||
export { AdvancedFrame } from './AdvancedFrame';
|
export { AdvancedFrame } from './AdvancedFrame';
|
||||||
export { DateLabel } from './DateLabel';
|
export { DateLabel } from './DateLabel';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* 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 { render } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom/extend-expect'; // For advanced DOM assertions
|
||||||
|
import { CurrentCalendarFrame } from '../components/CurrentCalendarFrame';
|
||||||
|
import { CurrentWeek } from '../types';
|
||||||
|
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
|
||||||
|
test('calls onChange(CurrentWeek) when value is invalid', () => {
|
||||||
|
render(<CurrentCalendarFrame onChange={mockOnChange} value="InvalidValue" />);
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith(CurrentWeek);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null if value is not a valid CurrentRangeType', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CurrentCalendarFrame onChange={mockOnChange} value="InvalidValue" />,
|
||||||
|
);
|
||||||
|
expect(container.childNodes.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
@ -24,6 +24,7 @@ export type SelectOptionType = {
|
||||||
export type FrameType =
|
export type FrameType =
|
||||||
| 'Common'
|
| 'Common'
|
||||||
| 'Calendar'
|
| 'Calendar'
|
||||||
|
| 'Current'
|
||||||
| 'Custom'
|
| 'Custom'
|
||||||
| 'Advanced'
|
| 'Advanced'
|
||||||
| 'No filter';
|
| 'No filter';
|
||||||
|
|
@ -85,6 +86,18 @@ export type CalendarRangeType =
|
||||||
| typeof PreviousCalendarMonth
|
| typeof PreviousCalendarMonth
|
||||||
| typeof PreviousCalendarYear;
|
| typeof PreviousCalendarYear;
|
||||||
|
|
||||||
|
export const CurrentDay = 'Current day';
|
||||||
|
export const CurrentWeek = 'Current week';
|
||||||
|
export const CurrentMonth = 'Current month';
|
||||||
|
export const CurrentYear = 'Current year';
|
||||||
|
export const CurrentQuarter = 'Current quarter';
|
||||||
|
export type CurrentRangeType =
|
||||||
|
| typeof CurrentDay
|
||||||
|
| typeof CurrentWeek
|
||||||
|
| typeof CurrentMonth
|
||||||
|
| typeof CurrentQuarter
|
||||||
|
| typeof CurrentYear;
|
||||||
|
|
||||||
export type FrameComponentProps = {
|
export type FrameComponentProps = {
|
||||||
onChange: (timeRange: string) => void;
|
onChange: (timeRange: string) => void;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,18 @@ import {
|
||||||
PreviousCalendarYear,
|
PreviousCalendarYear,
|
||||||
CommonRangeType,
|
CommonRangeType,
|
||||||
CalendarRangeType,
|
CalendarRangeType,
|
||||||
|
CurrentRangeType,
|
||||||
|
CurrentWeek,
|
||||||
|
CurrentMonth,
|
||||||
|
CurrentYear,
|
||||||
|
CurrentQuarter,
|
||||||
|
CurrentDay,
|
||||||
} from 'src/explore/components/controls/DateFilterControl/types';
|
} from 'src/explore/components/controls/DateFilterControl/types';
|
||||||
|
|
||||||
export const FRAME_OPTIONS: SelectOptionType[] = [
|
export const FRAME_OPTIONS: SelectOptionType[] = [
|
||||||
{ value: 'Common', label: t('Last') },
|
{ value: 'Common', label: t('Last') },
|
||||||
{ value: 'Calendar', label: t('Previous') },
|
{ value: 'Calendar', label: t('Previous') },
|
||||||
|
{ value: 'Current', label: t('Current') },
|
||||||
{ value: 'Custom', label: t('Custom') },
|
{ value: 'Custom', label: t('Custom') },
|
||||||
{ value: 'Advanced', label: t('Advanced') },
|
{ value: 'Advanced', label: t('Advanced') },
|
||||||
{ value: 'No filter', label: t('No filter') },
|
{ value: 'No filter', label: t('No filter') },
|
||||||
|
|
@ -48,16 +55,24 @@ export const COMMON_RANGE_VALUES_SET = new Set(
|
||||||
|
|
||||||
export const CALENDAR_RANGE_OPTIONS: SelectOptionType[] = [
|
export const CALENDAR_RANGE_OPTIONS: SelectOptionType[] = [
|
||||||
{ value: PreviousCalendarWeek, label: t('previous calendar week') },
|
{ value: PreviousCalendarWeek, label: t('previous calendar week') },
|
||||||
{
|
{ value: PreviousCalendarMonth, label: t('previous calendar month') },
|
||||||
value: PreviousCalendarMonth,
|
|
||||||
label: t('previous calendar month'),
|
|
||||||
},
|
|
||||||
{ value: PreviousCalendarYear, label: t('previous calendar year') },
|
{ value: PreviousCalendarYear, label: t('previous calendar year') },
|
||||||
];
|
];
|
||||||
export const CALENDAR_RANGE_VALUES_SET = new Set(
|
export const CALENDAR_RANGE_VALUES_SET = new Set(
|
||||||
CALENDAR_RANGE_OPTIONS.map(({ value }) => value),
|
CALENDAR_RANGE_OPTIONS.map(({ value }) => value),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const CURRENT_RANGE_OPTIONS: SelectOptionType[] = [
|
||||||
|
{ value: CurrentDay, label: t('Current day') },
|
||||||
|
{ value: CurrentWeek, label: t('Current week') },
|
||||||
|
{ value: CurrentMonth, label: t('Current month') },
|
||||||
|
{ value: CurrentQuarter, label: t('Current quarter') },
|
||||||
|
{ value: CurrentYear, label: t('Current year') },
|
||||||
|
];
|
||||||
|
export const CURRENT_RANGE_VALUES_SET = new Set(
|
||||||
|
CURRENT_RANGE_OPTIONS.map(({ value }) => value),
|
||||||
|
);
|
||||||
|
|
||||||
const GRAIN_OPTIONS = [
|
const GRAIN_OPTIONS = [
|
||||||
{ value: 'second', label: (rel: string) => t('Seconds %s', rel) },
|
{ value: 'second', label: (rel: string) => t('Seconds %s', rel) },
|
||||||
{ value: 'minute', label: (rel: string) => t('Minutes %s', rel) },
|
{ value: 'minute', label: (rel: string) => t('Minutes %s', rel) },
|
||||||
|
|
@ -107,6 +122,14 @@ export const CALENDAR_RANGE_SET: Set<CalendarRangeType> = new Set([
|
||||||
PreviousCalendarYear,
|
PreviousCalendarYear,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const CURRENT_CALENDAR_RANGE_SET: Set<CurrentRangeType> = new Set([
|
||||||
|
CurrentDay,
|
||||||
|
CurrentWeek,
|
||||||
|
CurrentMonth,
|
||||||
|
CurrentQuarter,
|
||||||
|
CurrentYear,
|
||||||
|
]);
|
||||||
|
|
||||||
export const MOMENT_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss';
|
export const MOMENT_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss';
|
||||||
export const SEVEN_DAYS_AGO = moment()
|
export const SEVEN_DAYS_AGO = moment()
|
||||||
.utc()
|
.utc()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
COMMON_RANGE_VALUES_SET,
|
COMMON_RANGE_VALUES_SET,
|
||||||
CALENDAR_RANGE_VALUES_SET,
|
CALENDAR_RANGE_VALUES_SET,
|
||||||
|
CURRENT_RANGE_VALUES_SET,
|
||||||
customTimeRangeDecode,
|
customTimeRangeDecode,
|
||||||
} from '.';
|
} from '.';
|
||||||
import { FrameType } from '../types';
|
import { FrameType } from '../types';
|
||||||
|
|
@ -32,6 +33,9 @@ export const guessFrame = (timeRange: string): FrameType => {
|
||||||
if (CALENDAR_RANGE_VALUES_SET.has(timeRange)) {
|
if (CALENDAR_RANGE_VALUES_SET.has(timeRange)) {
|
||||||
return 'Calendar';
|
return 'Calendar';
|
||||||
}
|
}
|
||||||
|
if (CURRENT_RANGE_VALUES_SET.has(timeRange)) {
|
||||||
|
return 'Current';
|
||||||
|
}
|
||||||
if (timeRange === NO_TIME_RANGE) {
|
if (timeRange === NO_TIME_RANGE) {
|
||||||
return 'No filter';
|
return 'No filter';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,36 @@ def get_since_until( # pylint: disable=too-many-arguments,too-many-locals,too-m
|
||||||
and separator not in time_range
|
and separator not in time_range
|
||||||
):
|
):
|
||||||
time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, YEAR), YEAR) : DATETRUNC(DATETIME('today'), YEAR)" # pylint: disable=line-too-long,useless-suppression
|
time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, YEAR), YEAR) : DATETRUNC(DATETIME('today'), YEAR)" # pylint: disable=line-too-long,useless-suppression
|
||||||
|
if (
|
||||||
|
time_range
|
||||||
|
and time_range.startswith("Current day")
|
||||||
|
and separator not in time_range
|
||||||
|
):
|
||||||
|
time_range = "DATETRUNC(DATEADD(DATETIME('today'), 0, DAY), DAY) : DATETRUNC(DATEADD(DATETIME('today'), 1, DAY), DAY)" # pylint: disable=line-too-long,useless-suppression
|
||||||
|
if (
|
||||||
|
time_range
|
||||||
|
and time_range.startswith("Current week")
|
||||||
|
and separator not in time_range
|
||||||
|
):
|
||||||
|
time_range = "DATETRUNC(DATEADD(DATETIME('today'), 0, WEEK), WEEK) : DATETRUNC(DATEADD(DATETIME('today'), 1, WEEK), WEEK)" # pylint: disable=line-too-long,useless-suppression
|
||||||
|
if (
|
||||||
|
time_range
|
||||||
|
and time_range.startswith("Current month")
|
||||||
|
and separator not in time_range
|
||||||
|
):
|
||||||
|
time_range = "DATETRUNC(DATEADD(DATETIME('today'), 0, MONTH), MONTH) : DATETRUNC(DATEADD(DATETIME('today'), 1, MONTH), MONTH)" # pylint: disable=line-too-long,useless-suppression
|
||||||
|
if (
|
||||||
|
time_range
|
||||||
|
and time_range.startswith("Current quarter")
|
||||||
|
and separator not in time_range
|
||||||
|
):
|
||||||
|
time_range = "DATETRUNC(DATEADD(DATETIME('today'), 0, QUARTER), QUARTER) : DATETRUNC(DATEADD(DATETIME('today'), 1, QUARTER), QUARTER)" # pylint: disable=line-too-long,useless-suppression
|
||||||
|
if (
|
||||||
|
time_range
|
||||||
|
and time_range.startswith("Current year")
|
||||||
|
and separator not in time_range
|
||||||
|
):
|
||||||
|
time_range = "DATETRUNC(DATEADD(DATETIME('today'), 0, YEAR), YEAR) : DATETRUNC(DATEADD(DATETIME('today'), 1, YEAR), YEAR)" # pylint: disable=line-too-long,useless-suppression
|
||||||
|
|
||||||
if time_range and separator in time_range:
|
if time_range and separator in time_range:
|
||||||
time_range_lookup = [
|
time_range_lookup = [
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,26 @@ def test_get_since_until() -> None:
|
||||||
expected = datetime(2015, 1, 1, 0, 0, 0), datetime(2016, 1, 1, 0, 0, 0)
|
expected = datetime(2015, 1, 1, 0, 0, 0), datetime(2016, 1, 1, 0, 0, 0)
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
|
result = get_since_until("Current day")
|
||||||
|
expected = datetime(2016, 11, 7, 0, 0, 0), datetime(2016, 11, 8, 0, 0, 0)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
result = get_since_until("Current week")
|
||||||
|
expected = datetime(2016, 11, 7, 0, 0, 0), datetime(2016, 11, 14, 0, 0, 0)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
result = get_since_until("Current month")
|
||||||
|
expected = datetime(2016, 11, 1, 0, 0, 0), datetime(2016, 12, 1, 0, 0, 0)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
result = get_since_until("Current quarter")
|
||||||
|
expected = datetime(2016, 10, 1, 0, 0, 0), datetime(2017, 1, 1, 0, 0, 0)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
result = get_since_until("Current year")
|
||||||
|
expected = expected = datetime(2016, 1, 1, 0, 0, 0), datetime(2017, 1, 1, 0, 0, 0)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
# Tests for our new instant_time_comparison logic and Feature Flag off
|
# Tests for our new instant_time_comparison logic and Feature Flag off
|
||||||
result = get_since_until(
|
result = get_since_until(
|
||||||
time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00",
|
time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue