Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboards/variables toolbar #12655

Merged
merged 1 commit into from
Mar 18, 2019
Merged

Conversation

AlirieGray
Copy link
Contributor

@AlirieGray AlirieGray commented Mar 15, 2019

Closes #12375

This PR adds a new toolbar to the dashboard with dropdowns for all of the variables used in that dashboard. The dropdowns allow the user to select the value that will be used for a given variable everywhere that variable is used on the dashboard they are viewing.

The toolbar can be shown or hidden with a button in the Dashboard Header.

2019-03-15 13 59 50

  • Rebased/mergeable
  • Tests pass
  • CHANGELOG.md updated

@AlirieGray AlirieGray force-pushed the dashboards/variables-toolbar branch 3 times, most recently from 2c474ce to 0688ce5 Compare March 15, 2019 21:07
Copy link
Contributor

@chnn chnn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome. I left a comment about extracting some logic out of components into selector functions that I think would be nice to do. Lemme know if you want any help with that.

@@ -16,6 +16,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'

interface PassedProps {
children: JSX.Element | JSX.Element[]
className?: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most of the other clockface components are using customClass for a prop like this

@ErrorHandling
class VariablesControlBar extends PureComponent<Props> {
render() {
// return <div className="variables-control-bar" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete me!


return (
<div className="variables-control-bar">
{Object.keys(this.variablesForDashboard).map(variableID => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just passing only the id of the variable to the VariableDropdown, and letting the VariableDropdown read its own state from Redux?

const variableIDs = Object.keys(this.valuesForDashboard)
let variablesForDash = {}

Object.keys(variables).forEach(variableID => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this stuff would belong better in a "selector" utility function. A selector is a function that reads Redux state and transforms/filters it into a nice format for a component to use. Selectors get used in the mstp for a component. That way the component stays focused only on presentation, and the selection logic can be reused across components.

Here is an example of a simple selector and where it gets used.

A lot of people also memoize selectors for performance reasons, but it's probably a detail not worth bothering with at the moment.

this.valuesForDashboard,
`${variableID}.selectedValue`
)
const name = _.get(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented below about selectors... I think this is also a good example of what could be a selector that gets used within the mstp for the VariableDropdown.

@@ -69,6 +74,20 @@ const removeVariable = (id: string): RemoveVariable => ({
payload: {id},
})

interface SelectValue {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh I just wrote something very similar. Let's merge yours first and then I'll consolidate after merging my PR.

@@ -78,6 +78,13 @@ export const variablesReducer = (
return
}

case 'SELECT_VALUE': {
const {contextID, id, value} = action.payload
draftState.values[contextID].values[id].selectedValue = value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to check that this path exists before trying to set it (e.g. that values[contextID] exists and values[id] exists and so on).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would something like if (!!draftState.values[contextID].values[id].selectedValue) work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that will still throw an error if the values[contextID] part doesn't exist, or if the values[id] part doesn't exist (would see something like Error: can't get selectedValue of null)

How about:

        const {contextID, id, selectedValue} = action.payload

        const valuesExist = !!get(
          draftState,
          `values.${contextID}.values.${id}`
        )

        if (!valuesExist) {
          return
        }

        draftState.values[contextID].values[id].selectedValue = selectedValue

since what

variableID: string
values: string[] //todo
initialSelected: string // todo
onSelect: (variableID: string, value: string) => void // todo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah, I just wanted to check with you that the values should be strings

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, yep!

Copy link
Contributor

@mavarius mavarius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments


const className = `empty-state empty-state--${size}`
const classname = className
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the logic here is starting to get more complex, we can bring in the classnames library and follow the pattern used in other components

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a good example of this pattern with a customClass property?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IndexListRow.tsx is a good example.

interface Props {
name: string
variableID: string
values: string[] //todo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these todo's?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

@ErrorHandling
class VariablesControlBar extends PureComponent<Props> {
render() {
// return <div className="variables-control-bar" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this comment?


return (
<div className="variables-control-bar">
{Object.keys(this.variablesForDashboard).map(variableID => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and the nested function calls in it) should probably be considered for refactoring. It's not very readable or maintainable in this state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

@@ -78,6 +78,13 @@ export const variablesReducer = (
return
}

case 'SELECT_VALUE': {
const {contextID, id, value} = action.payload
draftState.values[contextID].values[id].selectedValue = value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty long chain, is there any point where one of these 6 items could be undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

@AlirieGray AlirieGray force-pushed the dashboards/variables-toolbar branch 8 times, most recently from 43c96b8 to 40ee3e5 Compare March 18, 2019 19:03

const variablesIDs = Object.keys(get(values, `${dashboardID}.values`))

variablesIDs.forEach(variableID => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might supposed to be:

  variablesIDs.forEach(variableID => {
    const variable = get(variables, `${variableID}.variable`)

    if (variable) {
      variablesForDash.push(variable)
    }
  })

}

interface OwnProps {
name: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and onSelect could come from Redux too (but it's also fine like this)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of that but I thought a utility was a lot for something we already have peeled off in the parent

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's already a selector that could be useful:

export const getVariable = (state: AppState, variableID: string): Variable => {
return get(state, `variables.variables.${variableID}.variable`)
}

then mstp could look something like

const mstp = (state: AppState, props: OwnProps): StateProps => {
  const {variableID} = props

  const {name} = getVariable(state, variableID)

  return {name}
}

just as an example for how selectors can be reused.... but this is totally down the weeds and nitpicky, just wanted to share. it's totally cool as is

type Props = StateProps & OwnProps

class VariableDropdown extends PureComponent<Props> {
constructor(props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete me!

state: AppState,
variableID: string,
contextID: string
): string => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Co-Authored-By: Alex Paxton <thealexpaxton@gmail.com>
@AlirieGray AlirieGray force-pushed the dashboards/variables-toolbar branch from 40ee3e5 to 260c612 Compare March 18, 2019 20:59
@AlirieGray AlirieGray merged commit 394de87 into master Mar 18, 2019
@AlirieGray AlirieGray deleted the dashboards/variables-toolbar branch March 18, 2019 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants