diff --git a/CHANGELOG.md b/CHANGELOG.md index e72b29dc8..37b199ef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ 1. [#2698](https://github.com/influxdata/chronograf/pull/2698): Improve clarity of terminology surrounding InfluxDB & Kapacitor connections 1. [#2746](https://github.com/influxdata/chronograf/pull/2746): Separate saving TICKscript from exiting editor page 1. [#2774](https://github.com/influxdata/chronograf/pull/2774): Enable Save (⌘ + Enter) and Cancel (Escape) hotkeys in Cell Editor Overlay +1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Enable customization of Single Stat graph's "Base Color" ### Bug Fixes 1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected @@ -23,6 +24,7 @@ 1. [#2756](https://github.com/influxdata/chronograf/pull/2756): Display only 200 most recent TICKscript log messages and prevent overlapping 1. [#2757](https://github.com/influxdata/chronograf/pull/2757): Added "TO" field to kapacitor SMTP config, and improved error messages for config saving and testing 1. [#2761](https://github.com/influxdata/chronograf/pull/2761): Remove cli options from sysvinit service file +1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Fix disappearance of text in Single Stat graphs during editing 1. [#2780](https://github.com/influxdata/chronograf/pull/2780): Fix routing on alert save ## v1.4.0.1 [2017-1-9] diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 8432c8f31..5b82c428d 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -28,9 +28,10 @@ import { DEFAULT_VALUE_MIN, DEFAULT_VALUE_MAX, GAUGE_COLORS, - SINGLE_STAT_TEXT, - SINGLE_STAT_BG, - validateColors, + validateGaugeColors, + validateSingleStatColors, + getSingleStatType, + stringifyColorValues, } from 'src/dashboards/constants/gaugeColors' class CellEditorOverlay extends Component { @@ -49,7 +50,8 @@ class CellEditorOverlay extends Component { source, })) ) - const colorsTypeContainsText = _.some(colors, {type: SINGLE_STAT_TEXT}) + + const singleStatType = getSingleStatType(colors) this.state = { cellWorkingName: name, @@ -58,8 +60,9 @@ class CellEditorOverlay extends Component { activeQueryIndex: 0, isDisplayOptionsTabActive: false, axes, - colorSingleStatText: colorsTypeContainsText, - colors: validateColors(colors, type, colorsTypeContainsText), + singleStatType, + gaugeColors: validateGaugeColors(colors), + singleStatColors: validateSingleStatColors(colors, singleStatType), } } @@ -80,27 +83,21 @@ class CellEditorOverlay extends Component { this.overlayRef.focus() } - handleAddThreshold = () => { - const {colors, cellWorkingType} = this.state - const sortedColors = _.sortBy(colors, color => Number(color.value)) + handleAddGaugeThreshold = () => { + const {gaugeColors} = this.state + const sortedColors = _.sortBy(gaugeColors, color => color.value) if (sortedColors.length <= MAX_THRESHOLDS) { const randomColor = _.random(0, GAUGE_COLORS.length - 1) - const maxValue = - cellWorkingType === 'gauge' - ? Number(sortedColors[sortedColors.length - 1].value) - : DEFAULT_VALUE_MAX - const minValue = - cellWorkingType === 'gauge' - ? Number(sortedColors[0].value) - : DEFAULT_VALUE_MIN + const maxValue = sortedColors[sortedColors.length - 1].value + const minValue = sortedColors[0].value - const colorsValues = _.mapValues(colors, 'value') + const colorsValues = _.mapValues(gaugeColors, 'value') let randomValue do { - randomValue = `${_.round(_.random(minValue, maxValue, true), 2)}` + randomValue = _.round(_.random(minValue, maxValue, true), 2) } while (_.includes(colorsValues, randomValue)) const newThreshold = { @@ -111,68 +108,134 @@ class CellEditorOverlay extends Component { name: GAUGE_COLORS[randomColor].name, } - this.setState({colors: [...colors, newThreshold]}) + this.setState({gaugeColors: [...gaugeColors, newThreshold]}) } } + 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 {colors} = this.state + const {cellWorkingType} = this.state - const newColors = colors.filter(color => color.id !== threshold.id) + if (cellWorkingType === 'gauge') { + const gaugeColors = this.state.gaugeColors.filter( + color => color.id !== threshold.id + ) - this.setState({colors: newColors}) + this.setState({gaugeColors}) + } + + if (cellWorkingType === 'single-stat') { + const singleStatColors = this.state.singleStatColors.filter( + color => color.id !== threshold.id + ) + + this.setState({singleStatColors}) + } } handleChooseColor = threshold => chosenColor => { - const {colors} = this.state + const {cellWorkingType} = this.state - const newColors = colors.map( - color => - color.id === threshold.id - ? {...color, hex: chosenColor.hex, name: chosenColor.name} - : color - ) + if (cellWorkingType === 'gauge') { + const gaugeColors = this.state.gaugeColors.map( + color => + color.id === threshold.id + ? {...color, hex: chosenColor.hex, name: chosenColor.name} + : color + ) - this.setState({colors: newColors}) + this.setState({gaugeColors}) + } + + if (cellWorkingType === 'single-stat') { + const singleStatColors = this.state.singleStatColors.map( + color => + color.id === threshold.id + ? {...color, hex: chosenColor.hex, name: chosenColor.name} + : color + ) + + this.setState({singleStatColors}) + } } - handleUpdateColorValue = (threshold, newValue) => { - const {colors} = this.state - const newColors = colors.map( - color => (color.id === threshold.id ? {...color, value: newValue} : color) - ) - this.setState({colors: newColors}) + handleUpdateColorValue = (threshold, value) => { + const {cellWorkingType} = this.state + + if (cellWorkingType === 'gauge') { + const gaugeColors = this.state.gaugeColors.map( + color => (color.id === threshold.id ? {...color, value} : color) + ) + + this.setState({gaugeColors}) + } + + if (cellWorkingType === 'single-stat') { + const singleStatColors = this.state.singleStatColors.map( + color => (color.id === threshold.id ? {...color, value} : color) + ) + + this.setState({singleStatColors}) + } } - handleValidateColorValue = (threshold, e) => { - const {colors, cellWorkingType} = this.state - const sortedColors = _.sortBy(colors, color => Number(color.value)) - const thresholdValue = Number(threshold.value) - const targetValueNumber = Number(e.target.value) + handleValidateColorValue = (threshold, targetValue) => { + const {gaugeColors, singleStatColors, cellWorkingType} = this.state + const thresholdValue = threshold.value let allowedToUpdate = false if (cellWorkingType === 'single-stat') { // If type is single-stat then value only has to be unique - return !sortedColors.some(color => color.value === e.target.value) + const sortedColors = _.sortBy(singleStatColors, color => color.value) + return !sortedColors.some(color => color.value === targetValue) } - const minValue = Number(sortedColors[0].value) - const maxValue = Number(sortedColors[sortedColors.length - 1].value) + const sortedColors = _.sortBy(gaugeColors, color => color.value) + + const minValue = sortedColors[0].value + const maxValue = sortedColors[sortedColors.length - 1].value // If lowest value, make sure it is less than the next threshold if (thresholdValue === minValue) { - const nextValue = Number(sortedColors[1].value) - allowedToUpdate = targetValueNumber < nextValue + const nextValue = sortedColors[1].value + allowedToUpdate = targetValue < nextValue } // If highest value, make sure it is greater than the previous threshold if (thresholdValue === maxValue) { - const previousValue = Number(sortedColors[sortedColors.length - 2].value) - allowedToUpdate = previousValue < targetValueNumber + const previousValue = sortedColors[sortedColors.length - 2].value + allowedToUpdate = previousValue < targetValue } // If not min or max, make sure new value is greater than min, less than max, and unique if (thresholdValue !== minValue && thresholdValue !== maxValue) { - const greaterThanMin = targetValueNumber > minValue - const lessThanMax = targetValueNumber < maxValue + const greaterThanMin = targetValue > minValue + const lessThanMax = targetValue < maxValue const colorsWithoutMinOrMax = sortedColors.slice( 1, @@ -180,7 +243,7 @@ class CellEditorOverlay extends Component { ) const isUnique = !colorsWithoutMinOrMax.some( - color => color.value === e.target.value + color => color.value === targetValue ) allowedToUpdate = greaterThanMin && lessThanMax && isUnique @@ -189,16 +252,15 @@ class CellEditorOverlay extends Component { return allowedToUpdate } - handleToggleSingleStatText = () => { - const {colors, colorSingleStatText} = this.state - const formattedColors = colors.map(color => ({ + handleToggleSingleStatType = type => () => { + const singleStatColors = this.state.singleStatColors.map(color => ({ ...color, - type: colorSingleStatText ? SINGLE_STAT_BG : SINGLE_STAT_TEXT, + type, })) this.setState({ - colorSingleStatText: !colorSingleStatText, - colors: formattedColors, + singleStatType: type, + singleStatColors, }) } @@ -298,7 +360,8 @@ class CellEditorOverlay extends Component { cellWorkingType: type, cellWorkingName: name, axes, - colors, + gaugeColors, + singleStatColors, } = this.state const {cell} = this.props @@ -314,6 +377,13 @@ class CellEditorOverlay extends Component { } }) + let colors = [] + if (type === 'gauge') { + colors = stringifyColorValues(gaugeColors) + } else if (type === 'single-stat' || type === 'line-plus-single-stat') { + colors = stringifyColorValues(singleStatColors) + } + this.props.onSave({ ...cell, name, @@ -324,14 +394,8 @@ class CellEditorOverlay extends Component { }) } - handleSelectGraphType = graphType => () => { - const {colors, colorSingleStatText} = this.state - const validatedColors = validateColors( - colors, - graphType, - colorSingleStatText - ) - this.setState({cellWorkingType: graphType, colors: validatedColors}) + handleSelectGraphType = cellWorkingType => () => { + this.setState({cellWorkingType}) } handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => { @@ -474,13 +538,14 @@ class CellEditorOverlay extends Component { const { axes, - colors, + gaugeColors, + singleStatColors, activeQueryIndex, cellWorkingName, cellWorkingType, isDisplayOptionsTabActive, queriesWorkingDraft, - colorSingleStatText, + singleStatType, } = this.state const queryActions = { @@ -492,6 +557,9 @@ class CellEditorOverlay extends Component { (!!query.measurement && !!query.database && !!query.fields.length) || !!query.rawText + const visualizationColors = + cellWorkingType === 'gauge' ? gaugeColors : singleStatColors + return (
{ const { - colors, + gaugeColors, + singleStatColors, onSetBase, onSetScale, onSetLabel, @@ -43,13 +44,14 @@ class DisplayOptions extends Component { onSetPrefixSuffix, onSetYAxisBoundMin, onSetYAxisBoundMax, - onAddThreshold, + onAddGaugeThreshold, + onAddSingleStatThreshold, onDeleteThreshold, onChooseColor, onValidateColorValue, onUpdateColorValue, - colorSingleStatText, - onToggleSingleStatText, + singleStatType, + onToggleSingleStatType, onSetSuffix, } = this.props const {axes, axes: {y: {suffix}}} = this.state @@ -58,27 +60,27 @@ class DisplayOptions extends Component { case 'gauge': return ( ) case 'single-stat': return ( ) default: @@ -111,10 +113,11 @@ class DisplayOptions extends Component { ) } } -const {arrayOf, bool, func, shape, string} = PropTypes +const {arrayOf, func, number, shape, string} = PropTypes DisplayOptions.propTypes = { - onAddThreshold: func.isRequired, + onAddGaugeThreshold: func.isRequired, + onAddSingleStatThreshold: func.isRequired, onDeleteThreshold: func.isRequired, onChooseColor: func.isRequired, onValidateColorValue: func.isRequired, @@ -129,18 +132,27 @@ DisplayOptions.propTypes = { onSetLabel: func.isRequired, onSetBase: func.isRequired, axes: shape({}).isRequired, - colors: arrayOf( + gaugeColors: arrayOf( shape({ type: string.isRequired, hex: string.isRequired, id: string.isRequired, name: string.isRequired, - value: string.isRequired, + 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, - colorSingleStatText: bool.isRequired, - onToggleSingleStatText: func.isRequired, + singleStatType: string.isRequired, + onToggleSingleStatType: func.isRequired, } export default DisplayOptions diff --git a/ui/src/dashboards/components/GaugeOptions.js b/ui/src/dashboards/components/GaugeOptions.js index 8a70c37bf..4c42cc8e0 100644 --- a/ui/src/dashboards/components/GaugeOptions.js +++ b/ui/src/dashboards/components/GaugeOptions.js @@ -19,7 +19,7 @@ const GaugeOptions = ({ }) => { const disableMaxColor = colors.length > MIN_THRESHOLDS const disableAddThreshold = colors.length > MAX_THRESHOLDS - const sortedColors = _.sortBy(colors, color => Number(color.value)) + const sortedColors = _.sortBy(colors, color => color.value) return ( { + const {hex, name} = color + return {hex, name} +} const SingleStatOptions = ({ suffix, onSetSuffix, @@ -15,12 +26,12 @@ const SingleStatOptions = ({ onChooseColor, onValidateColorValue, onUpdateColorValue, - colorSingleStatText, - onToggleSingleStatText, + singleStatType, + onToggleSingleStatType, }) => { const disableAddThreshold = colors.length > MAX_THRESHOLDS - const sortedColors = _.sortBy(colors, color => Number(color.value)) + const sortedColors = _.sortBy(colors, color => color.value) return ( Add Threshold - {sortedColors.map(color => - + {sortedColors.map( + color => + color.id === SINGLE_STAT_BASE + ?
+
Base Color
+ +
+ : )}
@@ -54,14 +76,18 @@ const SingleStatOptions = ({