From 08ecae96a65c078fb1be504d211f41b43da63153 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 11:41:47 -0700 Subject: [PATCH 01/24] Introduce dashTime reducer v1 --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 22 +++++++++++++++++++ ui/src/dashboards/actions/index.js | 8 +++++++ ui/src/dashboards/reducers/dashTimeV1.js | 20 +++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 ui/spec/dashboards/reducers/dashTimeV1Spec.js create mode 100644 ui/src/dashboards/reducers/dashTimeV1.js diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js new file mode 100644 index 000000000..54aaf8eea --- /dev/null +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -0,0 +1,22 @@ +import reducer from 'src/dashboards/reducers/dashTimeV1' +import {setDashTimeV1} from 'src/dashboards/actions/index' + +describe.only('Dashboards.Reducers.DashTimeV1', () => { + it('can load initial state', () => { + const noopAction = () => ({type: 'NOOP'}) + const actual = reducer(undefined, noopAction) + const expected = {dashTimeV1: []} + + expect(actual).to.deep.equal(expected) + }) + + it('can set a dashboard time', () => { + const dashboardID = 1 + const timeRange = {upper: null, lower: 'now() - 15m'} + + const actual = reducer(undefined, setDashTimeV1(dashboardID, timeRange)) + const expected = [{dashboardID, timeRange}] + + expect(actual.dashTimeV1).to.deep.equal(expected) + }) +}) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 24eca86ed..0abc37d6e 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -28,6 +28,14 @@ export const loadDashboards = (dashboards, dashboardID) => ({ }, }) +export const setDashTimeV1 = (dashboardID, timeRange) => ({ + type: 'SET_DASHBOARD_TIME_RANGE_V1', + payload: { + dashboardID, + timeRange, + }, +}) + export const setTimeRange = timeRange => ({ type: 'SET_DASHBOARD_TIME_RANGE', payload: { diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js new file mode 100644 index 000000000..15bfe4708 --- /dev/null +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -0,0 +1,20 @@ +const initialState = { + dashTimeV1: [], +} + +const dashTimeV1 = (state = initialState, action) => { + switch (action.type) { + case 'SET_DASHBOARD_TIME_RANGE_V1': { + const {dashboardID, timeRange} = action.payload + const newState = { + dashTimeV1: [...state.dashTimeV1, {dashboardID, timeRange}], + } + + return {...state, ...newState} + } + } + + return state +} + +export default dashTimeV1 From 44b7a88a1da0f62df71af56f151d472c7d4d952b Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 11:43:24 -0700 Subject: [PATCH 02/24] Change set to add --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 4 ++-- ui/src/dashboards/actions/index.js | 4 ++-- ui/src/dashboards/reducers/dashTimeV1.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js index 54aaf8eea..de6b82daa 100644 --- a/ui/spec/dashboards/reducers/dashTimeV1Spec.js +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -1,5 +1,5 @@ import reducer from 'src/dashboards/reducers/dashTimeV1' -import {setDashTimeV1} from 'src/dashboards/actions/index' +import {addDashTimeV1} from 'src/dashboards/actions/index' describe.only('Dashboards.Reducers.DashTimeV1', () => { it('can load initial state', () => { @@ -14,7 +14,7 @@ describe.only('Dashboards.Reducers.DashTimeV1', () => { const dashboardID = 1 const timeRange = {upper: null, lower: 'now() - 15m'} - const actual = reducer(undefined, setDashTimeV1(dashboardID, timeRange)) + const actual = reducer(undefined, addDashTimeV1(dashboardID, timeRange)) const expected = [{dashboardID, timeRange}] expect(actual.dashTimeV1).to.deep.equal(expected) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 0abc37d6e..e87745699 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -28,8 +28,8 @@ export const loadDashboards = (dashboards, dashboardID) => ({ }, }) -export const setDashTimeV1 = (dashboardID, timeRange) => ({ - type: 'SET_DASHBOARD_TIME_RANGE_V1', +export const addDashTimeV1 = (dashboardID, timeRange) => ({ + type: 'ADD_DASHBOARD_TIME_RANGE_V1', payload: { dashboardID, timeRange, diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 15bfe4708..f81f32e08 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -4,7 +4,7 @@ const initialState = { const dashTimeV1 = (state = initialState, action) => { switch (action.type) { - case 'SET_DASHBOARD_TIME_RANGE_V1': { + case 'ADD_DASHBOARD_TIME_RANGE_V1': { const {dashboardID, timeRange} = action.payload const newState = { dashTimeV1: [...state.dashTimeV1, {dashboardID, timeRange}], From 4de564ef6a52152e01e6709c475a366b83c5dcd7 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 12:23:11 -0700 Subject: [PATCH 03/24] Add delete and update dashTime actions and reducers --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 51 +++++++++++++++---- ui/src/dashboards/actions/index.js | 9 ++++ ui/src/dashboards/reducers/dashTimeV1.js | 24 +++++++-- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js index de6b82daa..ff7680696 100644 --- a/ui/spec/dashboards/reducers/dashTimeV1Spec.js +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -1,22 +1,55 @@ import reducer from 'src/dashboards/reducers/dashTimeV1' -import {addDashTimeV1} from 'src/dashboards/actions/index' +import { + addDashTimeV1, + updateDashTimeV1, + deleteDashboard, +} from 'src/dashboards/actions/index' + +const initialState = { + ranges: [], +} + +const emptyState = undefined +const dashboardID = 1 +const timeRange = {upper: null, lower: 'now() - 15m'} describe.only('Dashboards.Reducers.DashTimeV1', () => { it('can load initial state', () => { const noopAction = () => ({type: 'NOOP'}) - const actual = reducer(undefined, noopAction) - const expected = {dashTimeV1: []} + const actual = reducer(emptyState, noopAction) + const expected = {ranges: []} expect(actual).to.deep.equal(expected) }) - it('can set a dashboard time', () => { - const dashboardID = 1 - const timeRange = {upper: null, lower: 'now() - 15m'} - - const actual = reducer(undefined, addDashTimeV1(dashboardID, timeRange)) + it('can add a dashboard time', () => { + const actual = reducer(emptyState, addDashTimeV1(dashboardID, timeRange)) const expected = [{dashboardID, timeRange}] - expect(actual.dashTimeV1).to.deep.equal(expected) + expect(actual.ranges).to.deep.equal(expected) + }) + + it('can delete a dashboard time range', () => { + const state = { + ranges: [{dashboardID, timeRange}], + } + + const actual = reducer(state, deleteDashboard({}, dashboardID)) + const expected = [] + + expect(actual.ranges).to.deep.equal(expected) + }) + + it('can updated a dashboard time range', () => { + const state = { + ranges: [{dashboardID, timeRange}], + } + + const newTimeRange = {upper: '2017-10-07 12:05', lower: '2017-10-05 12:04'} + + const actual = reducer(state, updateDashTimeV1(dashboardID, newTimeRange)) + const expected = [{dashboardID, timeRange: newTimeRange}] + + expect(actual.ranges).to.deep.equal(expected) }) }) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index e87745699..4881f7d5a 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -36,6 +36,14 @@ export const addDashTimeV1 = (dashboardID, timeRange) => ({ }, }) +export const updateDashTimeV1 = (dashboardID, timeRange) => ({ + type: 'UPDATE_DASHBOARD_TIME_V1', + payload: { + dashboardID, + timeRange, + }, +}) + export const setTimeRange = timeRange => ({ type: 'SET_DASHBOARD_TIME_RANGE', payload: { @@ -54,6 +62,7 @@ export const deleteDashboard = dashboard => ({ type: 'DELETE_DASHBOARD', payload: { dashboard, + dashboardID: dashboard.id, }, }) diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index f81f32e08..2e8fd8c00 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -1,16 +1,30 @@ const initialState = { - dashTimeV1: [], + ranges: [], } const dashTimeV1 = (state = initialState, action) => { switch (action.type) { case 'ADD_DASHBOARD_TIME_RANGE_V1': { const {dashboardID, timeRange} = action.payload - const newState = { - dashTimeV1: [...state.dashTimeV1, {dashboardID, timeRange}], - } + const ranges = [...state.ranges, {dashboardID, timeRange}] - return {...state, ...newState} + return {...state, ranges} + } + + case 'DELETE_DASHBOARD': { + const {dashboardID} = action.payload + const ranges = state.ranges.filter(d => d.id !== dashboardID) + + return {...state, ranges} + } + + case 'UPDATE_DASHBOARD_TIME_V1': { + const {dashboardID, timeRange} = action.payload + const ranges = state.ranges.map( + d => (d.dashboardID === dashboardID ? {dashboardID, timeRange} : d) + ) + + return {...state, ranges} } } From 692ae340763e9e370cbb121c0f005efe5b574765 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 14:07:29 -0700 Subject: [PATCH 04/24] Introduce dashTimeV1 state to DashboardPage --- ui/src/dashboards/actions/index.js | 9 +++++++- ui/src/dashboards/containers/DashboardPage.js | 20 ++++++++++++----- ui/src/dashboards/reducers/dashTimeV1.js | 22 +++++++++++++++++-- .../data_explorer/containers/DataExplorer.js | 1 + ui/src/store/configureStore.js | 2 ++ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 4881f7d5a..7093f13a1 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -28,8 +28,15 @@ export const loadDashboards = (dashboards, dashboardID) => ({ }, }) +export const loadDeafaultDashTimeV1 = dashboardID => ({ + type: 'ADD_DASHBOARD_TIME_V1', + payload: { + dashboardID, + }, +}) + export const addDashTimeV1 = (dashboardID, timeRange) => ({ - type: 'ADD_DASHBOARD_TIME_RANGE_V1', + type: 'ADD_DASHBOARD_TIME_V1', payload: { dashboardID, timeRange, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 60f6e8168..526b38e7e 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -22,6 +22,8 @@ import { } from 'shared/actions/app' import {presentationButtonDispatcher} from 'shared/dispatchers' +const defaultTimeRange = {upper: null, lower: 'now() - 15m'} + class DashboardPage extends Component { constructor(props) { super(props) @@ -48,7 +50,6 @@ class DashboardPage extends Component { const dashboards = await getDashboardsAsync() const dashboard = dashboards.find(d => d.id === +dashboardID) - // Refresh and persists influxql generated template variable values await updateTempVarValues(source, dashboard) await putDashboardByID(dashboardID) @@ -81,8 +82,9 @@ class DashboardPage extends Component { this.setState({selectedCell: cell}) } - handleChooseTimeRange = timeRange => { - this.props.dashboardActions.setTimeRange(timeRange) + handleChooseTimeRange = ({upper, lower}) => { + const {params: {dashboardID}, dashboardActions} = this.props + dashboardActions.updateDashTimeV1(+dashboardID, {upper, lower}) } handleUpdatePosition = cells => { @@ -401,7 +403,7 @@ DashboardPage.propTypes = { handleChooseAutoRefresh: func.isRequired, autoRefresh: number.isRequired, templateControlBarVisibilityToggled: func.isRequired, - timeRange: shape({}).isRequired, + timeRange: shape({}), showTemplateControlBar: bool.isRequired, inPresentationMode: bool.isRequired, handleClickPresentationButton: func, @@ -412,16 +414,22 @@ DashboardPage.propTypes = { errorThrown: func, } -const mapStateToProps = state => { +const mapStateToProps = (state, {params}) => { const { app: { ephemeral: {inPresentationMode}, persisted: {autoRefresh, showTemplateControlBar}, }, - dashboardUI: {dashboards, timeRange, cellQueryStatus}, + dashboardUI: {dashboards, cellQueryStatus}, sources, + dashTimeV1, } = state + const timeRange = + dashTimeV1.ranges.find( + ({dashboardID}) => dashboardID === +params.dashboardID + ) || defaultTimeRange + return { dashboards, autoRefresh, diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 2e8fd8c00..58397b198 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -4,7 +4,15 @@ const initialState = { const dashTimeV1 = (state = initialState, action) => { switch (action.type) { - case 'ADD_DASHBOARD_TIME_RANGE_V1': { + case 'LOAD_DEFAULT_DASHBOARD_TIME_V1': { + const {dashboardID} = action.payload + const timeRange = {upper: null, lower: 'now() - 15m'} + const ranges = [...state.ranges, {dashboardID, timeRange}] + + return {...state, ranges} + } + + case 'ADD_DASHBOARD_TIME_V1': { const {dashboardID, timeRange} = action.payload const ranges = [...state.ranges, {dashboardID, timeRange}] @@ -20,8 +28,18 @@ const dashTimeV1 = (state = initialState, action) => { case 'UPDATE_DASHBOARD_TIME_V1': { const {dashboardID, timeRange} = action.payload + const exists = state.ranges.find(r => r.dashboardID === dashboardID) + const {upper, lower} = timeRange + + if (!exists) { + return { + ...state, + ranges: [...state.ranges, {dashboardID, upper, lower}], + } + } + const ranges = state.ranges.map( - d => (d.dashboardID === dashboardID ? {dashboardID, timeRange} : d) + d => (d.dashboardID === dashboardID ? {dashboardID, upper, lower} : d) ) return {...state, ranges} diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index 5e579ef6d..ab04cf0e3 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -35,6 +35,7 @@ class DataExplorer extends Component { if (queryConfigs.length === 0) { this.props.queryConfigActions.addQuery() } + return queryConfigs[0] } diff --git a/ui/src/store/configureStore.js b/ui/src/store/configureStore.js index 11a876b3d..3fad50db3 100644 --- a/ui/src/store/configureStore.js +++ b/ui/src/store/configureStore.js @@ -12,6 +12,7 @@ import dataExplorerReducers from 'src/data_explorer/reducers' import adminReducer from 'src/admin/reducers/admin' import kapacitorReducers from 'src/kapacitor/reducers' import dashboardUI from 'src/dashboards/reducers/ui' +import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1' import persistStateEnhancer from './persistStateEnhancer' const rootReducer = combineReducers({ @@ -21,6 +22,7 @@ const rootReducer = combineReducers({ ...kapacitorReducers, admin: adminReducer, dashboardUI, + dashTimeV1, routing: routerReducer, }) From f3972819b7b7650f926122a992c6763675d97c53 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 14:16:07 -0700 Subject: [PATCH 05/24] Add dashTimeV1 to localStorage --- ui/src/dashboards/reducers/dashTimeV1.js | 8 -------- ui/src/localStorage.js | 2 ++ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 58397b198..966eff516 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -4,14 +4,6 @@ const initialState = { const dashTimeV1 = (state = initialState, action) => { switch (action.type) { - case 'LOAD_DEFAULT_DASHBOARD_TIME_V1': { - const {dashboardID} = action.payload - const timeRange = {upper: null, lower: 'now() - 15m'} - const ranges = [...state.ranges, {dashboardID, timeRange}] - - return {...state, ranges} - } - case 'ADD_DASHBOARD_TIME_V1': { const {dashboardID, timeRange} = action.payload const ranges = [...state.ranges, {dashboardID, timeRange}] diff --git a/ui/src/localStorage.js b/ui/src/localStorage.js index a1cc90525..0f21658e9 100644 --- a/ui/src/localStorage.js +++ b/ui/src/localStorage.js @@ -34,6 +34,7 @@ export const saveToLocalStorage = ({ dataExplorerQueryConfigs, timeRange, dataExplorer, + dashTimeV1, }) => { try { const appPersisted = Object.assign({}, {app: {persisted}}) @@ -46,6 +47,7 @@ export const saveToLocalStorage = ({ timeRange, dataExplorer, VERSION, // eslint-disable-line no-undef + dashTimeV1, }) ) } catch (err) { From a5282fe34411e3bbe3388c697df5ad12ffea58a3 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 14:28:18 -0700 Subject: [PATCH 06/24] Fix delete --- ui/src/dashboards/reducers/dashTimeV1.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 966eff516..06867d25c 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -13,7 +13,7 @@ const dashTimeV1 = (state = initialState, action) => { case 'DELETE_DASHBOARD': { const {dashboardID} = action.payload - const ranges = state.ranges.filter(d => d.id !== dashboardID) + const ranges = state.ranges.filter(r => r.dashboardID !== dashboardID) return {...state, ranges} } From 8b55efc019116f67d6cfd23560f65b6accdeb37c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 16:02:27 -0700 Subject: [PATCH 07/24] Introduce dashboardTime normalizer --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 2 +- ui/spec/normalizers/dashboardTimeSpec.js | 59 +++++++++++++++++++ ui/src/normalizers/dashboardTime.js | 42 +++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 ui/spec/normalizers/dashboardTimeSpec.js create mode 100644 ui/src/normalizers/dashboardTime.js diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js index ff7680696..ed32e211b 100644 --- a/ui/spec/dashboards/reducers/dashTimeV1Spec.js +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -13,7 +13,7 @@ const emptyState = undefined const dashboardID = 1 const timeRange = {upper: null, lower: 'now() - 15m'} -describe.only('Dashboards.Reducers.DashTimeV1', () => { +describe('Dashboards.Reducers.DashTimeV1', () => { it('can load initial state', () => { const noopAction = () => ({type: 'NOOP'}) const actual = reducer(emptyState, noopAction) diff --git a/ui/spec/normalizers/dashboardTimeSpec.js b/ui/spec/normalizers/dashboardTimeSpec.js new file mode 100644 index 000000000..d918c2f6b --- /dev/null +++ b/ui/spec/normalizers/dashboardTimeSpec.js @@ -0,0 +1,59 @@ +import sinon from 'sinon' +import normalizer from 'src/normalizers/dashboardTime' + +const dashboardID = 1 +const upper = null +const lower = 'now() - 15m' +const timeRange = {dashboardID, upper, lower} + +describe.only('Normalizers.DashboardTime', () => { + it('can filter out non-objects', () => { + const ranges = [1, null, undefined, 'string', timeRange] + + const actual = normalizer(ranges) + const expected = [timeRange] + + expect(actual).to.deep.equal(expected) + }) + + it('can remove objects with missing keys', () => { + const ranges = [ + {}, + {dashboardID, upper}, + {dashboardID, lower}, + {upper, lower}, + timeRange, + ] + + const actual = normalizer(ranges) + const expected = [timeRange] + expect(actual).to.deep.equal(expected) + }) + + it('can remove timeRanges with incorrect dashboardID', () => { + const ranges = [{dashboardID: '1', upper, lower}, timeRange] + + const actual = normalizer(ranges) + const expected = [timeRange] + expect(actual).to.deep.equal(expected) + }) + + it('can remove timeRange when is neither an upper or lower bound', () => { + const noBounds = {dashboardID, upper: null, lower: null} + const ranges = [timeRange, noBounds] + + const actual = normalizer(ranges) + const expected = [timeRange] + expect(actual).to.deep.equal(expected) + }) + + it('can remove a timeRange when upper and lower bounds are of the wrong type', () => { + const badTime = {dashboardID, upper: 2017, lower} + const reallyBadTime = {dashboardID, upper, lower: {foo: 'bar'}} + const ranges = [timeRange, badTime, reallyBadTime] + + const actual = normalizer(ranges) + const expected = [timeRange] + expect(actual).to.deep.equal(expected) + }) +}) diff --git a/ui/src/normalizers/dashboardTime.js b/ui/src/normalizers/dashboardTime.js new file mode 100644 index 000000000..e8e0d02a7 --- /dev/null +++ b/ui/src/normalizers/dashboardTime.js @@ -0,0 +1,42 @@ +const dashtime = ranges => { + if (!Array.isArray(ranges)) { + return [] + } + + const normalized = ranges.filter(r => { + if (r !== Object(r)) { + return false + } + + // check for presence of keys + if ( + !r.hasOwnProperty('dashboardID') || + !r.hasOwnProperty('lower') || + !r.hasOwnProperty('upper') + ) { + return false + } + + const {dashboardID, lower, upper} = r + + if (!dashboardID || typeof dashboardID !== 'number') { + return false + } + + if (!lower && !upper) { + return false + } + + const isCorrectType = bound => typeof bound === 'string' || bound === null + + if (!isCorrectType(lower) || !isCorrectType(upper)) { + return false + } + + return true + }) + + return normalized +} + +export default dashtime From fa52fc78350c5b7681ede25df1b46b753f509509 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 16:25:10 -0700 Subject: [PATCH 08/24] Add number as valid type of bound --- ui/spec/normalizers/dashboardTimeSpec.js | 4 ++-- ui/src/normalizers/dashboardTime.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/spec/normalizers/dashboardTimeSpec.js b/ui/spec/normalizers/dashboardTimeSpec.js index d918c2f6b..25921a8fd 100644 --- a/ui/spec/normalizers/dashboardTimeSpec.js +++ b/ui/spec/normalizers/dashboardTimeSpec.js @@ -48,8 +48,8 @@ describe.only('Normalizers.DashboardTime', () => { }) it('can remove a timeRange when upper and lower bounds are of the wrong type', () => { - const badTime = {dashboardID, upper: 2017, lower} - const reallyBadTime = {dashboardID, upper, lower: {foo: 'bar'}} + const badTime = {dashboardID, upper: [], lower} + const reallyBadTime = {dashboardID, upper, lower: {bad: 'time'}} const ranges = [timeRange, badTime, reallyBadTime] const actual = normalizer(ranges) diff --git a/ui/src/normalizers/dashboardTime.js b/ui/src/normalizers/dashboardTime.js index e8e0d02a7..2f374c6fd 100644 --- a/ui/src/normalizers/dashboardTime.js +++ b/ui/src/normalizers/dashboardTime.js @@ -1,3 +1,5 @@ +import _ from 'lodash' + const dashtime = ranges => { if (!Array.isArray(ranges)) { return [] @@ -27,7 +29,8 @@ const dashtime = ranges => { return false } - const isCorrectType = bound => typeof bound === 'string' || bound === null + const isCorrectType = bound => + _.isString(bound) || _.isNull(bound) || _.isInteger(bound) if (!isCorrectType(lower) || !isCorrectType(upper)) { return false From 7e5de8c9b7548007354e48775cda71c3a49415aa Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 17:24:15 -0700 Subject: [PATCH 09/24] Normalize dashboard time ranges on load and save to localStorage --- ui/src/localStorage.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/ui/src/localStorage.js b/ui/src/localStorage.js index 0f21658e9..689825ed8 100644 --- a/ui/src/localStorage.js +++ b/ui/src/localStorage.js @@ -1,3 +1,5 @@ +import normalizer from 'src/normalizers/dashboardTime' + export const loadLocalStorage = errorsQueue => { try { const serializedState = localStorage.getItem('state') @@ -12,8 +14,22 @@ export const loadLocalStorage = errorsQueue => { console.log(errorText) // eslint-disable-line no-console errorsQueue.push(errorText) - window.localStorage.removeItem('state') - return {} + if (!state.dashTimeV1) { + window.localStorage.removeItem('state') + return {} + } + + const ranges = normalizer(state.dashTimeV1.ranges) + const dashTimeV1 = {ranges} + + window.localStorage.setItem( + 'state', + JSON.stringify({ + dashTimeV1, + }) + ) + + return {dashTimeV1} } delete state.VERSION @@ -34,10 +50,11 @@ export const saveToLocalStorage = ({ dataExplorerQueryConfigs, timeRange, dataExplorer, - dashTimeV1, + dashTimeV1: {ranges}, }) => { try { const appPersisted = Object.assign({}, {app: {persisted}}) + const dashTimeV1 = {ranges: normalizer(ranges)} window.localStorage.setItem( 'state', From 66df472dfe7a65d974f960941118b4ac2e31bc5a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 17:45:02 -0700 Subject: [PATCH 10/24] Use lodash for object detection --- ui/src/normalizers/dashboardTime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/normalizers/dashboardTime.js b/ui/src/normalizers/dashboardTime.js index 2f374c6fd..5ea7afda8 100644 --- a/ui/src/normalizers/dashboardTime.js +++ b/ui/src/normalizers/dashboardTime.js @@ -6,7 +6,7 @@ const dashtime = ranges => { } const normalized = ranges.filter(r => { - if (r !== Object(r)) { + if (!_.isObject(r)) { return false } From a77f76d706aba4efa4632b8db148037c3bf64231 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 17:54:44 -0700 Subject: [PATCH 11/24] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a908e26..1f69016a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 1. [#2015](https://github.com/influxdata/chronograf/pull/2015): Chronograf shows real status for windows hosts when metrics are saved in non-default db - thank you, @ar7z1! 1. [#2019](https://github.com/influxdata/chronograf/pull/2006): Fix false error warning for duplicate kapacitor name 1. [#2018](https://github.com/influxdata/chronograf/pull/2018): Fix unresponsive display options and query builder in dashboards +1.[#2004](https://github.com/influxdata/chronograf/pull/2004): Fix DE query templates dropdown disappearance +1.[#2006](https://github.com/influxdata/chronograf/pull/2006): Fix no alert for duplicate db name +1.[#2015](https://github.com/influxdata/chronograf/pull/2015): Chronograf shows real status for windows hosts when metrics are saved in non-default db - thank you, @ar7z1! +1.[#2019](https://github.com/influxdata/chronograf/pull/2006): Fix false error warning for duplicate kapacitor name +1.[#2018](https://github.com/influxdata/chronograf/pull/2018): Fix unresponsive display options and query builder in dashboards +1.[#1996](https://github.com/influxdata/chronograf/pull/1996): Able to switch InfluxDB sources on a per graph basis +1.[#2083](https://github.com/influxdata/chronograf/pull/2083): Persist dashboard time ranges per dashboard ### Features 1. [#1885](https://github.com/influxdata/chronograf/pull/1885): Add `fill` options to data explorer and dashboard queries From a861c7a86243534b41a18cb37775e4b3fff20bb5 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 18:04:32 -0700 Subject: [PATCH 12/24] Test cleanup --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 14 +++++++++----- ui/spec/normalizers/dashboardTimeSpec.js | 3 +-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js index ed32e211b..3b69a7262 100644 --- a/ui/spec/dashboards/reducers/dashTimeV1Spec.js +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -33,22 +33,26 @@ describe('Dashboards.Reducers.DashTimeV1', () => { const state = { ranges: [{dashboardID, timeRange}], } + const dashboard = {id: dashboardID} - const actual = reducer(state, deleteDashboard({}, dashboardID)) + const actual = reducer(state, deleteDashboard(dashboard)) const expected = [] expect(actual.ranges).to.deep.equal(expected) }) - it('can updated a dashboard time range', () => { + it('can update a dashboard time range', () => { const state = { ranges: [{dashboardID, timeRange}], } - const newTimeRange = {upper: '2017-10-07 12:05', lower: '2017-10-05 12:04'} + const {upper, lower} = { + upper: '2017-10-07 12:05', + lower: '2017-10-05 12:04', + } - const actual = reducer(state, updateDashTimeV1(dashboardID, newTimeRange)) - const expected = [{dashboardID, timeRange: newTimeRange}] + const actual = reducer(state, updateDashTimeV1(dashboardID, {upper, lower})) + const expected = [{dashboardID, upper, lower}] expect(actual.ranges).to.deep.equal(expected) }) diff --git a/ui/spec/normalizers/dashboardTimeSpec.js b/ui/spec/normalizers/dashboardTimeSpec.js index 25921a8fd..e6c38dfc2 100644 --- a/ui/spec/normalizers/dashboardTimeSpec.js +++ b/ui/spec/normalizers/dashboardTimeSpec.js @@ -1,4 +1,3 @@ -import sinon from 'sinon' import normalizer from 'src/normalizers/dashboardTime' const dashboardID = 1 @@ -6,7 +5,7 @@ const upper = null const lower = 'now() - 15m' const timeRange = {dashboardID, upper, lower} -describe.only('Normalizers.DashboardTime', () => { +describe('Normalizers.DashboardTime', () => { it('can filter out non-objects', () => { const ranges = [1, null, undefined, 'string', timeRange] From 9abbce4160c6439bc2e96075bae10fda90e2fcca Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 18:16:06 -0700 Subject: [PATCH 13/24] Test cleanup --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js index 3b69a7262..b81be018d 100644 --- a/ui/spec/dashboards/reducers/dashTimeV1Spec.js +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -41,19 +41,37 @@ describe('Dashboards.Reducers.DashTimeV1', () => { expect(actual.ranges).to.deep.equal(expected) }) - it('can update a dashboard time range', () => { - const state = { - ranges: [{dashboardID, timeRange}], - } + describe('setting a dashboard time range', () => { + it('can update an existing dashboard', () => { + const state = { + ranges: [{dashboardID, upper: timeRange.upper, lower: timeRange.lower}], + } - const {upper, lower} = { - upper: '2017-10-07 12:05', - lower: '2017-10-05 12:04', - } + const {upper, lower} = { + upper: '2017-10-07 12:05', + lower: '2017-10-05 12:04', + } - const actual = reducer(state, updateDashTimeV1(dashboardID, {upper, lower})) - const expected = [{dashboardID, upper, lower}] + const actual = reducer( + state, + updateDashTimeV1(dashboardID, {upper, lower}) + ) + const expected = [{dashboardID, upper, lower}] - expect(actual.ranges).to.deep.equal(expected) + expect(actual.ranges).to.deep.equal(expected) + }) + + it('can set a new time range if none exists', () => { + const actual = reducer( + emptyState, + updateDashTimeV1(dashboardID, timeRange) + ) + + const expected = [ + {dashboardID, upper: timeRange.upper, lower: timeRange.lower}, + ] + + expect(actual.ranges).to.deep.equal(expected) + }) }) }) From 33593e7df04f264d87e55fad12f69175de30281a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 5 Oct 2017 20:19:30 -0700 Subject: [PATCH 14/24] Semantics --- ui/spec/dashboards/reducers/dashTimeV1Spec.js | 12 +++--------- ui/src/dashboards/actions/index.js | 4 ++-- ui/src/dashboards/containers/DashboardPage.js | 2 +- ui/src/dashboards/reducers/dashTimeV1.js | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/ui/spec/dashboards/reducers/dashTimeV1Spec.js b/ui/spec/dashboards/reducers/dashTimeV1Spec.js index b81be018d..7c7ee3925 100644 --- a/ui/spec/dashboards/reducers/dashTimeV1Spec.js +++ b/ui/spec/dashboards/reducers/dashTimeV1Spec.js @@ -1,7 +1,7 @@ import reducer from 'src/dashboards/reducers/dashTimeV1' import { addDashTimeV1, - updateDashTimeV1, + setDashTimeV1, deleteDashboard, } from 'src/dashboards/actions/index' @@ -52,20 +52,14 @@ describe('Dashboards.Reducers.DashTimeV1', () => { lower: '2017-10-05 12:04', } - const actual = reducer( - state, - updateDashTimeV1(dashboardID, {upper, lower}) - ) + const actual = reducer(state, setDashTimeV1(dashboardID, {upper, lower})) const expected = [{dashboardID, upper, lower}] expect(actual.ranges).to.deep.equal(expected) }) it('can set a new time range if none exists', () => { - const actual = reducer( - emptyState, - updateDashTimeV1(dashboardID, timeRange) - ) + const actual = reducer(emptyState, setDashTimeV1(dashboardID, timeRange)) const expected = [ {dashboardID, upper: timeRange.upper, lower: timeRange.lower}, diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 7093f13a1..b151b5e19 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -43,8 +43,8 @@ export const addDashTimeV1 = (dashboardID, timeRange) => ({ }, }) -export const updateDashTimeV1 = (dashboardID, timeRange) => ({ - type: 'UPDATE_DASHBOARD_TIME_V1', +export const setDashTimeV1 = (dashboardID, timeRange) => ({ + type: 'SET_DASHBOARD_TIME_V1', payload: { dashboardID, timeRange, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 526b38e7e..25c4fcae2 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -84,7 +84,7 @@ class DashboardPage extends Component { handleChooseTimeRange = ({upper, lower}) => { const {params: {dashboardID}, dashboardActions} = this.props - dashboardActions.updateDashTimeV1(+dashboardID, {upper, lower}) + dashboardActions.setDashTimeV1(+dashboardID, {upper, lower}) } handleUpdatePosition = cells => { diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 06867d25c..efecc54b8 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -18,7 +18,7 @@ const dashTimeV1 = (state = initialState, action) => { return {...state, ranges} } - case 'UPDATE_DASHBOARD_TIME_V1': { + case 'SET_DASHBOARD_TIME_V1': { const {dashboardID, timeRange} = action.payload const exists = state.ranges.find(r => r.dashboardID === dashboardID) const {upper, lower} = timeRange From c0422f21638d8b0fd0830e1517989ee2601da9fb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 11:40:50 -0700 Subject: [PATCH 15/24] Future proof buildInfluxQL function --- .../utils/influxql/selectSpec.js | 26 +++++++++++++++++-- .../components/CellEditorOverlay.js | 9 ++++--- ui/src/dashboards/components/QueryMaker.js | 5 ++-- ui/src/dashboards/constants/index.js | 3 +++ ui/src/utils/buildQueriesForGraphs.js | 6 +++-- ui/src/utils/influxql.js | 26 ++++++++++++++----- 6 files changed, 59 insertions(+), 16 deletions(-) diff --git a/ui/spec/data_explorer/utils/influxql/selectSpec.js b/ui/spec/data_explorer/utils/influxql/selectSpec.js index d9ca39812..70a463509 100644 --- a/ui/spec/data_explorer/utils/influxql/selectSpec.js +++ b/ui/spec/data_explorer/utils/influxql/selectSpec.js @@ -1,7 +1,8 @@ -import buildInfluxQLQuery from 'utils/influxql' +import buildInfluxQLQuery, {buildQuery} from 'utils/influxql' import defaultQueryConfig from 'src/utils/defaultQueryConfig' import {NONE, NULL_STRING} from 'shared/constants/queryFillOptions' +import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' function mergeConfig(options) { return Object.assign({}, defaultQueryConfig(123), options) @@ -155,7 +156,7 @@ describe('buildInfluxQLQuery', () => { it('builds the right query', () => { const expected = - 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m), "t1", "t2" FILL(null)' + 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m), "t1", "t2"' expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected) }) }) @@ -271,4 +272,25 @@ describe('buildInfluxQLQuery', () => { }) }) }) + + describe('build query', () => { + beforeEach(() => { + config = mergeConfig({ + database: 'db1', + measurement: 'm1', + retentionPolicy: 'rp1', + fields: [{field: 'f1', func: null}], + groupBy: {time: '10m', tags: []}, + }) + }) + + it('builds an influxql relative time bound query', () => { + const timeRange = {upper: null, lower: 'now() - 15m'} + const expected = + 'SELECT "f1" FROM "db1"."rp1"."m1" WHERE time > now() - 15m GROUP BY time(10m) FILL(null)' + const actual = buildQuery(TYPE_QUERY_CONFIG, timeRange, config) + + expect(actual).to.equal(expected) + }) + }) }) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 4d443c78f..c3545c9f0 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -12,10 +12,13 @@ import DisplayOptions from 'src/dashboards/components/DisplayOptions' import * as queryModifiers from 'src/utils/queryTransitions' import defaultQueryConfig from 'src/utils/defaultQueryConfig' -import buildInfluxQLQuery from 'utils/influxql' +import {buildQuery} from 'utils/influxql' import {getQueryConfig} from 'shared/apis' -import {removeUnselectedTemplateValues} from 'src/dashboards/constants' +import { + removeUnselectedTemplateValues, + TYPE_QUERY_CONFIG, +} from 'src/dashboards/constants' import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames' import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants' @@ -147,7 +150,7 @@ class CellEditorOverlay extends Component { const queries = queriesWorkingDraft.map(q => { const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} - const query = q.rawText || buildInfluxQLQuery(timeRange, q) + const query = q.rawText || buildQuery(TYPE_QUERY_CONFIG, timeRange, q) return { queryConfig: q, diff --git a/ui/src/dashboards/components/QueryMaker.js b/ui/src/dashboards/components/QueryMaker.js index 3a8c1e56c..a832c82a8 100644 --- a/ui/src/dashboards/components/QueryMaker.js +++ b/ui/src/dashboards/components/QueryMaker.js @@ -4,13 +4,14 @@ import EmptyQuery from 'src/shared/components/EmptyQuery' import QueryTabList from 'src/shared/components/QueryTabList' import QueryTextArea from 'src/dashboards/components/QueryTextArea' import SchemaExplorer from 'src/shared/components/SchemaExplorer' -import buildInfluxQLQuery from 'utils/influxql' +import {buildQuery} from 'utils/influxql' +import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' const TEMPLATE_RANGE = {upper: null, lower: ':dashboardTime:'} const rawTextBinder = (links, id, action) => text => action(links.queries, id, text) const buildText = q => - q.rawText || buildInfluxQLQuery(q.range || TEMPLATE_RANGE, q) || '' + q.rawText || buildQuery(TYPE_QUERY_CONFIG, q.range || TEMPLATE_RANGE, q) || '' const QueryMaker = ({ source, diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index 5e0d0d9fe..6202463f0 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -106,3 +106,6 @@ export const TOOLTIP_CONTENT = { FORMAT: '

K/M/B = Thousand / Million / Billion
K/M/G = Kilo / Mega / Giga

', } + +export const TYPE_QUERY_CONFIG = 'queryConfig' +export const TYPE_IFQL = 'ifql' diff --git a/ui/src/utils/buildQueriesForGraphs.js b/ui/src/utils/buildQueriesForGraphs.js index 7b2a1691f..46e5cfc6a 100644 --- a/ui/src/utils/buildQueriesForGraphs.js +++ b/ui/src/utils/buildQueriesForGraphs.js @@ -1,9 +1,11 @@ -import buildInfluxQLQuery from 'utils/influxql' +import {buildQuery} from 'utils/influxql' +import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' const buildQueries = (proxy, queryConfigs, timeRange) => { const statements = queryConfigs.map(query => { const text = - query.rawText || buildInfluxQLQuery(query.range || timeRange, query) + query.rawText || + buildQuery(TYPE_QUERY_CONFIG, query.range || timeRange, query) return {text, id: query.id, queryConfig: query} }) diff --git a/ui/src/utils/influxql.js b/ui/src/utils/influxql.js index 71ba07f0a..eb420aab0 100644 --- a/ui/src/utils/influxql.js +++ b/ui/src/utils/influxql.js @@ -5,6 +5,7 @@ import { DEFAULT_DASHBOARD_GROUP_BY_INTERVAL, } from 'shared/constants' import {NULL_STRING} from 'shared/constants/queryFillOptions' +import {TYPE_QUERY_CONFIG, TYPE_IFQL} from 'src/dashboards/constants' import timeRanges from 'hson!shared/data/timeRanges.hson' /* eslint-disable quotes */ @@ -21,13 +22,9 @@ export const quoteIfTimestamp = ({lower, upper}) => { } /* eslint-enable quotes */ -export default function buildInfluxQLQuery( - timeBounds, - config, - isKapacitorRule -) { +export default function buildInfluxQLQuery(timeRange, config) { const {groupBy, fill = NULL_STRING, tags, areTagsAccepted} = config - const {upper, lower} = quoteIfTimestamp(timeBounds) + const {upper, lower} = quoteIfTimestamp(timeRange) const select = _buildSelect(config) if (select === null) { @@ -36,7 +33,7 @@ export default function buildInfluxQLQuery( const condition = _buildWhereClause({lower, upper, tags, areTagsAccepted}) const dimensions = _buildGroupBy(groupBy) - const fillClause = isKapacitorRule || !groupBy.time ? '' : _buildFill(fill) + const fillClause = groupBy.time ? _buildFill(fill) : '' return `${select}${condition}${dimensions}${fillClause}` } @@ -53,6 +50,21 @@ function _buildSelect({fields, database, retentionPolicy, measurement}) { return statement } +// type arg will reason about new query types i.e. IFQL, GraphQL, or queryConfig +export const buildQuery = (type, timeRange, config) => { + switch (type) { + case `${TYPE_QUERY_CONFIG}`: { + return buildInfluxQLQuery(timeRange, config) + } + + case `${TYPE_IFQL}`: { + // build query usining IFQL here + } + } + + return buildInfluxQLQuery(timeRange, config) +} + export function buildSelectStatement(config) { return _buildSelect(config) } From d2c2f81975753c57271bf167c8fb40ae8a8312b7 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 11:55:20 -0700 Subject: [PATCH 16/24] Move coercion of string into id normalizer If an when we move to using URI's as the dynamic parameters in routes. We will need a consolidate place to decocde them or ensure they are of the proper types and shape. This commit introduces this pattern. --- ui/src/dashboards/containers/DashboardPage.js | 26 +++++++++++++------ ui/src/normalizers/id.js | 18 +++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 ui/src/normalizers/id.js diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 25c4fcae2..cf8176c12 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -13,6 +13,7 @@ import Dashboard from 'src/dashboards/components/Dashboard' import TemplateVariableManager from 'src/dashboards/components/template_variables/Manager' import {errorThrown as errorThrownAction} from 'shared/actions/errors' +import idNormalizer, {TYPE_ID} from 'src/normalizers/id' import * as dashboardActionCreators from 'src/dashboards/actions' @@ -49,7 +50,9 @@ class DashboardPage extends Component { } = this.props const dashboards = await getDashboardsAsync() - const dashboard = dashboards.find(d => d.id === +dashboardID) + const dashboard = dashboards.find( + d => d.id === idNormalizer(TYPE_ID, dashboardID) + ) // Refresh and persists influxql generated template variable values await updateTempVarValues(source, dashboard) await putDashboardByID(dashboardID) @@ -84,7 +87,10 @@ class DashboardPage extends Component { handleChooseTimeRange = ({upper, lower}) => { const {params: {dashboardID}, dashboardActions} = this.props - dashboardActions.setDashTimeV1(+dashboardID, {upper, lower}) + dashboardActions.setDashTimeV1(idNormalizer(TYPE_ID, dashboardID), { + upper, + lower, + }) } handleUpdatePosition = cells => { @@ -129,7 +135,7 @@ class DashboardPage extends Component { handleSelectTemplate = templateID => values => { const {params: {dashboardID}} = this.props this.props.dashboardActions.templateVariableSelected( - +dashboardID, + idNormalizer(TYPE_ID, dashboardID), templateID, [values] ) @@ -157,8 +163,12 @@ class DashboardPage extends Component { synchronizer = dygraph => { const dygraphs = [...this.state.dygraphs, dygraph] - const {dashboards, params} = this.props - const dashboard = dashboards.find(d => d.id === +params.dashboardID) + const {dashboards, params: {dashboardID}} = this.props + + const dashboard = dashboards.find( + d => d.id === idNormalizer(TYPE_ID, dashboardID) + ) + if ( dashboard && dygraphs.length === dashboard.cells.length && @@ -183,7 +193,7 @@ class DashboardPage extends Component { getActiveDashboard() { const {params: {dashboardID}, dashboards} = this.props - return dashboards.find(d => d.id === +dashboardID) + return dashboards.find(d => d.id === idNormalizer(TYPE_ID, dashboardID)) } render() { @@ -414,7 +424,7 @@ DashboardPage.propTypes = { errorThrown: func, } -const mapStateToProps = (state, {params}) => { +const mapStateToProps = (state, {params: {dashboardID}}) => { const { app: { ephemeral: {inPresentationMode}, @@ -427,7 +437,7 @@ const mapStateToProps = (state, {params}) => { const timeRange = dashTimeV1.ranges.find( - ({dashboardID}) => dashboardID === +params.dashboardID + r => r.dashboardID === idNormalizer(TYPE_ID, dashboardID) ) || defaultTimeRange return { diff --git a/ui/src/normalizers/id.js b/ui/src/normalizers/id.js new file mode 100644 index 000000000..06442173f --- /dev/null +++ b/ui/src/normalizers/id.js @@ -0,0 +1,18 @@ +export const TYPE_ID = 'ID' +export const TYPE_URI = 'ID' + +const idNormalizer = (type, id) => { + switch (type) { + case 'ID': { + return +id + } + + case 'URI': { + // handle decode of URI here + } + } + + return id +} + +export default idNormalizer From 50ecc5d75719e4e196533aa2ae98e3adfed810da Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 12:21:30 -0700 Subject: [PATCH 17/24] Add dashboard to props --- ui/src/dashboards/containers/DashboardPage.js | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index cf8176c12..a80ea0199 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -53,6 +53,7 @@ class DashboardPage extends Component { const dashboard = dashboards.find( d => d.id === idNormalizer(TYPE_ID, dashboardID) ) + // Refresh and persists influxql generated template variable values await updateTempVarValues(source, dashboard) await putDashboardByID(dashboardID) @@ -76,8 +77,9 @@ class DashboardPage extends Component { } handleSaveEditedCell = newCell => { - this.props.dashboardActions - .updateDashboardCell(this.getActiveDashboard(), newCell) + const {dashboardActions, dashboard} = this.props + dashboardActions + .updateDashboardCell(dashboard, newCell) .then(this.handleDismissOverlay) } @@ -86,21 +88,24 @@ class DashboardPage extends Component { } handleChooseTimeRange = ({upper, lower}) => { - const {params: {dashboardID}, dashboardActions} = this.props - dashboardActions.setDashTimeV1(idNormalizer(TYPE_ID, dashboardID), { + const {dashboard, dashboardActions} = this.props + dashboardActions.setDashTimeV1(dashboard.id, { upper, lower, }) } handleUpdatePosition = cells => { - const newDashboard = {...this.getActiveDashboard(), cells} - this.props.dashboardActions.updateDashboard(newDashboard) - this.props.dashboardActions.putDashboard(newDashboard) + const {dashboardActions, dashboard} = this.props + const newDashboard = {...dashboard, cells} + + dashboardActions.updateDashboard(newDashboard) + dashboardActions.putDashboard(newDashboard) } handleAddCell = () => { - this.props.dashboardActions.addDashboardCellAsync(this.getActiveDashboard()) + const {dashboardActions, dashboard} = this.props + dashboardActions.addDashboardCellAsync(dashboard) } handleEditDashboard = () => { @@ -112,42 +117,40 @@ class DashboardPage extends Component { } handleRenameDashboard = name => { + const {dashboardActions, dashboard} = this.props this.setState({isEditMode: false}) - const newDashboard = {...this.getActiveDashboard(), name} - this.props.dashboardActions.updateDashboard(newDashboard) - this.props.dashboardActions.putDashboard(newDashboard) + const newDashboard = {...dashboard, name} + + dashboardActions.updateDashboard(newDashboard) + dashboardActions.putDashboard(newDashboard) } - handleUpdateDashboardCell = newCell => { - return () => { - this.props.dashboardActions.updateDashboardCell( - this.getActiveDashboard(), - newCell - ) - } + handleUpdateDashboardCell = newCell => () => { + const {dashboardActions, dashboard} = this.props + dashboardActions.updateDashboardCell(dashboard, newCell) } handleDeleteDashboardCell = cell => { - const dashboard = this.getActiveDashboard() - this.props.dashboardActions.deleteDashboardCellAsync(dashboard, cell) + const {dashboardActions, dashboard} = this.props + dashboardActions.deleteDashboardCellAsync(dashboard, cell) } handleSelectTemplate = templateID => values => { - const {params: {dashboardID}} = this.props - this.props.dashboardActions.templateVariableSelected( - idNormalizer(TYPE_ID, dashboardID), - templateID, - [values] - ) + const {dashboardActions, dashboard} = this.props + dashboardActions.templateVariableSelected(dashboard.id, templateID, [ + values, + ]) } handleEditTemplateVariables = ( templates, onSaveTemplatesSuccess ) => async () => { + const {dashboardActions, dashboard} = this.props + try { - await this.props.dashboardActions.putDashboard({ - ...this.getActiveDashboard(), + await dashboardActions.putDashboard({ + dashboard, templates, }) onSaveTemplatesSuccess() @@ -191,11 +194,6 @@ class DashboardPage extends Component { this.setState({zoomedTimeRange: {zoomedLower, zoomedUpper}}) } - getActiveDashboard() { - const {params: {dashboardID}, dashboards} = this.props - return dashboards.find(d => d.id === idNormalizer(TYPE_ID, dashboardID)) - } - render() { const {zoomedTimeRange} = this.state const {zoomedLower, zoomedUpper} = zoomedTimeRange @@ -206,6 +204,7 @@ class DashboardPage extends Component { timeRange, timeRange: {lower, upper}, showTemplateControlBar, + dashboard, dashboards, autoRefresh, cellQueryStatus, @@ -258,8 +257,6 @@ class DashboardPage extends Component { values: [], } - const dashboard = this.getActiveDashboard() - let templatesIncludingDashTime if (dashboard) { templatesIncludingDashTime = [ @@ -378,6 +375,7 @@ DashboardPage.propTypes = { pathname: string.isRequired, query: shape({}), }).isRequired, + dashboard: shape({}), dashboardActions: shape({ putDashboard: func.isRequired, getDashboardsAsync: func.isRequired, @@ -440,9 +438,14 @@ const mapStateToProps = (state, {params: {dashboardID}}) => { r => r.dashboardID === idNormalizer(TYPE_ID, dashboardID) ) || defaultTimeRange + const dashboard = dashboards.find( + d => d.id === idNormalizer(TYPE_ID, dashboardID) + ) + return { dashboards, autoRefresh, + dashboard, timeRange, showTemplateControlBar, inPresentationMode, From caa3060950e92a9555a2910ac76b01a67b761bf7 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 12:26:08 -0700 Subject: [PATCH 18/24] Gaurd against ranges being undefined --- ui/src/localStorage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/localStorage.js b/ui/src/localStorage.js index 689825ed8..77cb2268f 100644 --- a/ui/src/localStorage.js +++ b/ui/src/localStorage.js @@ -1,3 +1,4 @@ +import _ from 'lodash' import normalizer from 'src/normalizers/dashboardTime' export const loadLocalStorage = errorsQueue => { @@ -19,7 +20,7 @@ export const loadLocalStorage = errorsQueue => { return {} } - const ranges = normalizer(state.dashTimeV1.ranges) + const ranges = normalizer(_.get(state, ['dashTimeV1', 'ranges'], [])) const dashTimeV1 = {ranges} window.localStorage.setItem( From 3cc59e847e3da3b69d4d8a7e6eae665023f83fdd Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 12:42:01 -0700 Subject: [PATCH 19/24] Simplify reducer --- ui/src/dashboards/reducers/dashTimeV1.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index efecc54b8..067d79c00 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -1,3 +1,4 @@ +import _ from 'lodash' const initialState = { ranges: [], } @@ -19,20 +20,9 @@ const dashTimeV1 = (state = initialState, action) => { } case 'SET_DASHBOARD_TIME_V1': { - const {dashboardID, timeRange} = action.payload - const exists = state.ranges.find(r => r.dashboardID === dashboardID) - const {upper, lower} = timeRange - - if (!exists) { - return { - ...state, - ranges: [...state.ranges, {dashboardID, upper, lower}], - } - } - - const ranges = state.ranges.map( - d => (d.dashboardID === dashboardID ? {dashboardID, upper, lower} : d) - ) + const {dashboardID, timeRange: {upper, lower}} = action.payload + const newTimeRange = [{dashboardID, upper, lower}] + const ranges = _.unionBy(newTimeRange, state.ranges, 'dashboardID') return {...state, ranges} } From d4f002cc633918159b7805bb11fd3a4da6874f27 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 12:43:11 -0700 Subject: [PATCH 20/24] Add more properties to timeRange proptypes --- ui/src/dashboards/containers/DashboardPage.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index a80ea0199..91aeaf888 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -411,7 +411,10 @@ DashboardPage.propTypes = { handleChooseAutoRefresh: func.isRequired, autoRefresh: number.isRequired, templateControlBarVisibilityToggled: func.isRequired, - timeRange: shape({}), + timeRange: shape({ + upper: string, + lower: string, + }), showTemplateControlBar: bool.isRequired, inPresentationMode: bool.isRequired, handleClickPresentationButton: func, From d6663a1dcb1c9327c3b0bae4e5129cd603eb0ea9 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 12:50:07 -0700 Subject: [PATCH 21/24] Add format to timeRange --- ui/src/dashboards/containers/DashboardPage.js | 8 +++++++- ui/src/dashboards/reducers/dashTimeV1.js | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 91aeaf888..fa518407b 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -23,7 +23,12 @@ import { } from 'shared/actions/app' import {presentationButtonDispatcher} from 'shared/dispatchers' -const defaultTimeRange = {upper: null, lower: 'now() - 15m'} +const FORMAT_INFLUXQL = 'influxql' +const defaultTimeRange = { + upper: null, + lower: 'now() - 15m', + format: FORMAT_INFLUXQL, +} class DashboardPage extends Component { constructor(props) { @@ -92,6 +97,7 @@ class DashboardPage extends Component { dashboardActions.setDashTimeV1(dashboard.id, { upper, lower, + format: FORMAT_INFLUXQL, }) } diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 067d79c00..0bedd3f43 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -20,8 +20,8 @@ const dashTimeV1 = (state = initialState, action) => { } case 'SET_DASHBOARD_TIME_V1': { - const {dashboardID, timeRange: {upper, lower}} = action.payload - const newTimeRange = [{dashboardID, upper, lower}] + const {dashboardID, timeRange: {upper, lower, format}} = action.payload + const newTimeRange = [{dashboardID, upper, lower, format}] const ranges = _.unionBy(newTimeRange, state.ranges, 'dashboardID') return {...state, ranges} From 4be06d1bf24c4ea102aed58b76c9b47c55d7ce9a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 12:59:01 -0700 Subject: [PATCH 22/24] Fix tests --- ui/spec/data_explorer/utils/influxql/selectSpec.js | 2 +- ui/src/dashboards/reducers/dashTimeV1.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/spec/data_explorer/utils/influxql/selectSpec.js b/ui/spec/data_explorer/utils/influxql/selectSpec.js index 70a463509..f70f0e3d2 100644 --- a/ui/spec/data_explorer/utils/influxql/selectSpec.js +++ b/ui/spec/data_explorer/utils/influxql/selectSpec.js @@ -156,7 +156,7 @@ describe('buildInfluxQLQuery', () => { it('builds the right query', () => { const expected = - 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m), "t1", "t2"' + 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m), "t1", "t2" FILL(null)' expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected) }) }) diff --git a/ui/src/dashboards/reducers/dashTimeV1.js b/ui/src/dashboards/reducers/dashTimeV1.js index 0bedd3f43..4d8c81d27 100644 --- a/ui/src/dashboards/reducers/dashTimeV1.js +++ b/ui/src/dashboards/reducers/dashTimeV1.js @@ -20,8 +20,8 @@ const dashTimeV1 = (state = initialState, action) => { } case 'SET_DASHBOARD_TIME_V1': { - const {dashboardID, timeRange: {upper, lower, format}} = action.payload - const newTimeRange = [{dashboardID, upper, lower, format}] + const {dashboardID, timeRange} = action.payload + const newTimeRange = [{dashboardID, ...timeRange}] const ranges = _.unionBy(newTimeRange, state.ranges, 'dashboardID') return {...state, ranges} From 066419ce527db9edde22ad6e95171d298fa96227 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 13:02:49 -0700 Subject: [PATCH 23/24] Update CHANGELOG --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f69016a3..a88e984e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v1.3.10.0 [2017-10-06] +### Bug Fixes + +### Features +1.[#2083](https://github.com/influxdata/chronograf/pull/2083): Persist dashboard time ranges per dashboard + +### UI Improvements + ## v1.3.9.0 [2017-10-06] ### Bug Fixes 1. [#2004](https://github.com/influxdata/chronograf/pull/2004): Fix Data Explorer disappearing query templates in dropdown @@ -11,7 +19,6 @@ 1.[#2019](https://github.com/influxdata/chronograf/pull/2006): Fix false error warning for duplicate kapacitor name 1.[#2018](https://github.com/influxdata/chronograf/pull/2018): Fix unresponsive display options and query builder in dashboards 1.[#1996](https://github.com/influxdata/chronograf/pull/1996): Able to switch InfluxDB sources on a per graph basis -1.[#2083](https://github.com/influxdata/chronograf/pull/2083): Persist dashboard time ranges per dashboard ### Features 1. [#1885](https://github.com/influxdata/chronograf/pull/1885): Add `fill` options to data explorer and dashboard queries From 04184ab6790ee7afb739ff48bb88dacb3ab0b76b Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 9 Oct 2017 13:13:18 -0700 Subject: [PATCH 24/24] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a88e984e1..72b6cb75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Bug Fixes ### Features -1.[#2083](https://github.com/influxdata/chronograf/pull/2083): Persist dashboard time ranges per dashboard +1.[#2083](https://github.com/influxdata/chronograf/pull/2083): Every dashboard can now have its own time range ### UI Improvements