diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js new file mode 100644 index 0000000000..71ac90bb31 --- /dev/null +++ b/ui/src/shared/components/ColorDropdown.js @@ -0,0 +1,115 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' + +import classnames from 'classnames' +import OnClickOutside from 'shared/components/OnClickOutside' +import FancyScrollbar from 'shared/components/FancyScrollbar' +import {ErrorHandling} from 'src/shared/decorators/errors' +import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index' + +@ErrorHandling +class ColorDropdown extends Component { + constructor(props) { + super(props) + + this.state = { + visible: false, + } + } + + handleToggleMenu = () => { + const {disabled} = this.props + + if (disabled) { + return + } + this.setState({visible: !this.state.visible}) + } + + handleClickOutside = () => { + this.setState({visible: false}) + } + + handleColorClick = color => () => { + this.props.onChoose(color) + this.setState({visible: false}) + } + + render() { + const {visible} = this.state + const {colors, selected, disabled, stretchToFit} = this.props + + const dropdownClassNames = classnames('color-dropdown', { + open: visible, + 'color-dropdown--stretch': stretchToFit, + }) + const toggleClassNames = classnames( + 'btn btn-sm btn-default color-dropdown--toggle', + {active: visible, 'color-dropdown__disabled': disabled} + ) + + return ( +
+
+
+
{selected.name}
+ +
+ {visible ? ( +
+ + {colors.map((color, i) => ( +
+ + {color.name} +
+ ))} +
+
+ ) : null} +
+ ) + } +} + +const {arrayOf, bool, func, shape, string} = PropTypes + +ColorDropdown.propTypes = { + selected: shape({ + hex: string.isRequired, + name: string.isRequired, + }).isRequired, + onChoose: func.isRequired, + colors: arrayOf( + shape({ + hex: string.isRequired, + name: string.isRequired, + }).isRequired + ).isRequired, + stretchToFit: bool, + disabled: bool, +} + +export default OnClickOutside(ColorDropdown) diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js deleted file mode 100644 index 4a396dd345..0000000000 --- a/ui/src/shared/components/SingleStat.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, {PureComponent} from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import getLastValues from 'shared/parsing/lastValues' -import _ from 'lodash' - -import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers' -import {DYGRAPH_CONTAINER_V_MARGIN} from 'shared/constants' -import {generateThresholdsListHexs} from 'shared/constants/colorOperations' -import {colorsStringSchema} from 'shared/schemas' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class SingleStat extends PureComponent { - render() { - const { - data, - cellHeight, - isFetchingInitially, - colors, - prefix, - suffix, - lineGraph, - staticLegendHeight, - } = this.props - - // If data for this graph is being fetched for the first time, show a graph-wide spinner. - if (isFetchingInitially) { - return ( -
-

-

- ) - } - const {lastValues, series} = getLastValues(data) - const firstAlphabeticalSeriesName = _.sortBy(series)[0] - - const firstAlphabeticalindex = _.indexOf( - series, - firstAlphabeticalSeriesName - ) - const lastValue = lastValues[firstAlphabeticalindex] - const precision = 100.0 - const roundedValue = Math.round(+lastValue * precision) / precision - - const {bgColor, textColor} = generateThresholdsListHexs({ - colors, - lastValue, - cellType: lineGraph ? 'line-plus-single-stat' : 'single-stat', - }) - const backgroundColor = bgColor - const color = textColor - - const height = `calc(100% - ${staticLegendHeight + - DYGRAPH_CONTAINER_V_MARGIN * 2}px)` - - const singleStatStyles = staticLegendHeight - ? { - backgroundColor, - color, - height, - } - : { - backgroundColor, - color, - } - - return ( -
- - {prefix} - {roundedValue} - {suffix} - {lineGraph &&
} - -
- ) - } -} - -const {arrayOf, bool, number, shape, string} = PropTypes - -SingleStat.propTypes = { - data: arrayOf(shape()).isRequired, - isFetchingInitially: bool, - cellHeight: number, - colors: colorsStringSchema, - prefix: string, - suffix: string, - lineGraph: bool, - staticLegendHeight: number, -} - -export default SingleStat diff --git a/ui/src/shared/components/SingleStat.tsx b/ui/src/shared/components/SingleStat.tsx new file mode 100644 index 0000000000..0b7ef2cbe0 --- /dev/null +++ b/ui/src/shared/components/SingleStat.tsx @@ -0,0 +1,125 @@ +import React, {PureComponent} from 'react' +import classnames from 'classnames' +import getLastValues from 'src/shared/parsing/lastValues' +import _ from 'lodash' + +import {SMALL_CELL_HEIGHT} from 'src/shared/graphs/helpers' +import {DYGRAPH_CONTAINER_V_MARGIN} from 'src/shared/constants' +import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations' +import {ColorNumber} from 'src/types/colors' +import {Data} from 'src/types/dygraphs' +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props { + isFetchingInitially: boolean + cellHeight: number + colors: ColorNumber[] + prefix?: string + suffix?: string + lineGraph: boolean + staticLegendHeight: number + data: Data +} + +@ErrorHandling +class SingleStat extends PureComponent { + public static defaultProps: Partial = { + prefix: '', + suffix: '', + } + + public render() { + const {isFetchingInitially} = this.props + + if (isFetchingInitially) { + return ( +
+

+

+ ) + } + + return ( +
+ + {this.completeValue} + {this.renderShadow} + +
+ ) + } + + private get renderShadow(): JSX.Element { + const {lineGraph} = this.props + + return lineGraph &&
+ } + + private get completeValue(): string { + const {prefix, suffix} = this.props + + return `${prefix}${this.roundedLastValue}${suffix}` + } + + private get roundedLastValue(): string { + const {data} = this.props + const {lastValues, series} = getLastValues(data) + const firstAlphabeticalSeriesName = _.sortBy(series)[0] + + const firstAlphabeticalindex = _.indexOf( + series, + firstAlphabeticalSeriesName + ) + const lastValue = lastValues[firstAlphabeticalindex] + const HUNDRED = 100.0 + const roundedValue = Math.round(+lastValue * HUNDRED) / HUNDRED + + return `${roundedValue}` + } + + private get styles() { + const {data, colors, lineGraph, staticLegendHeight} = this.props + + const {lastValues, series} = getLastValues(data) + const firstAlphabeticalSeriesName = _.sortBy(series)[0] + + const firstAlphabeticalindex = _.indexOf( + series, + firstAlphabeticalSeriesName + ) + const lastValue = lastValues[firstAlphabeticalindex] + + const {bgColor, textColor} = generateThresholdsListHexs({ + colors, + lastValue, + cellType: lineGraph ? 'line-plus-single-stat' : 'single-stat', + }) + + const backgroundColor = bgColor + const color = textColor + + const height = `calc(100% - ${staticLegendHeight + + DYGRAPH_CONTAINER_V_MARGIN * 2}px)` + + return staticLegendHeight + ? { + backgroundColor, + color, + height, + } + : { + backgroundColor, + color, + } + } + + private get className(): string { + const {cellHeight} = this.props + + return classnames('single-stat--value', { + 'single-stat--small': cellHeight === SMALL_CELL_HEIGHT, + }) + } +} + +export default SingleStat diff --git a/ui/src/shared/parsing/lastValues.ts b/ui/src/shared/parsing/lastValues.ts index 0de8de3ede..a3b9eea94b 100644 --- a/ui/src/shared/parsing/lastValues.ts +++ b/ui/src/shared/parsing/lastValues.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import {Data} from 'src/types/dygraphs' interface Result { lastValues: number[] @@ -24,7 +25,7 @@ export interface TimeSeriesResponse { } export default function( - timeSeriesResponse: TimeSeriesResponse[] | null + timeSeriesResponse: TimeSeriesResponse[] | Data | null ): Result { const values = _.get( timeSeriesResponse,