From c6b2fc5d2ce9b8a47d027c8afc13d31a3367196c Mon Sep 17 00:00:00 2001 From: Ariel Salem Date: Mon, 11 May 2020 09:15:48 -0700 Subject: [PATCH] feat(custom-tz): added the ability to set UTC as the timezone when making custom time range queries (#18011) --- .circleci/config.yml | 2 +- CHANGELOG.md | 10 +++ ui/src/checks/components/CheckHistory.tsx | 3 +- ui/src/dashboards/selectors/index.test.ts | 82 +++++++++++++++++-- ui/src/dashboards/selectors/index.ts | 45 +++++++++- ui/src/shared/components/TimeZoneDropdown.tsx | 5 +- ui/src/timeMachine/actions/queryBuilder.ts | 7 +- ui/src/timeMachine/components/Vis.tsx | 4 +- ui/src/variables/selectors/index.test.tsx | 5 ++ ui/src/variables/selectors/index.tsx | 7 +- 10 files changed, 148 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d1440e5799..a70db12672 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -494,7 +494,7 @@ workflows: hourly-e2e: triggers: - schedule: - cron: '0 * * * *' + cron: "0 * * * *" filters: branches: only: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a2c52c863..694e7bb3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v2.0.0-beta.11 [unreleased] + +### Features + +1. [18011](https://github.com/influxdata/influxdb/pull/18011): Integrate UTC dropdown when making custom time range query + +### Bug Fixes + +### UI Improvements + ## v2.0.0-beta.10 [2020-05-07] ### Features diff --git a/ui/src/checks/components/CheckHistory.tsx b/ui/src/checks/components/CheckHistory.tsx index 9a33c2946c..849f97cd38 100644 --- a/ui/src/checks/components/CheckHistory.tsx +++ b/ui/src/checks/components/CheckHistory.tsx @@ -21,6 +21,7 @@ import {STATUS_FIELDS} from 'src/alerting/constants/history' // Utils import {loadStatuses, getInitialState} from 'src/alerting/utils/history' import {getCheckIDs} from 'src/checks/selectors' +import {getTimeZone} from 'src/dashboards/selectors' // Types import {ResourceIDs} from 'src/checks/reducers' @@ -101,7 +102,7 @@ const CheckHistory: FC = ({ } const mstp = (state: AppState, props: OwnProps) => { - const timeZone = state.app.persisted.timeZone + const timeZone = getTimeZone(state) const checkIDs = getCheckIDs(state) const check = getByID(state, ResourceType.Checks, props.params.checkID) diff --git a/ui/src/dashboards/selectors/index.test.ts b/ui/src/dashboards/selectors/index.test.ts index 0b4b1fb49b..d36d2ac51a 100644 --- a/ui/src/dashboards/selectors/index.test.ts +++ b/ui/src/dashboards/selectors/index.test.ts @@ -1,10 +1,15 @@ // Funcs -import {getTimeRange} from 'src/dashboards/selectors/index' +import { + getTimeRange, + getTimeRangeWithTimezone, +} from 'src/dashboards/selectors/index' +import moment from 'moment' // Types import {RangeState} from 'src/dashboards/reducers/ranges' import {CurrentDashboardState} from 'src/shared/reducers/currentDashboard' -import {TimeRange} from 'src/types' +import {TimeRange, TimeZone, CustomTimeRange} from 'src/types' +import {AppState as AppPresentationState} from 'src/shared/reducers/app' // Constants import { @@ -18,14 +23,32 @@ const untypedGetTimeRangeByDashboardID = getTimeRange as (a: { currentDashboard: CurrentDashboardState }) => TimeRange +const untypedGetTimeRangeWithTimeZone = getTimeRangeWithTimezone as (a: { + ranges: RangeState + currentDashboard: CurrentDashboardState + app: AppPresentationState +}) => TimeRange + describe('Dashboards.Selector', () => { - const dashboardIDs = ['04c6f3976f4b8001', '04c6f3976f4b8000'] + const dashboardIDs = [ + '04c6f3976f4b8001', + '04c6f3976f4b8000', + '04c6f3976f4b8002', + ] + const lower = `2020-05-05T10:00:00${moment().format('Z')}` + const upper = `2020-05-05T11:00:00${moment().format('Z')}` + const customTimeRange = { + lower, + upper, + type: 'custom', + } as CustomTimeRange const ranges: RangeState = { [dashboardIDs[0]]: pastFifteenMinTimeRange, [dashboardIDs[1]]: pastHourTimeRange, + [dashboardIDs[2]]: customTimeRange, } - it('should return the the correct range when a matching dashboard ID is found', () => { + it('should return the correct range when a matching dashboard ID is found', () => { const currentDashboard = {id: dashboardIDs[0]} expect( @@ -33,7 +56,7 @@ describe('Dashboards.Selector', () => { ).toEqual(pastFifteenMinTimeRange) }) - it('should return the the default range when no matching dashboard ID is found', () => { + it('should return the default range when no matching dashboard ID is found', () => { const currentDashboard = {id: 'Oogum Boogum'} expect( @@ -41,11 +64,58 @@ describe('Dashboards.Selector', () => { ).toEqual(DEFAULT_TIME_RANGE) }) - it('should return the the default range when no ranges are passed in', () => { + it('should return the default range when no ranges are passed in', () => { const currentDashboard = {id: dashboardIDs[0]} expect( untypedGetTimeRangeByDashboardID({ranges: {}, currentDashboard}) ).toEqual(DEFAULT_TIME_RANGE) }) + + it('should return the an unmodified version of the timeRange when the timeZone is local', () => { + const currentDashboard = {id: dashboardIDs[2]} + const app: AppPresentationState = { + ephemeral: { + inPresentationMode: false, + }, + persisted: { + autoRefresh: 0, + showTemplateControlBar: false, + navBarState: 'expanded', + timeZone: 'Local' as TimeZone, + theme: 'dark', + }, + } + + expect( + untypedGetTimeRangeWithTimeZone({ranges, currentDashboard, app}) + ).toEqual(customTimeRange) + }) + + it('should return the timeRange for the same hour with a UTC timezone when the timeZone is UTC', () => { + const currentDashboard = {id: dashboardIDs[2]} + + const app: AppPresentationState = { + ephemeral: { + inPresentationMode: false, + }, + persisted: { + autoRefresh: 0, + showTemplateControlBar: false, + navBarState: 'expanded', + timeZone: 'UTC' as TimeZone, + theme: 'dark', + }, + } + + const expected = { + lower: `2020-05-05T10:00:00Z`, + upper: `2020-05-05T11:00:00Z`, + type: 'custom', + } + + expect( + untypedGetTimeRangeWithTimeZone({ranges, currentDashboard, app}) + ).toEqual(expected) + }) }) diff --git a/ui/src/dashboards/selectors/index.ts b/ui/src/dashboards/selectors/index.ts index a789e8c4d2..f34f7c21a7 100644 --- a/ui/src/dashboards/selectors/index.ts +++ b/ui/src/dashboards/selectors/index.ts @@ -1,6 +1,6 @@ import {get} from 'lodash' - -import {AppState, View, Check, ViewType, TimeRange} from 'src/types' +import moment from 'moment' +import {AppState, View, Check, ViewType, TimeRange, TimeZone} from 'src/types' import {currentContext} from 'src/shared/selectors/currentContext' // Constants @@ -15,6 +15,47 @@ export const getTimeRange = (state: AppState): TimeRange => { return state.ranges[contextID] || DEFAULT_TIME_RANGE } +export const getTimeRangeWithTimezone = (state: AppState): TimeRange => { + const timeRange = getTimeRange(state) + const timeZone = getTimeZone(state) + + const newTimeRange = {...timeRange} + if (timeRange.type === 'custom' && timeZone === 'UTC') { + // conforms dates to account to UTC with proper offset if needed + newTimeRange.lower = setTimeToUTC(newTimeRange.lower) + newTimeRange.upper = setTimeToUTC(newTimeRange.upper) + } + return newTimeRange +} + +// The purpose of this function is to set a user's custom time range selection +// from the local time to the same time in UTC if UTC is selected from the +// timezone dropdown. This is feature was original requested here: +// https://github.com/influxdata/influxdb/issues/17877 +// Example: user selected 10-11:00am and sets the dropdown to UTC +// Query should run against 10-11:00am UTC rather than querying +// 10-11:00am local time (offset depending on timezone) +export const setTimeToUTC = (date: string): string => { + const offset = new Date(date).getTimezoneOffset() + if (offset > 0) { + return moment + .utc(date) + .subtract(offset, 'minutes') + .format() + } + if (offset < 0) { + return moment + .utc(date) + .add(offset, 'minutes') + .format() + } + return moment.utc(date).format() +} + +export const getTimeZone = (state: AppState): TimeZone => { + return state.app.persisted.timeZone || 'Local' +} + export const getCheckForView = ( state: AppState, view: View diff --git a/ui/src/shared/components/TimeZoneDropdown.tsx b/ui/src/shared/components/TimeZoneDropdown.tsx index 92680cb69f..9fdace1d08 100644 --- a/ui/src/shared/components/TimeZoneDropdown.tsx +++ b/ui/src/shared/components/TimeZoneDropdown.tsx @@ -3,8 +3,9 @@ import React, {FunctionComponent} from 'react' import {connect} from 'react-redux' import {SelectDropdown, IconFont} from '@influxdata/clockface' -// Actions +// Actions & Selectors import {setTimeZone} from 'src/shared/actions/app' +import {getTimeZone} from 'src/dashboards/selectors' // Constants import {TIME_ZONES} from 'src/shared/constants/timeZones' @@ -38,7 +39,7 @@ const TimeZoneDropdown: FunctionComponent = ({ } const mstp = (state: AppState): StateProps => { - return {timeZone: state.app.persisted.timeZone || 'Local'} + return {timeZone: getTimeZone(state)} } const mdtp = {onSetTimeZone: setTimeZone} diff --git a/ui/src/timeMachine/actions/queryBuilder.ts b/ui/src/timeMachine/actions/queryBuilder.ts index 9f3b23fcee..418f6415d4 100644 --- a/ui/src/timeMachine/actions/queryBuilder.ts +++ b/ui/src/timeMachine/actions/queryBuilder.ts @@ -6,7 +6,7 @@ import {fetchDemoDataBuckets} from 'src/cloud/apis/demodata' // Utils import {getActiveQuery, getActiveTimeMachine} from 'src/timeMachine/selectors' -import {getTimeRange} from 'src/dashboards/selectors' +import {getTimeRangeWithTimezone} from 'src/dashboards/selectors' // Types import { @@ -222,7 +222,8 @@ export const loadTagSelector = (index: number) => async ( const orgID = get(foundBucket, 'orgID', getOrg(getState()).id) try { - const timeRange = getTimeRange(state) + const timeRange = getTimeRangeWithTimezone(state) + const searchTerm = getActiveTimeMachine(state).queryBuilder.tags[index] .keysSearchTerm @@ -287,7 +288,7 @@ const loadTagSelectorValues = (index: number) => async ( dispatch(setBuilderTagValuesStatus(index, RemoteDataState.Loading)) try { - const timeRange = getTimeRange(state) + const timeRange = getTimeRangeWithTimezone(state) const key = getActiveQuery(getState()).builderConfig.tags[index].key const searchTerm = getActiveTimeMachine(getState()).queryBuilder.tags[index] .valuesSearchTerm diff --git a/ui/src/timeMachine/components/Vis.tsx b/ui/src/timeMachine/components/Vis.tsx index 89cc5cc270..fbc9e831f1 100644 --- a/ui/src/timeMachine/components/Vis.tsx +++ b/ui/src/timeMachine/components/Vis.tsx @@ -21,7 +21,7 @@ import { getFillColumnsSelection, getSymbolColumnsSelection, } from 'src/timeMachine/selectors' -import {getTimeRange} from 'src/dashboards/selectors' +import {getTimeRange, getTimeZone} from 'src/dashboards/selectors' // Types import { @@ -164,7 +164,7 @@ const mstp = (state: AppState): StateProps => { const fillColumns = getFillColumnsSelection(state) const symbolColumns = getSymbolColumnsSelection(state) - const timeZone = state.app.persisted.timeZone + const timeZone = getTimeZone(state) return { loading, diff --git a/ui/src/variables/selectors/index.test.tsx b/ui/src/variables/selectors/index.test.tsx index 7f8b33eb90..e1fdbd332e 100644 --- a/ui/src/variables/selectors/index.test.tsx +++ b/ui/src/variables/selectors/index.test.tsx @@ -7,6 +7,11 @@ import { import {AppState} from 'src/types' const MOCKSTATE = ({ + app: { + persisted: { + timeZone: 'UTC', + }, + }, currentDashboard: { id: '', }, diff --git a/ui/src/variables/selectors/index.tsx b/ui/src/variables/selectors/index.tsx index 500ba8089a..cf553c1803 100644 --- a/ui/src/variables/selectors/index.tsx +++ b/ui/src/variables/selectors/index.tsx @@ -4,7 +4,7 @@ import {get} from 'lodash' // Utils import {getActiveQuery} from 'src/timeMachine/selectors' import {getRangeVariable} from 'src/variables/utils/getTimeRangeVars' -import {getTimeRange} from 'src/dashboards/selectors' +import {getTimeRange, getTimeRangeWithTimezone} from 'src/dashboards/selectors' import {getWindowPeriodVariable} from 'src/variables/utils/getWindowVars' import { TIME_RANGE_START, @@ -108,11 +108,9 @@ export const getAllVariables = ( .concat([TIME_RANGE_START, TIME_RANGE_STOP, WINDOW_PERIOD]) .reduce((prev, curr) => { prev.push(getVariable(state, curr)) - return prev }, []) .filter(v => !!v) - return vars } @@ -126,8 +124,7 @@ export const getVariable = (state: AppState, variableID: string): Variable => { } if (variableID === TIME_RANGE_START || variableID === TIME_RANGE_STOP) { - const timeRange = getTimeRange(state) - + const timeRange = getTimeRangeWithTimezone(state) vari = getRangeVariable(variableID, timeRange) }