feat(dashboards): add variable control bar for selecting values

Co-Authored-By: Alex Paxton <thealexpaxton@gmail.com>
pull/12655/head
Alirie Gray 2019-03-12 11:10:46 -07:00
parent 243f1ea511
commit 260c61262f
9 changed files with 345 additions and 6 deletions

View File

@ -4,16 +4,19 @@
1. [12663](https://github.com/influxdata/influxdb/pull/12663): Insert flux function near cursor in flux editor
1. [12678](https://github.com/influxdata/influxdb/pull/12678): Enable the use of variables in the Data Explorer and Cell Editor Overlay
1. [12655](https://github.com/influxdata/influxdb/pull/12655): Add a variable control bar to dashboards to select values for variables.
### Bug Fixes
1. [12684](https://github.com/influxdata/influxdb/pull/12684): Fix mismatch in bucket row and header
1. [12684](https://github.com/influxdata/influxdb/pull/12684): Fix mismatch in bucket row and header
### UI Improvements
## v2.0.0-alpha.6 [2019-03-15]
### Release Notes
We have updated the way we do predefined dashboards to [include Templates](https://github.com/influxdata/influxdb/pull/12532) in this release which will cause existing Organizations to not have a System dashboard created when they build a new Telegraf configuration. In order to get this functionality, remove your existing data and start from scratch.
We have updated the way we do predefined dashboards to [include Templates](https://github.com/influxdata/influxdb/pull/12532) in this release which will cause existing Organizations to not have a System dashboard created when they build a new Telegraf configuration. In order to get this functionality, remove your existing data and start from scratch.
**NOTE: This will remove all data from your InfluxDB v2.0 instance including timeseries data.**
@ -35,6 +38,7 @@ Once completed, `v2.0.0-alpha.6` can be started.
1. [12532](https://github.com/influxdata/influxdb/pull/12532): Add System template on onboarding
### Bug Fixes
1. [12641](https://github.com/influxdata/influxdb/pull/12641): Stop scrollbars from covering text in flux editor
### UI Improvements

View File

@ -1,5 +1,6 @@
// Libraries
import React, {Component} from 'react'
import classnames from 'classnames'
// Components
import EmptyStateText from 'src/clockface/components/empty_state/EmptyStateText'
@ -16,6 +17,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
interface PassedProps {
children: JSX.Element | JSX.Element[]
customClass?: string
}
interface DefaultProps {
@ -36,16 +38,23 @@ class EmptyState extends Component<Props> {
public static SubText = EmptyStateSubText
public render() {
const {children, size, testID} = this.props
const className = `empty-state empty-state--${size}`
const {children, testID} = this.props
return (
<div className={className} data-testid={testID}>
<div className={this.className} data-testid={testID}>
{children}
</div>
)
}
private get className(): string {
const {customClass, size} = this.props
return classnames('empty-state', {
[`empty-state--${size}`]: size,
[customClass]: customClass,
})
}
}
export default EmptyState

View File

@ -42,6 +42,8 @@ interface OwnProps {
showTemplateControlBar: boolean
zoomedTimeRange: QueriesModels.TimeRange
onRenameDashboard: (name: string) => Promise<void>
toggleVariablesControlBar: () => void
isShowingVariablesControlBar: boolean
isHidden: boolean
}
@ -69,6 +71,8 @@ class DashboardHeader extends Component<Props> {
zoomedTimeRange: {upper: zoomedUpper, lower: zoomedLower},
isHidden,
onAddNote,
toggleVariablesControlBar,
isShowingVariablesControlBar,
} = this.props
return (
@ -94,6 +98,15 @@ class DashboardHeader extends Component<Props> {
lower: zoomedLower || lower,
}}
/>
<Button
text="Variables"
onClick={toggleVariablesControlBar}
color={
isShowingVariablesControlBar
? ComponentColor.Primary
: ComponentColor.Default
}
/>
<Button
icon={IconFont.ExpandA}
titleText="Enter Presentation Mode"

View File

@ -12,6 +12,7 @@ import DashboardComponent from 'src/dashboards/components/Dashboard'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import {HoverTimeProvider} from 'src/dashboards/utils/hoverTime'
import NoteEditorContainer from 'src/dashboards/components/NoteEditorContainer'
import VariablesControlBar from 'src/dashboards/components/variablesControlBar/VariablesControlBar'
// Actions
import * as dashboardActions from 'src/dashboards/actions'
@ -91,6 +92,7 @@ interface State {
scrollTop: number
windowHeight: number
isShowingVEO: boolean
isShowingVariablesControlBar: boolean
}
@ErrorHandling
@ -102,6 +104,7 @@ class DashboardPage extends Component<Props, State> {
scrollTop: 0,
windowHeight: window.innerHeight,
isShowingVEO: false,
isShowingVariablesControlBar: false,
}
}
@ -150,6 +153,7 @@ class DashboardPage extends Component<Props, State> {
handleClickPresentationButton,
children,
} = this.props
const {isShowingVariablesControlBar} = this.state
return (
<Page titleTag={this.pageTitle}>
@ -168,7 +172,12 @@ class DashboardPage extends Component<Props, State> {
handleChooseAutoRefresh={handleChooseAutoRefresh}
handleChooseTimeRange={this.handleChooseTimeRange}
handleClickPresentationButton={handleClickPresentationButton}
toggleVariablesControlBar={this.toggleVariablesControlBar}
isShowingVariablesControlBar={isShowingVariablesControlBar}
/>
{isShowingVariablesControlBar && (
<VariablesControlBar dashboardID={dashboard.id} />
)}
{!!dashboard && (
<DashboardComponent
inView={this.inView}
@ -275,6 +284,12 @@ class DashboardPage extends Component<Props, State> {
this.setState({windowHeight: window.innerHeight})
}
private toggleVariablesControlBar = (): void => {
this.setState({
isShowingVariablesControlBar: !this.state.isShowingVariablesControlBar,
})
}
private get pageTitle(): string {
const {dashboard} = this.props

View File

@ -0,0 +1,34 @@
@import 'src/style/modules';
.variable-dropdown {
display: flex;
flex-direction: row;
height: $form-sm-height;
}
.variable-dropdown--dropdown {
.button {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
}
.variable-dropdown--label {
user-select: none;
line-height: $form-sm-height;
font-size: $form-sm-font;
font-weight: 600;
color: $c-comet;
white-space: nowrap;
overflow: hidden;
padding: 0 $form-sm-padding;
border-radius: $radius 0 0 $radius;
background-color: $g3-castle;
background-attachment: fixed;
> span {
@include gradient-h($c-comet, $c-laser);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}

View File

@ -0,0 +1,93 @@
// Libraries
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
// Components
import {Dropdown, DropdownMenuColors} from 'src/clockface'
// Utils
import {
getValuesForVariable,
getSelectedValueForVariable,
} from 'src/variables/selectors'
// Styles
import 'src/dashboards/components/variablesControlBar/VariableDropdown.scss'
// Types
import {AppState} from 'src/types/v2'
interface StateProps {
values: string[]
selectedValue: string
}
interface OwnProps {
name: string
variableID: string
dashboardID: string
onSelect: (variableID: string, value: string) => void
}
type Props = StateProps & OwnProps
class VariableDropdown extends PureComponent<Props> {
render() {
const {name, selectedValue} = this.props
return (
<div className="variable-dropdown">
{/* TODO: Add variable description to title attribute when it is ready */}
<div className="variable-dropdown--label">
<span>{name}</span>
</div>
<Dropdown
selectedID={selectedValue}
onChange={this.handleSelect}
widthPixels={140}
customClass="variable-dropdown--dropdown"
menuColor={DropdownMenuColors.Amethyst}
>
{this.dropdownItems}
</Dropdown>
</div>
)
}
private get dropdownItems(): JSX.Element[] {
const {values} = this.props
return values.map(v => {
return (
<Dropdown.Item key={v} id={v} value={v}>
{v}
</Dropdown.Item>
)
})
}
private handleSelect = (value: string) => {
const {variableID, onSelect} = this.props
onSelect(variableID, value)
}
}
const mstp = (state: AppState, props: OwnProps): StateProps => {
const {dashboardID, variableID} = props
const values = getValuesForVariable(state, variableID, dashboardID)
const selectedValue = getSelectedValueForVariable(
state,
variableID,
dashboardID
)
return {values, selectedValue}
}
export default connect<StateProps, {}, OwnProps>(
mstp,
null
)(VariableDropdown)

View File

@ -0,0 +1,35 @@
@import 'src/style/modules';
$variables-control-bar--height: $page-header-size -
(($page-header-size - $form-sm-height) / 2);
$variables-control-bar--gutter: $ix-marg-a;
.variables-control-bar {
min-height: $variables-control-bar--height;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
padding: 0 ($page-gutter - ($variables-control-bar--gutter / 2));
padding-bottom: (($page-header-size - $form-sm-height) / 2) -
$variables-control-bar--gutter;
/* Adjust height of Page Contents when control bar is present */
& + .page-contents {
height: calc(
100% - #{$variables-control-bar--height + $page-header-size}
) !important;
}
.variable-dropdown {
margin: 0 $variables-control-bar--gutter / 2;
margin-bottom: $variables-control-bar--gutter;
}
}
.variables-control-bar--empty {
background-color: $g3-castle;
border-radius: $radius;
height: 100%;
justify-content: center;
}

View File

@ -0,0 +1,92 @@
// Libraries
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
// Components
import VariableDropdown from 'src/dashboards/components/variablesControlBar/VariableDropdown'
import {EmptyState, ComponentSize} from 'src/clockface'
// Utils
import {getVariablesForDashboard} from 'src/variables/selectors'
// Styles
import 'src/dashboards/components/variablesControlBar/VariablesControlBar.scss'
// Actions
import {selectValue} from 'src/variables/actions'
// Types
import {AppState} from 'src/types/v2'
import {Variable} from '@influxdata/influx'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface OwnProps {
dashboardID: string
}
interface StateProps {
variables: Variable[]
}
interface DispatchProps {
selectValue: typeof selectValue
}
type Props = StateProps & DispatchProps & OwnProps
@ErrorHandling
class VariablesControlBar extends PureComponent<Props> {
render() {
const {dashboardID, variables} = this.props
if (_.isEmpty(variables)) {
return (
<div className="variables-control-bar">
<EmptyState
size={ComponentSize.ExtraSmall}
customClass="variables-control-bar--empty"
>
<EmptyState.Text text="To see variable controls here, use a variable in a cell query" />
</EmptyState>
</div>
)
}
return (
<div className="variables-control-bar">
{variables.map(v => {
return (
<VariableDropdown
key={v.id}
name={v.name}
variableID={v.id}
dashboardID={dashboardID}
onSelect={this.handleSelectValue}
/>
)
})}
</div>
)
}
private handleSelectValue = (variableID: string, value: string) => {
const {selectValue, dashboardID} = this.props
selectValue(dashboardID, variableID, value)
}
}
const mdtp = {
selectValue: selectValue,
}
const mstp = (state: AppState, props: OwnProps): StateProps => {
return {variables: getVariablesForDashboard(state, props.dashboardID)}
}
export default connect<StateProps, DispatchProps, OwnProps>(
mstp,
mdtp
)(VariablesControlBar)

View File

@ -32,6 +32,50 @@ export const getVariablesForOrg = (
return getVariablesForOrgMemoized(state.variables.variables, orgID)
}
export const getVariablesForDashboard = (
state: AppState,
dashboardID: string
): Variable[] => {
const {
variables: {variables, values},
} = state
let variablesForDash = []
const variablesIDs = Object.keys(get(values, `${dashboardID}.values`))
variablesIDs.forEach(variableID => {
const variable = get(variables, `${variableID}.variable`)
if (variable) {
variablesForDash.push(variable)
}
})
return variablesForDash
}
export const getValuesForVariable = (
state: AppState,
variableID: string,
contextID: string
): string[] => {
const {variables} = state
return get(variables, `values.${contextID}.values.${variableID}.values`)
}
export const getSelectedValueForVariable = (
state: AppState,
variableID: string,
contextID: string
): string => {
const {variables} = state
return get(
variables,
`values.${contextID}.values.${variableID}.selectedValue`
)
}
export const getValueSelections = (
state: AppState,
contextID: string