feat: dashboard var linking (#17522)

please don't be broken
pull/17562/head
Alex Boatwright 2020-04-01 16:47:32 -07:00 committed by GitHub
parent 9d3294b31c
commit 5762e64e0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 17 deletions

View File

@ -163,20 +163,40 @@ describe('Dashboard', () => {
.pipe(getSelectedVariable(dashboard.id, 0))
.should('equal', 'c1')
// sanity check on the url before beginning
cy.location('search').should('eq', '?lower=now%28%29%20-%201h')
// select 3rd value in dashboard
cy.getByTestID('variable-dropdown--button')
.eq(0)
.click()
cy.get(`#c3`).click()
// selected value in dashboard is 3rd value
cy.getByTestID('variable-dropdown')
.eq(0)
.should('contain', 'c3')
cy.window()
.pipe(getSelectedVariable(dashboard.id, 0))
.should('equal', 'c3')
// and that it updates the variable in the URL
cy.location('search').should(
'eq',
'?lower=now%28%29%20-%201h&vars%5BCSVVariable%5D=c3'
)
// select 2nd value in dashboard
cy.getByTestID('variable-dropdown--button')
.eq(0)
.click()
cy.get(`#c2`).click()
// breaks here
// selected value in dashboard is 2nd value
cy.getByTestID('variable-dropdown')
.eq(0)
.should('contain', 'c2')
cy.window()
.pipe(getSelectedVariable(dashboard.id, 0))
.should('equal', 'c2')
// and that it updates the variable in the URL without breaking stuff
cy.location('search').should(
'eq',
'?lower=now%28%29%20-%201h&vars%5BCSVVariable%5D=c2'
)
// open CEO
cy.getByTestID('cell-context--toggle').click()

View File

@ -91,6 +91,18 @@ export const updateQueryParams = (updatedQueryParams: object): RouterAction => {
return replace(newLocation)
}
export const updateQueryVars = varsObj => {
const urlVars = qs.parse(window.location.search, {ignoreQueryPrefix: true})
const vars = {
...(urlVars.vars || {}),
...varsObj,
}
return updateQueryParams({
vars,
})
}
export const updateTimeRangeFromQueryParams = (dashboardID: string) => (
dispatch: Dispatch<Action>,
getState

View File

@ -1,31 +1,97 @@
import React, {PureComponent} from 'react'
import qs from 'qs'
import {connect} from 'react-redux'
import {withRouter, WithRouterProps} from 'react-router'
import {setDashboard} from 'src/shared/actions/currentDashboard'
import {AppState} from 'src/types'
import {getVariables} from 'src/variables/selectors'
import {selectValue} from 'src/variables/actions/creators'
import {AppState, Variable} from 'src/types'
interface StateProps {
variables: Variable[]
dashboard: string
}
interface DispatchProps {
updateDashboard: typeof setDashboard
selectValue: typeof selectValue
}
type Props = StateProps & DispatchProps & WithRouterProps
class DashboardRoute extends PureComponent<Props> {
check(props) {
const {dashboard, updateDashboard} = props
const dashboardID = props.params.dashboardID
pendingVars: [{[key: string]: any}]
if (dashboard !== dashboardID) {
updateDashboard(dashboardID)
}
// this function takes the hydrated variables from state
// and runs the `selectValue` action against them if the
// selected value in the search params doesn't match the
// selected value in the redux store
// urlVars represents the `vars` object variable in the
// query params here, and unwrapping / validation is
// handled elsewhere
syncVariables(props, urlVars) {
const dashboardID = props.params.dashboardID
const {variables, selectValue} = props
variables.forEach(v => {
let val
if (v.selected) {
val = v.selected[0]
}
if (val !== urlVars[v.name]) {
val = urlVars[v.name]
selectValue(dashboardID, v.id, val)
}
})
}
componentDidMount() {
this.check(this.props)
const {dashboard, updateDashboard, variables} = this.props
const dashboardID = this.props.params.dashboardID
const urlVars = qs.parse(this.props.location.search, {
ignoreQueryPrefix: true,
})
// always keep the dashboard in sync
if (dashboard !== dashboardID) {
updateDashboard(dashboardID)
}
// nothing to sync as the query params aren't defining
// any variables
if (!urlVars.hasOwnProperty('vars')) {
return
}
// resource is still loading
// we have to wait for it so that we can filter out arbitrary user input
// from the redux state before commiting it back to localstorage
if (!variables.length) {
this.pendingVars = urlVars.vars
return
}
this.syncVariables(this.props, urlVars.vars)
}
componentDidUpdate(props) {
if (props.variables === this.props.variables) {
return
}
if (!this.props.variables.length) {
return
}
if (!this.pendingVars) {
return
}
this.syncVariables(this.props, this.pendingVars)
delete this.pendingVars
}
componentWillUnmount() {
@ -42,13 +108,17 @@ class DashboardRoute extends PureComponent<Props> {
}
const mstp = (state: AppState): StateProps => {
const variables = getVariables(state, state.currentDashboard.id)
return {
variables,
dashboard: state.currentDashboard.id,
}
}
const mdtp: DispatchProps = {
updateDashboard: setDashboard,
selectValue: selectValue,
}
export default connect<StateProps, DispatchProps>(

View File

@ -7,8 +7,10 @@ import {setExportTemplate} from 'src/templates/actions/creators'
import {
setVariables,
setVariable,
selectValue as selectValueInState,
removeVariable,
} from 'src/variables/actions/creators'
import {updateQueryVars} from 'src/dashboards/actions/ranges'
// Schemas
import {variableSchema, arrayOfVariables} from 'src/schemas/variables'
@ -20,6 +22,7 @@ import {createVariableFromTemplate as createVariableFromTemplateAJAX} from 'src/
// Utils
import {
getVariable as getVariableFromState,
getVariables as getVariablesFromState,
getAllVariables as getAllVariablesFromState,
} from 'src/variables/selectors'
@ -357,3 +360,15 @@ export const removeVariableLabelAsync = (
dispatch(notify(copy.removeVariableLabelFailed()))
}
}
export const selectValue = (
contextID: string,
variableID: string,
selected: string
) => async (dispatch: Dispatch<Action>, getState: GetState) => {
const variable = getVariableFromState(getState(), contextID, variableID)
await dispatch(selectValueInState(contextID, variableID, selected))
dispatch(updateQueryVars({[variable.name]: selected}))
}

View File

@ -10,7 +10,7 @@ import {
} from '@influxdata/clockface'
// Actions
import {selectValue} from 'src/variables/actions/creators'
import {selectValue} from 'src/variables/actions/thunks'
// Utils
import {getVariable} from 'src/variables/selectors'