-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Dashboards/variables toolbar #12655
Conversation
2c474ce
to
0688ce5
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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" /> |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
.
ui/src/variables/actions/index.ts
Outdated
@@ -69,6 +74,20 @@ const removeVariable = (id: string): RemoveVariable => ({ | |||
payload: {id}, | |||
}) | |||
|
|||
interface SelectValue { |
There was a problem hiding this comment.
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.
ui/src/variables/reducers/index.ts
Outdated
@@ -78,6 +78,13 @@ export const variablesReducer = ( | |||
return | |||
} | |||
|
|||
case 'SELECT_VALUE': { | |||
const {contextID, id, value} = action.payload | |||
draftState.values[contextID].values[id].selectedValue = value |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, yep!
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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" /> |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed
ui/src/variables/reducers/index.ts
Outdated
@@ -78,6 +78,13 @@ export const variablesReducer = ( | |||
return | |||
} | |||
|
|||
case 'SELECT_VALUE': { | |||
const {contextID, id, value} = action.payload | |||
draftState.values[contextID].values[id].selectedValue = value |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point
43c96b8
to
40ee3e5
Compare
|
||
const variablesIDs = Object.keys(get(values, `${dashboardID}.values`)) | ||
|
||
variablesIDs.forEach(variableID => { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
influxdb/ui/src/variables/selectors/index.tsx
Lines 115 to 117 in 243f1ea
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) { |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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>
40ee3e5
to
260c612
Compare
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.