feat(custom-tz): added the ability to set UTC as the timezone when making custom time range queries (#18011)

pull/18055/head
Ariel Salem 2020-05-11 09:15:48 -07:00 committed by GitHub
parent ebe77518a5
commit c6b2fc5d2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 22 deletions

View File

@ -494,7 +494,7 @@ workflows:
hourly-e2e:
triggers:
- schedule:
cron: '0 * * * *'
cron: "0 * * * *"
filters:
branches:
only:

View File

@ -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

View File

@ -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<Props> = ({
}
const mstp = (state: AppState, props: OwnProps) => {
const timeZone = state.app.persisted.timeZone
const timeZone = getTimeZone(state)
const checkIDs = getCheckIDs(state)
const check = getByID<Check>(state, ResourceType.Checks, props.params.checkID)

View File

@ -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)
})
})

View File

@ -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

View File

@ -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<Props> = ({
}
const mstp = (state: AppState): StateProps => {
return {timeZone: state.app.persisted.timeZone || 'Local'}
return {timeZone: getTimeZone(state)}
}
const mdtp = {onSetTimeZone: setTimeZone}

View File

@ -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

View File

@ -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,

View File

@ -7,6 +7,11 @@ import {
import {AppState} from 'src/types'
const MOCKSTATE = ({
app: {
persisted: {
timeZone: 'UTC',
},
},
currentDashboard: {
id: '',
},

View File

@ -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)
}