feat(dashboards): add variable control bar for selecting values
Co-Authored-By: Alex Paxton <thealexpaxton@gmail.com>pull/12655/head
parent
243f1ea511
commit
260c61262f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue