Refactor single stat colors into redux state instead of CEO state

pull/10616/head
Alex P 2018-02-20 22:27:42 -08:00
parent da600f613c
commit 85d13d27ed
7 changed files with 301 additions and 285 deletions

View File

@ -22,3 +22,17 @@ export const renameCell = cellName => ({
cellName,
},
})
export const updateSingleStatColors = singleStatColors => ({
type: 'UPDATE_SINGLE_STAT_COLORS',
payload: {
singleStatColors,
},
})
export const updateSingleStatType = singleStatType => ({
type: 'UPDATE_SINGLE_STAT_TYPE',
payload: {
singleStatType,
},
})

View File

@ -25,12 +25,8 @@ import {AUTO_GROUP_BY} from 'shared/constants'
import {
COLOR_TYPE_THRESHOLD,
MAX_THRESHOLDS,
DEFAULT_VALUE_MIN,
DEFAULT_VALUE_MAX,
GAUGE_COLORS,
validateGaugeColors,
validateSingleStatColors,
getSingleStatType,
stringifyColorValues,
} from 'src/dashboards/constants/gaugeColors'
@ -51,16 +47,12 @@ class CellEditorOverlay extends Component {
}))
)
const singleStatType = getSingleStatType(colors)
this.state = {
queriesWorkingDraft,
activeQueryIndex: 0,
isDisplayOptionsTabActive: false,
axes,
singleStatType,
gaugeColors: validateGaugeColors(colors),
singleStatColors: validateSingleStatColors(colors, singleStatType),
}
}
@ -110,113 +102,39 @@ class CellEditorOverlay extends Component {
}
}
handleAddSingleStatThreshold = () => {
const {singleStatColors, singleStatType} = this.state
const randomColor = _.random(0, GAUGE_COLORS.length - 1)
const maxValue = DEFAULT_VALUE_MIN
const minValue = DEFAULT_VALUE_MAX
let randomValue = _.round(_.random(minValue, maxValue, true), 2)
if (singleStatColors.length > 0) {
const colorsValues = _.mapValues(singleStatColors, 'value')
do {
randomValue = _.round(_.random(minValue, maxValue, true), 2)
} while (_.includes(colorsValues, randomValue))
}
const newThreshold = {
type: singleStatType,
id: uuid.v4(),
value: randomValue,
hex: GAUGE_COLORS[randomColor].hex,
name: GAUGE_COLORS[randomColor].name,
}
this.setState({singleStatColors: [...singleStatColors, newThreshold]})
}
handleDeleteThreshold = threshold => () => {
const {type} = this.props.cell
const gaugeColors = this.state.gaugeColors.filter(
color => color.id !== threshold.id
)
if (type === 'gauge') {
const gaugeColors = this.state.gaugeColors.filter(
color => color.id !== threshold.id
)
this.setState({gaugeColors})
}
if (type === 'single-stat') {
const singleStatColors = this.state.singleStatColors.filter(
color => color.id !== threshold.id
)
this.setState({singleStatColors})
}
this.setState({gaugeColors})
}
handleChooseColor = threshold => chosenColor => {
const {type} = this.props.cell
const gaugeColors = this.state.gaugeColors.map(
color =>
color.id === threshold.id
? {...color, hex: chosenColor.hex, name: chosenColor.name}
: color
)
if (type === 'gauge') {
const gaugeColors = this.state.gaugeColors.map(
color =>
color.id === threshold.id
? {...color, hex: chosenColor.hex, name: chosenColor.name}
: color
)
this.setState({gaugeColors})
}
if (type === 'single-stat') {
const singleStatColors = this.state.singleStatColors.map(
color =>
color.id === threshold.id
? {...color, hex: chosenColor.hex, name: chosenColor.name}
: color
)
this.setState({singleStatColors})
}
this.setState({gaugeColors})
}
handleUpdateColorValue = (threshold, value) => {
const {type} = this.props.cell
const gaugeColors = this.state.gaugeColors.map(
color => (color.id === threshold.id ? {...color, value} : color)
)
if (type === 'gauge') {
const gaugeColors = this.state.gaugeColors.map(
color => (color.id === threshold.id ? {...color, value} : color)
)
this.setState({gaugeColors})
}
if (type === 'single-stat') {
const singleStatColors = this.state.singleStatColors.map(
color => (color.id === threshold.id ? {...color, value} : color)
)
this.setState({singleStatColors})
}
this.setState({gaugeColors})
}
handleValidateColorValue = (threshold, targetValue) => {
const {gaugeColors, singleStatColors} = this.state
const {type} = this.props.cell
const {gaugeColors} = this.state
const thresholdValue = threshold.value
let allowedToUpdate = false
if (type === 'single-stat') {
// If type is single-stat then value only has to be unique
const sortedColors = _.sortBy(singleStatColors, color => color.value)
return !sortedColors.some(color => color.value === targetValue)
}
const sortedColors = _.sortBy(gaugeColors, color => color.value)
const minValue = sortedColors[0].value
@ -252,18 +170,6 @@ class CellEditorOverlay extends Component {
return allowedToUpdate
}
handleToggleSingleStatType = type => () => {
const singleStatColors = this.state.singleStatColors.map(color => ({
...color,
type,
}))
this.setState({
singleStatType: type,
singleStatColors,
})
}
handleSetSuffix = e => {
const {axes} = this.state
@ -355,14 +261,9 @@ class CellEditorOverlay extends Component {
}
handleSaveCell = () => {
const {
queriesWorkingDraft,
axes,
gaugeColors,
singleStatColors,
} = this.state
const {queriesWorkingDraft, axes, gaugeColors} = this.state
const {cell} = this.props
const {cell, singleStatColors} = this.props
const queries = queriesWorkingDraft.map(q => {
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
@ -520,7 +421,6 @@ class CellEditorOverlay extends Component {
render() {
const {
cell,
onCancel,
templates,
timeRange,
@ -531,11 +431,9 @@ class CellEditorOverlay extends Component {
const {
axes,
gaugeColors,
singleStatColors,
activeQueryIndex,
isDisplayOptionsTabActive,
queriesWorkingDraft,
singleStatType,
} = this.state
const queryActions = {
@ -547,9 +445,6 @@ class CellEditorOverlay extends Component {
(!!query.measurement && !!query.database && !!query.fields.length) ||
!!query.rawText
const visualizationColors =
cell.type === 'gauge' ? gaugeColors : singleStatColors
return (
<div
className={OVERLAY_TECHNOLOGY}
@ -566,8 +461,6 @@ class CellEditorOverlay extends Component {
>
<Visualization
axes={axes}
colors={visualizationColors}
type={cell.type}
timeRange={timeRange}
templates={templates}
autoRefresh={autoRefresh}
@ -590,15 +483,11 @@ class CellEditorOverlay extends Component {
? <DisplayOptions
axes={axes}
gaugeColors={gaugeColors}
singleStatColors={singleStatColors}
onChooseColor={this.handleChooseColor}
onValidateColorValue={this.handleValidateColorValue}
onUpdateColorValue={this.handleUpdateColorValue}
onAddGaugeThreshold={this.handleAddGaugeThreshold}
onAddSingleStatThreshold={this.handleAddSingleStatThreshold}
onDeleteThreshold={this.handleDeleteThreshold}
onToggleSingleStatType={this.handleToggleSingleStatType}
singleStatType={singleStatType}
onSetBase={this.handleSetBase}
onSetLabel={this.handleSetLabel}
onSetScale={this.handleSetScale}
@ -663,6 +552,8 @@ CellEditorOverlay.propTypes = {
}).isRequired,
dashboardID: string.isRequired,
sources: arrayOf(shape()),
singleStatType: string.isRequired,
singleStatColors: arrayOf(shape({}).isRequired).isRequired,
}
CEOBottom.propTypes = {

View File

@ -38,7 +38,6 @@ class DisplayOptions extends Component {
const {
cell,
gaugeColors,
singleStatColors,
onSetBase,
onSetScale,
onSetLabel,
@ -46,13 +45,10 @@ class DisplayOptions extends Component {
onSetYAxisBoundMin,
onSetYAxisBoundMax,
onAddGaugeThreshold,
onAddSingleStatThreshold,
onDeleteThreshold,
onChooseColor,
onValidateColorValue,
onUpdateColorValue,
singleStatType,
onToggleSingleStatType,
onSetSuffix,
} = this.props
const {axes, axes: {y: {suffix}}} = this.state
@ -70,20 +66,7 @@ class DisplayOptions extends Component {
/>
)
case 'single-stat':
return (
<SingleStatOptions
colors={singleStatColors}
suffix={suffix}
onSetSuffix={onSetSuffix}
onChooseColor={onChooseColor}
onValidateColorValue={onValidateColorValue}
onUpdateColorValue={onUpdateColorValue}
onAddThreshold={onAddSingleStatThreshold}
onDeleteThreshold={onDeleteThreshold}
singleStatType={singleStatType}
onToggleSingleStatType={onToggleSingleStatType}
/>
)
return <SingleStatOptions suffix={suffix} onSetSuffix={onSetSuffix} />
default:
return (
<AxesOptions
@ -113,7 +96,6 @@ const {arrayOf, func, number, shape, string} = PropTypes
DisplayOptions.propTypes = {
onAddGaugeThreshold: func.isRequired,
onAddSingleStatThreshold: func.isRequired,
onDeleteThreshold: func.isRequired,
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
@ -138,18 +120,7 @@ DisplayOptions.propTypes = {
value: number.isRequired,
}).isRequired
),
singleStatColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
queryConfigs: arrayOf(shape()).isRequired,
singleStatType: string.isRequired,
onToggleSingleStatType: func.isRequired,
}
const mapStateToProps = ({cellEditorOverlay: {cell}}) => ({

View File

@ -1,5 +1,9 @@
import React, {PropTypes} from 'react'
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
import uuid from 'node-uuid'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import Threshold from 'src/dashboards/components/Threshold'
@ -7,106 +11,185 @@ import ColorDropdown from 'shared/components/ColorDropdown'
import {
GAUGE_COLORS,
DEFAULT_VALUE_MIN,
DEFAULT_VALUE_MAX,
MAX_THRESHOLDS,
SINGLE_STAT_BASE,
SINGLE_STAT_TEXT,
SINGLE_STAT_BG,
} from 'src/dashboards/constants/gaugeColors'
import {
updateSingleStatType,
updateSingleStatColors,
} from 'src/dashboards/actions/cellEditorOverlay'
const formatColor = color => {
const {hex, name} = color
return {hex, name}
}
const SingleStatOptions = ({
suffix,
onSetSuffix,
colors,
onAddThreshold,
onDeleteThreshold,
onChooseColor,
onValidateColorValue,
onUpdateColorValue,
singleStatType,
onToggleSingleStatType,
}) => {
const disableAddThreshold = colors.length > MAX_THRESHOLDS
const sortedColors = _.sortBy(colors, color => color.value)
class SingleStatOptions extends Component {
handleToggleSingleStatType = newType => () => {
const {handleUpdateSingleStatType} = this.props
return (
<FancyScrollbar
className="display-options--cell y-axis-controls"
autoHide={false}
>
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">Single Stat Controls</h5>
<div className="gauge-controls">
<button
className="btn btn-sm btn-primary gauge-controls--add-threshold"
onClick={onAddThreshold}
disabled={disableAddThreshold}
>
<span className="icon plus" /> Add Threshold
</button>
{sortedColors.map(
color =>
color.id === SINGLE_STAT_BASE
? <div className="gauge-controls--section" key={color.id}>
<div className="gauge-controls--label">Base Color</div>
<ColorDropdown
colors={GAUGE_COLORS}
selected={formatColor(color)}
onChoose={onChooseColor(color)}
stretchToFit={true}
handleUpdateSingleStatType(newType)
}
handleAddThreshold = () => {
const {
singleStatColors,
singleStatType,
handleUpdateSingleStatColors,
} = this.props
const randomColor = _.random(0, GAUGE_COLORS.length - 1)
const maxValue = DEFAULT_VALUE_MIN
const minValue = DEFAULT_VALUE_MAX
let randomValue = _.round(_.random(minValue, maxValue, true), 2)
if (singleStatColors.length > 0) {
const colorsValues = _.mapValues(singleStatColors, 'value')
do {
randomValue = _.round(_.random(minValue, maxValue, true), 2)
} while (_.includes(colorsValues, randomValue))
}
const newThreshold = {
type: singleStatType,
id: uuid.v4(),
value: randomValue,
hex: GAUGE_COLORS[randomColor].hex,
name: GAUGE_COLORS[randomColor].name,
}
handleUpdateSingleStatColors([...singleStatColors, newThreshold])
}
handleDeleteThreshold = threshold => () => {
const {handleUpdateSingleStatColors} = this.props
const singleStatColors = this.props.singleStatColors.filter(
color => color.id !== threshold.id
)
handleUpdateSingleStatColors(singleStatColors)
}
handleChooseColor = threshold => chosenColor => {
const {handleUpdateSingleStatColors} = this.props
const singleStatColors = this.props.singleStatColors.map(
color =>
color.id === threshold.id
? {...color, hex: chosenColor.hex, name: chosenColor.name}
: color
)
handleUpdateSingleStatColors(singleStatColors)
}
handleUpdateColorValue = (threshold, value) => {
const {handleUpdateSingleStatColors} = this.props
const singleStatColors = this.props.singleStatColors.map(
color => (color.id === threshold.id ? {...color, value} : color)
)
handleUpdateSingleStatColors(singleStatColors)
}
handleValidateColorValue = (threshold, targetValue) => {
const {singleStatColors} = this.props
const sortedColors = _.sortBy(singleStatColors, color => color.value)
return !sortedColors.some(color => color.value === targetValue)
}
render() {
const {suffix, onSetSuffix, singleStatColors, singleStatType} = this.props
const disableAddThreshold = singleStatColors.length > MAX_THRESHOLDS
const sortedColors = _.sortBy(singleStatColors, color => color.value)
return (
<FancyScrollbar
className="display-options--cell y-axis-controls"
autoHide={false}
>
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">Single Stat Controls</h5>
<div className="gauge-controls">
<button
className="btn btn-sm btn-primary gauge-controls--add-threshold"
onClick={this.handleAddThreshold}
disabled={disableAddThreshold}
>
<span className="icon plus" /> Add Threshold
</button>
{sortedColors.map(
color =>
color.id === SINGLE_STAT_BASE
? <div className="gauge-controls--section" key={color.id}>
<div className="gauge-controls--label">Base Color</div>
<ColorDropdown
colors={GAUGE_COLORS}
selected={formatColor(color)}
onChoose={this.handleChooseColor(color)}
stretchToFit={true}
/>
</div>
: <Threshold
visualizationType="single-stat"
threshold={color}
key={color.id}
onChooseColor={this.handleChooseColor}
onValidateColorValue={this.handleValidateColorValue}
onUpdateColorValue={this.handleUpdateColorValue}
onDeleteThreshold={this.handleDeleteThreshold}
/>
</div>
: <Threshold
visualizationType="single-stat"
threshold={color}
key={color.id}
onChooseColor={onChooseColor}
onValidateColorValue={onValidateColorValue}
onUpdateColorValue={onUpdateColorValue}
onDeleteThreshold={onDeleteThreshold}
/>
)}
</div>
<div className="single-stat-controls">
<div className="form-group col-xs-6">
<label>Coloring</label>
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={`${singleStatType === SINGLE_STAT_BG
? 'active'
: ''}`}
onClick={onToggleSingleStatType(SINGLE_STAT_BG)}
>
Background
</li>
<li
className={`${singleStatType === SINGLE_STAT_TEXT
? 'active'
: ''}`}
onClick={onToggleSingleStatType(SINGLE_STAT_TEXT)}
>
Text
</li>
</ul>
)}
</div>
<div className="form-group col-xs-6">
<label>Suffix</label>
<input
className="form-control input-sm"
placeholder="%, MPH, etc."
defaultValue={suffix}
onChange={onSetSuffix}
maxLength="5"
/>
<div className="single-stat-controls">
<div className="form-group col-xs-6">
<label>Coloring</label>
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={`${singleStatType === SINGLE_STAT_BG
? 'active'
: ''}`}
onClick={this.handleToggleSingleStatType(SINGLE_STAT_BG)}
>
Background
</li>
<li
className={`${singleStatType === SINGLE_STAT_TEXT
? 'active'
: ''}`}
onClick={this.handleToggleSingleStatType(SINGLE_STAT_TEXT)}
>
Text
</li>
</ul>
</div>
<div className="form-group col-xs-6">
<label>Suffix</label>
<input
className="form-control input-sm"
placeholder="%, MPH, etc."
defaultValue={suffix}
onChange={onSetSuffix}
maxLength="5"
/>
</div>
</div>
</div>
</div>
</FancyScrollbar>
)
</FancyScrollbar>
)
}
}
const {arrayOf, func, number, shape, string} = PropTypes
@ -116,7 +199,8 @@ SingleStatOptions.defaultProps = {
}
SingleStatOptions.propTypes = {
colors: arrayOf(
singleStatType: string.isRequired,
singleStatColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
@ -125,15 +209,28 @@ SingleStatOptions.propTypes = {
value: number.isRequired,
}).isRequired
),
onAddThreshold: func.isRequired,
onDeleteThreshold: func.isRequired,
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
onUpdateColorValue: func.isRequired,
singleStatType: string.isRequired,
onToggleSingleStatType: func.isRequired,
onSetSuffix: func.isRequired,
suffix: string.isRequired,
handleUpdateSingleStatType: func.isRequired,
handleUpdateSingleStatColors: func.isRequired,
}
export default SingleStatOptions
const mapStateToProps = ({
cellEditorOverlay: {singleStatType, singleStatColors},
}) => ({
singleStatType,
singleStatColors,
})
const mapDispatchToProps = dispatch => ({
handleUpdateSingleStatType: bindActionCreators(
updateSingleStatType,
dispatch
),
handleUpdateSingleStatColors: bindActionCreators(
updateSingleStatColors,
dispatch
),
})
export default connect(mapStateToProps, mapDispatchToProps)(SingleStatOptions)

View File

@ -1,4 +1,6 @@
import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import RefreshingGraph from 'shared/components/RefreshingGraph'
import buildQueries from 'utils/buildQueriesForGraphs'
import VisualizationName from 'src/dashboards/components/VisualizationName'
@ -9,40 +11,41 @@ const DashVisualization = (
{
axes,
type,
colors,
templates,
timeRange,
autoRefresh,
queryConfigs,
editQueryStatus,
resizerTopHeight,
singleStatColors,
},
{source: {links: {proxy}}}
) =>
<div className="graph">
<VisualizationName />
<div className="graph-container">
<RefreshingGraph
colors={stringifyColorValues(colors)}
axes={axes}
type={type}
queries={buildQueries(proxy, queryConfigs, timeRange)}
templates={templates}
autoRefresh={autoRefresh}
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
/>
) => {
// const colors = type === 'gauge' ? gaugeColors : singleStatColors
return (
<div className="graph">
<VisualizationName />
<div className="graph-container">
<RefreshingGraph
colors={stringifyColorValues(singleStatColors)}
axes={axes}
type={type}
queries={buildQueries(proxy, queryConfigs, timeRange)}
templates={templates}
autoRefresh={autoRefresh}
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
/>
</div>
</div>
</div>
)
}
const {arrayOf, func, number, shape, string} = PropTypes
DashVisualization.defaultProps = {
type: '',
}
DashVisualization.propTypes = {
type: string,
type: string.isRequired,
autoRefresh: number.isRequired,
templates: arrayOf(shape()),
timeRange: shape({
@ -57,15 +60,11 @@ DashVisualization.propTypes = {
}),
}),
resizerTopHeight: number,
colors: arrayOf(
singleStatColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
})
),
}).isRequired
).isRequired,
}
DashVisualization.contextTypes = {
@ -76,4 +75,11 @@ DashVisualization.contextTypes = {
}).isRequired,
}
export default DashVisualization
const mapStateToProps = ({
cellEditorOverlay: {singleStatColors, cell: {type}},
}) => ({
singleStatColors,
type,
})
export default connect(mapStateToProps, null)(DashVisualization)

View File

@ -244,6 +244,8 @@ class DashboardPage extends Component {
manualRefresh,
onManualRefresh,
cellQueryStatus,
singleStatType,
singleStatColors,
dashboardActions,
inPresentationMode,
handleChooseAutoRefresh,
@ -348,6 +350,8 @@ class DashboardPage extends Component {
onCancel={handleHideCellEditorOverlay}
templates={templatesIncludingDashTime}
editQueryStatus={dashboardActions.editCellQueryStatus}
singleStatType={singleStatType}
singleStatColors={singleStatColors}
/>
: null}
<DashboardHeader
@ -473,6 +477,8 @@ DashboardPage.propTypes = {
handleShowCellEditorOverlay: func.isRequired,
handleHideCellEditorOverlay: func.isRequired,
selectedCell: shape({}),
singleStatType: string.isRequired,
singleStatColors: arrayOf(shape({}).isRequired).isRequired,
}
const mapStateToProps = (state, {params: {dashboardID}}) => {
@ -485,7 +491,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
sources,
dashTimeV1,
auth: {me, isUsingAuth},
cellEditorOverlay: {cell},
cellEditorOverlay: {cell, singleStatType, singleStatColors},
} = state
const meRole = _.get(me, 'role', null)
@ -511,6 +517,8 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
meRole,
isUsingAuth,
selectedCell,
singleStatType,
singleStatColors,
}
}

View File

@ -1,13 +1,25 @@
import {
SINGLE_STAT_TEXT,
DEFAULT_SINGLESTAT_COLORS,
validateSingleStatColors,
getSingleStatType,
} from 'src/dashboards/constants/gaugeColors'
const initialState = {
cell: null,
singleStatType: SINGLE_STAT_TEXT,
singleStatColors: DEFAULT_SINGLESTAT_COLORS,
}
export default function cellEditorOverlay(state = initialState, action) {
switch (action.type) {
case 'SHOW_CELL_EDITOR_OVERLAY': {
const {cell} = action.payload
const {cell, cell: {colors}} = action.payload
return {...state, cell}
const singleStatType = getSingleStatType(colors)
const singleStatColors = validateSingleStatColors(colors, singleStatType)
return {...state, cell, singleStatType, singleStatColors}
}
case 'HIDE_CELL_EDITOR_OVERLAY': {
@ -29,6 +41,23 @@ export default function cellEditorOverlay(state = initialState, action) {
return {...state, cell}
}
case 'UPDATE_SINGLE_STAT_COLORS': {
const {singleStatColors} = action.payload
return {...state, singleStatColors}
}
case 'UPDATE_SINGLE_STAT_TYPE': {
const {singleStatType} = action.payload
const singleStatColors = state.singleStatColors.map(color => ({
...color,
type: singleStatType,
}))
return {...state, singleStatType, singleStatColors}
}
}
return state