diff --git a/superset-frontend/spec/javascripts/explore/components/DateFilterControl/utils_spec.ts b/superset-frontend/spec/javascripts/explore/components/DateFilterControl/utils_spec.ts new file mode 100644 index 00000000000..d44b08dddd9 --- /dev/null +++ b/superset-frontend/spec/javascripts/explore/components/DateFilterControl/utils_spec.ts @@ -0,0 +1,298 @@ +/** + * 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 { + customTimeRangeEncode, + customTimeRangeDecode, +} from 'src/explore/components/controls/DateFilterControl/utils'; + +describe('Custom TimeRange', () => { + describe('customTimeRangeEncode', () => { + it('1) specific : specific', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: '2021-01-20T00:00:00', + sinceMode: 'specific', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-27T00:00:00', + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual('2021-01-20T00:00:00 : 2021-01-27T00:00:00'); + }); + + it('2) specific : relative', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: '2021-01-20T00:00:00', + sinceMode: 'specific', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-20T00:00:00', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual( + '2021-01-20T00:00:00 : DATEADD(DATETIME("2021-01-20T00:00:00"), 7, day)', + ); + }); + + it('3) now : relative', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: 'now', + sinceMode: 'now', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: 'now', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual('now : DATEADD(DATETIME("now"), 7, day)'); + }); + + it('4) today : relative', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: 'today', + sinceMode: 'today', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: 'today', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual('today : DATEADD(DATETIME("today"), 7, day)'); + }); + + it('5) relative : specific', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: '2021-01-27T00:00:00', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-27T00:00:00', + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual( + 'DATEADD(DATETIME("2021-01-27T00:00:00"), -7, day) : 2021-01-27T00:00:00', + ); + }); + + it('6) relative : now', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: 'now', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: 'now', + untilMode: 'now', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual('DATEADD(DATETIME("now"), -7, day) : now'); + }); + + it('7) relative : today', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: 'today', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: 'today', + untilMode: 'today', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual('DATEADD(DATETIME("today"), -7, day) : today'); + }); + + it('8) relative : relative (now)', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: 'now', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: 'now', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }), + ).toEqual( + 'DATEADD(DATETIME("now"), -7, day) : DATEADD(DATETIME("now"), 7, day)', + ); + }); + + it('9) relative : relative (date/time)', () => { + expect( + customTimeRangeEncode({ + sinceDatetime: '2021-01-27T00:00:00', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-27T00:00:00', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'specific', + anchorValue: '2021-01-27T00:00:00', + }), + ).toEqual( + 'DATEADD(DATETIME("2021-01-27T00:00:00"), -7, day) : DATEADD(DATETIME("2021-01-27T00:00:00"), 7, day)', + ); + }); + }); + + describe('customTimeRangeDecode', () => { + it('1) specific : specific', () => { + expect( + customTimeRangeDecode('2021-01-20T00:00:00 : 2021-01-27T00:00:00'), + ).toEqual({ + customRange: { + sinceDatetime: '2021-01-20T00:00:00', + sinceMode: 'specific', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-27T00:00:00', + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: true, + }); + }); + + it('2) specific : relative', () => { + expect( + customTimeRangeDecode( + '2021-01-20T00:00:00 : DATEADD(DATETIME("2021-01-20T00:00:00"), 7, day)', + ), + ).toEqual({ + customRange: { + sinceDatetime: '2021-01-20T00:00:00', + sinceMode: 'specific', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-20T00:00:00', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: true, + }); + }); + + it('3) relative : specific', () => { + expect( + customTimeRangeDecode( + 'DATEADD(DATETIME("2021-01-27T00:00:00"), -7, day) : 2021-01-27T00:00:00', + ), + ).toEqual({ + customRange: { + sinceDatetime: '2021-01-27T00:00:00', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-27T00:00:00', + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: true, + }); + }); + + it('4) relative : relative (now)', () => { + expect( + customTimeRangeDecode( + 'DATEADD(DATETIME("now"), -7, day) : DATEADD(DATETIME("now"), 7, day)', + ), + ).toEqual({ + customRange: { + sinceDatetime: 'now', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: 'now', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: true, + }); + }); + + it('5) relative : relative (date/time)', () => { + expect( + customTimeRangeDecode( + 'DATEADD(DATETIME("2021-01-27T00:00:00"), -7, day) : DATEADD(DATETIME("2021-01-27T00:00:00"), 7, day)', + ), + ).toEqual({ + customRange: { + sinceDatetime: '2021-01-27T00:00:00', + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: '2021-01-27T00:00:00', + untilMode: 'relative', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'specific', + anchorValue: '2021-01-27T00:00:00', + }, + matchedFlag: true, + }); + }); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/frame/CustomFrame.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/frame/CustomFrame.tsx index 3f15873bdaf..08045ee1c10 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/frame/CustomFrame.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/frame/CustomFrame.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { t } from '@superset-ui/core'; -import moment, { Moment } from 'moment'; +import { Moment } from 'moment'; import { isInteger } from 'lodash'; import { Col, @@ -36,23 +36,17 @@ import { MOMENT_FORMAT, MIDNIGHT, } from '../constants'; -import { customTimeRangeDecode, customTimeRangeEncode } from '../utils'; +import { + customTimeRangeDecode, + customTimeRangeEncode, + dttmToMoment, +} from '../utils'; import { CustomRangeKey, SelectOptionType, FrameComponentProps, } from '../types'; -const dttmToMoment = (dttm: string): Moment => { - if (dttm === 'now') { - return moment().utc().startOf('second'); - } - if (dttm === 'today') { - return moment().utc().startOf('day'); - } - return moment(dttm); -}; - export function CustomFrame(props: FrameComponentProps) { const { customRange, matchedFlag } = customTimeRangeDecode(props.value); if (!matchedFlag) { diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts index 1d400de29d6..0fcdfca786e 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts @@ -57,7 +57,7 @@ export type CustomRangeType = { sinceDatetime: string; sinceGrain: DateTimeGrainType; sinceGrainValue: number; - untilMode: 'specific' | 'relative' | 'now' | 'today'; + untilMode: DateTimeModeType; untilDatetime: string; untilGrain: DateTimeGrainType; untilGrainValue: number; diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/utils.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/utils.ts index 0cdf64e6890..4deb81b0a79 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/utils.ts +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/utils.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import moment, { Moment } from 'moment'; import { SEPARATOR } from 'src/explore/dateFilterUtils'; import { CustomRangeDecodeType, @@ -23,7 +24,7 @@ import { DateTimeGrainType, DateTimeModeType, } from './types'; -import { SEVEN_DAYS_AGO, MIDNIGHT } from './constants'; +import { SEVEN_DAYS_AGO, MIDNIGHT, MOMENT_FORMAT } from './constants'; /** * RegExp to test a string for a full ISO 8601 Date @@ -60,6 +61,19 @@ const defaultCustomRange: CustomRangeType = { }; const SPECIFIC_MODE = ['specific', 'today', 'now']; +export const dttmToMoment = (dttm: string): Moment => { + if (dttm === 'now') { + return moment().utc().startOf('second'); + } + if (dttm === 'today') { + return moment().utc().startOf('day'); + } + return moment(dttm); +}; + +export const dttmToString = (dttm: string): string => + dttmToMoment(dttm).format(MOMENT_FORMAT); + export const customTimeRangeDecode = ( timeRange: string, ): CustomRangeDecodeType => { @@ -104,6 +118,7 @@ export const customTimeRangeDecode = ( ...defaultCustomRange, sinceGrain: grain as DateTimeGrainType, sinceGrainValue: parseInt(grainValue, 10), + sinceDatetime: dttm, untilDatetime: dttm, sinceMode: 'relative', untilMode, @@ -129,6 +144,7 @@ export const customTimeRangeDecode = ( untilGrain: grain as DateTimeGrainType, untilGrainValue: parseInt(grainValue, 10), sinceDatetime: dttm, + untilDatetime: dttm, untilMode: 'relative', sinceMode, }, @@ -150,8 +166,10 @@ export const customTimeRangeDecode = ( ...defaultCustomRange, sinceGrain: sinceGrain as DateTimeGrainType, sinceGrainValue: parseInt(sinceGrainValue, 10), + sinceDatetime: sinceDttm, untilGrain: untilGrain as DateTimeGrainType, untilGrainValue: parseInt(untilGrainValue, 10), + untilDatetime: untileDttm, anchorValue: sinceDttm, sinceMode: 'relative', untilMode: 'relative', @@ -183,31 +201,35 @@ export const customTimeRangeEncode = (customRange: CustomRangeType): string => { } = { ...customRange }; // specific : specific if (SPECIFIC_MODE.includes(sinceMode) && SPECIFIC_MODE.includes(untilMode)) { - const since = sinceMode === 'specific' ? sinceDatetime : sinceMode; - const until = untilMode === 'specific' ? untilDatetime : untilMode; + const since = + sinceMode === 'specific' ? dttmToString(sinceDatetime) : sinceMode; + const until = + untilMode === 'specific' ? dttmToString(untilDatetime) : untilMode; return `${since} : ${until}`; } // specific : relative if (SPECIFIC_MODE.includes(sinceMode) && untilMode === 'relative') { - const since = sinceMode === 'specific' ? sinceDatetime : sinceMode; + const since = + sinceMode === 'specific' ? dttmToString(sinceDatetime) : sinceMode; const until = `DATEADD(DATETIME("${since}"), ${untilGrainValue}, ${untilGrain})`; return `${since} : ${until}`; } // relative : specific if (sinceMode === 'relative' && SPECIFIC_MODE.includes(untilMode)) { - const until = untilMode === 'specific' ? untilDatetime : untilMode; + const until = + untilMode === 'specific' ? dttmToString(untilDatetime) : untilMode; const since = `DATEADD(DATETIME("${until}"), ${-Math.abs( sinceGrainValue, - )}, ${sinceGrain})`; // eslint-disable-line + )}, ${sinceGrain})`; return `${since} : ${until}`; } // relative : relative const since = `DATEADD(DATETIME("${anchorValue}"), ${-Math.abs( sinceGrainValue, - )}, ${sinceGrain})`; // eslint-disable-line + )}, ${sinceGrain})`; const until = `DATEADD(DATETIME("${anchorValue}"), ${untilGrainValue}, ${untilGrain})`; return `${since} : ${until}`; };