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 (
+
+
+ {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,