merge master into branch

pull/10616/head
Alex P 2018-05-11 16:40:33 -07:00
commit 6d03496960
4 changed files with 242 additions and 99 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,