merge master into branch
commit
6d03496960
|
@ -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 (
|
||||||
|
<div className={dropdownClassNames}>
|
||||||
|
<div
|
||||||
|
className={toggleClassNames}
|
||||||
|
onClick={this.handleToggleMenu}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="color-dropdown--swatch"
|
||||||
|
style={{backgroundColor: selected.hex}}
|
||||||
|
/>
|
||||||
|
<div className="color-dropdown--name">{selected.name}</div>
|
||||||
|
<span className="caret" />
|
||||||
|
</div>
|
||||||
|
{visible ? (
|
||||||
|
<div className="color-dropdown--menu">
|
||||||
|
<FancyScrollbar
|
||||||
|
autoHide={false}
|
||||||
|
autoHeight={true}
|
||||||
|
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
|
||||||
|
>
|
||||||
|
{colors.map((color, i) => (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
color.name === selected.name
|
||||||
|
? 'color-dropdown--item active'
|
||||||
|
: 'color-dropdown--item'
|
||||||
|
}
|
||||||
|
key={i}
|
||||||
|
onClick={this.handleColorClick(color)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="color-dropdown--swatch"
|
||||||
|
style={{backgroundColor: color.hex}}
|
||||||
|
/>
|
||||||
|
<span className="color-dropdown--name">{color.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</FancyScrollbar>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
|
@ -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 (
|
|
||||||
<div className="graph-empty">
|
|
||||||
<h3 className="graph-spinner" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
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 (
|
|
||||||
<div className="single-stat" style={singleStatStyles}>
|
|
||||||
<span
|
|
||||||
className={classnames('single-stat--value', {
|
|
||||||
'single-stat--small': cellHeight === SMALL_CELL_HEIGHT,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{prefix}
|
|
||||||
{roundedValue}
|
|
||||||
{suffix}
|
|
||||||
{lineGraph && <div className="single-stat--shadow" />}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
|
@ -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<Props> {
|
||||||
|
public static defaultProps: Partial<Props> = {
|
||||||
|
prefix: '',
|
||||||
|
suffix: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {isFetchingInitially} = this.props
|
||||||
|
|
||||||
|
if (isFetchingInitially) {
|
||||||
|
return (
|
||||||
|
<div className="graph-empty">
|
||||||
|
<h3 className="graph-spinner" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="single-stat" style={this.styles}>
|
||||||
|
<span className={this.className}>
|
||||||
|
{this.completeValue}
|
||||||
|
{this.renderShadow}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get renderShadow(): JSX.Element {
|
||||||
|
const {lineGraph} = this.props
|
||||||
|
|
||||||
|
return lineGraph && <div className="single-stat--shadow" />
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
|
@ -1,4 +1,5 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import {Data} from 'src/types/dygraphs'
|
||||||
|
|
||||||
interface Result {
|
interface Result {
|
||||||
lastValues: number[]
|
lastValues: number[]
|
||||||
|
@ -24,7 +25,7 @@ export interface TimeSeriesResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(
|
export default function(
|
||||||
timeSeriesResponse: TimeSeriesResponse[] | null
|
timeSeriesResponse: TimeSeriesResponse[] | Data | null
|
||||||
): Result {
|
): Result {
|
||||||
const values = _.get(
|
const values = _.get(
|
||||||
timeSeriesResponse,
|
timeSeriesResponse,
|
||||||
|
|
Loading…
Reference in New Issue