Merge pull request #3825 from influxdata/feature/locale-number-separators
Feature/locale number separatorspull/10616/head
commit
54bf8ba0ee
|
@ -1,12 +1,10 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent, MouseEvent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import OptIn from 'shared/components/OptIn'
|
||||
import OptIn from 'src/shared/components/OptIn'
|
||||
import Input from 'src/dashboards/components/DisplayOptionsInput'
|
||||
import {Tabber, Tab} from 'src/dashboards/components/Tabber'
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import LineGraphColorSelector from 'src/shared/components/LineGraphColorSelector'
|
||||
|
||||
import {
|
||||
|
@ -17,80 +15,51 @@ import {GRAPH_TYPES} from 'src/dashboards/graphics/graph'
|
|||
|
||||
import {updateAxes} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {Axes} from 'src/types'
|
||||
|
||||
const {LINEAR, LOG, BASE_2, BASE_10} = AXES_SCALE_OPTIONS
|
||||
const getInputMin = scale => (scale === LOG ? '0' : null)
|
||||
|
||||
interface Props {
|
||||
type: string
|
||||
axes: Axes
|
||||
staticLegend: boolean
|
||||
defaultYLabel: string
|
||||
handleUpdateAxes: (axes: Axes) => void
|
||||
onToggleStaticLegend: (x: boolean) => (e: MouseEvent<HTMLLIElement>) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class AxesOptions extends Component {
|
||||
handleSetPrefixSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const {prefix, suffix} = e.target.form
|
||||
|
||||
const newAxes = {
|
||||
...axes,
|
||||
class AxesOptions extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
axes: {
|
||||
y: {
|
||||
...axes.y,
|
||||
prefix: prefix.value,
|
||||
suffix: suffix.value,
|
||||
bounds: ['', ''],
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
base: BASE_10,
|
||||
scale: LINEAR,
|
||||
label: '',
|
||||
},
|
||||
}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleSetYAxisBoundMin = min => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const {
|
||||
y: {
|
||||
bounds: [, max],
|
||||
x: {
|
||||
bounds: ['', ''],
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
base: BASE_10,
|
||||
scale: LINEAR,
|
||||
label: '',
|
||||
},
|
||||
} = this.props.axes
|
||||
const newAxes = {...axes, y: {...axes.y, bounds: [min, max]}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
},
|
||||
}
|
||||
|
||||
handleSetYAxisBoundMax = max => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const {
|
||||
y: {
|
||||
bounds: [min],
|
||||
},
|
||||
} = axes
|
||||
const newAxes = {...axes, y: {...axes.y, bounds: [min, max]}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleSetLabel = label => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, label}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleSetScale = scale => () => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, scale}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleSetBase = base => () => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, base}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const {
|
||||
axes: {
|
||||
y: {bounds, label, prefix, suffix, base, scale, defaultYLabel},
|
||||
y: {bounds, label, prefix, suffix, base, scale},
|
||||
},
|
||||
type,
|
||||
staticLegend,
|
||||
defaultYLabel,
|
||||
onToggleStaticLegend,
|
||||
} = this.props
|
||||
|
||||
|
@ -109,10 +78,10 @@ class AxesOptions extends Component {
|
|||
<div className="form-group col-sm-12">
|
||||
<label htmlFor="prefix">Title</label>
|
||||
<OptIn
|
||||
customPlaceholder={defaultYLabel || 'y-axis title'}
|
||||
type="text"
|
||||
customValue={label}
|
||||
onSetValue={this.handleSetLabel}
|
||||
type="text"
|
||||
customPlaceholder={defaultYLabel || 'y-axis title'}
|
||||
/>
|
||||
</div>
|
||||
<LineGraphColorSelector />
|
||||
|
@ -129,7 +98,7 @@ class AxesOptions extends Component {
|
|||
<div className="form-group col-sm-6">
|
||||
<label htmlFor="max">Max</label>
|
||||
<OptIn
|
||||
customPlaceholder={'max'}
|
||||
customPlaceholder="max"
|
||||
customValue={max}
|
||||
onSetValue={this.handleSetYAxisBoundMax}
|
||||
type="number"
|
||||
|
@ -142,7 +111,6 @@ class AxesOptions extends Component {
|
|||
value={prefix}
|
||||
labelText="Y-Value's Prefix"
|
||||
onChange={this.handleSetPrefixSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
<Input
|
||||
name="suffix"
|
||||
|
@ -150,11 +118,10 @@ class AxesOptions extends Component {
|
|||
value={suffix}
|
||||
labelText="Y-Value's Suffix"
|
||||
onChange={this.handleSetPrefixSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
<Tabber
|
||||
labelText="Y-Value's Format"
|
||||
tipID="Y-Values's Format"
|
||||
tipID="Y-Value's Format"
|
||||
tipContent={TOOLTIP_Y_VALUE_FORMAT}
|
||||
>
|
||||
<Tab
|
||||
|
@ -202,38 +169,74 @@ class AxesOptions extends Component {
|
|||
</FancyScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
private handleSetPrefixSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const {prefix, suffix} = e.target.form
|
||||
|
||||
const newAxes = {
|
||||
...axes,
|
||||
y: {
|
||||
...axes.y,
|
||||
prefix: prefix.value,
|
||||
suffix: suffix.value,
|
||||
},
|
||||
}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
private handleSetYAxisBoundMin = (min: string): void => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const {
|
||||
y: {
|
||||
bounds: [, max],
|
||||
},
|
||||
} = this.props.axes
|
||||
|
||||
const bounds: [string, string] = [min, max]
|
||||
const newAxes = {...axes, y: {...axes.y, bounds}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
private handleSetYAxisBoundMax = (max: string): void => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const {
|
||||
y: {
|
||||
bounds: [min],
|
||||
},
|
||||
} = axes
|
||||
|
||||
const bounds: [string, string] = [min, max]
|
||||
const newAxes = {...axes, y: {...axes.y, bounds}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
private handleSetLabel = label => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, label}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
private handleSetScale = scale => () => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, scale}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
private handleSetBase = base => () => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, base}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
AxesOptions.defaultProps = {
|
||||
axes: {
|
||||
y: {
|
||||
bounds: ['', ''],
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
base: BASE_10,
|
||||
scale: LINEAR,
|
||||
defaultYLabel: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
AxesOptions.propTypes = {
|
||||
type: string.isRequired,
|
||||
axes: shape({
|
||||
y: shape({
|
||||
bounds: arrayOf(string),
|
||||
label: string,
|
||||
defaultYLabel: string,
|
||||
}),
|
||||
}).isRequired,
|
||||
onToggleStaticLegend: func.isRequired,
|
||||
staticLegend: bool,
|
||||
handleUpdateAxes: func.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
const mstp = ({
|
||||
cellEditorOverlay: {
|
||||
cell: {axes, type},
|
||||
},
|
||||
|
@ -242,8 +245,8 @@ const mapStateToProps = ({
|
|||
type,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleUpdateAxes: bindActionCreators(updateAxes, dispatch),
|
||||
})
|
||||
const mdtp = {
|
||||
handleUpdateAxes: updateAxes,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AxesOptions)
|
||||
export default connect(mstp, mdtp)(AxesOptions)
|
|
@ -1,110 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector'
|
||||
import GaugeOptions from 'src/dashboards/components/GaugeOptions'
|
||||
import SingleStatOptions from 'src/dashboards/components/SingleStatOptions'
|
||||
import AxesOptions from 'src/dashboards/components/AxesOptions'
|
||||
import TableOptions from 'src/dashboards/components/TableOptions'
|
||||
|
||||
import {buildDefaultYLabel} from 'shared/presenters'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
@ErrorHandling
|
||||
class DisplayOptions extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const {axes, queryConfigs} = props
|
||||
|
||||
this.state = {
|
||||
axes: this.setDefaultLabels(axes, queryConfigs),
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {axes, queryConfigs} = nextProps
|
||||
|
||||
this.setState({axes: this.setDefaultLabels(axes, queryConfigs)})
|
||||
}
|
||||
|
||||
setDefaultLabels(axes, queryConfigs) {
|
||||
return queryConfigs.length
|
||||
? {
|
||||
...axes,
|
||||
y: {...axes.y, defaultYLabel: buildDefaultYLabel(queryConfigs[0])},
|
||||
}
|
||||
: axes
|
||||
}
|
||||
|
||||
renderOptions = () => {
|
||||
const {
|
||||
cell: {type},
|
||||
staticLegend,
|
||||
onToggleStaticLegend,
|
||||
onResetFocus,
|
||||
queryConfigs,
|
||||
} = this.props
|
||||
switch (type) {
|
||||
case 'gauge':
|
||||
return <GaugeOptions onResetFocus={onResetFocus} />
|
||||
case 'single-stat':
|
||||
return <SingleStatOptions onResetFocus={onResetFocus} />
|
||||
case 'table':
|
||||
return (
|
||||
<TableOptions
|
||||
onResetFocus={onResetFocus}
|
||||
queryConfigs={queryConfigs}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<AxesOptions
|
||||
onToggleStaticLegend={onToggleStaticLegend}
|
||||
staticLegend={staticLegend}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="display-options">
|
||||
<GraphTypeSelector />
|
||||
{this.renderOptions()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
DisplayOptions.propTypes = {
|
||||
cell: shape({
|
||||
type: string.isRequired,
|
||||
}).isRequired,
|
||||
axes: shape({
|
||||
y: shape({
|
||||
bounds: arrayOf(string),
|
||||
label: string,
|
||||
defaultYLabel: string,
|
||||
}),
|
||||
}).isRequired,
|
||||
queryConfigs: arrayOf(shape()).isRequired,
|
||||
onToggleStaticLegend: func.isRequired,
|
||||
staticLegend: bool,
|
||||
onResetFocus: func.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
cellEditorOverlay: {
|
||||
cell,
|
||||
cell: {axes},
|
||||
},
|
||||
}) => ({
|
||||
cell,
|
||||
axes,
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, null)(DisplayOptions)
|
|
@ -0,0 +1,104 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector'
|
||||
import GaugeOptions from 'src/dashboards/components/GaugeOptions'
|
||||
import SingleStatOptions from 'src/dashboards/components/SingleStatOptions'
|
||||
import AxesOptions from 'src/dashboards/components/AxesOptions'
|
||||
import TableOptions from 'src/dashboards/components/TableOptions'
|
||||
|
||||
import {buildDefaultYLabel} from 'src/shared/presenters'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {Axes, Cell, QueryConfig} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
cell: Cell
|
||||
Axes: Axes
|
||||
queryConfigs: QueryConfig[]
|
||||
staticLegend: boolean
|
||||
onResetFocus: () => void
|
||||
onToggleStaticLegend: (x: boolean) => () => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
defaultYLabel: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class DisplayOptions extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
defaultYLabel: this.defaultYLabel,
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps) {
|
||||
const {queryConfigs} = prevProps
|
||||
|
||||
if (!_.isEqual(queryConfigs[0], this.props.queryConfigs[0])) {
|
||||
this.setState({defaultYLabel: this.defaultYLabel})
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="display-options">
|
||||
<GraphTypeSelector />
|
||||
{this.renderOptions}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get renderOptions(): JSX.Element {
|
||||
const {
|
||||
cell,
|
||||
staticLegend,
|
||||
onToggleStaticLegend,
|
||||
onResetFocus,
|
||||
queryConfigs,
|
||||
} = this.props
|
||||
|
||||
const {defaultYLabel} = this.state
|
||||
|
||||
switch (cell.type) {
|
||||
case 'gauge':
|
||||
return <GaugeOptions onResetFocus={onResetFocus} />
|
||||
case 'single-stat':
|
||||
return <SingleStatOptions onResetFocus={onResetFocus} />
|
||||
case 'table':
|
||||
return (
|
||||
<TableOptions
|
||||
onResetFocus={onResetFocus}
|
||||
queryConfigs={queryConfigs}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<AxesOptions
|
||||
staticLegend={staticLegend}
|
||||
defaultYLabel={defaultYLabel}
|
||||
onToggleStaticLegend={onToggleStaticLegend}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private get defaultYLabel(): string {
|
||||
const {queryConfigs} = this.props
|
||||
if (queryConfigs.length) {
|
||||
return buildDefaultYLabel(queryConfigs[0])
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const mstp = ({cellEditorOverlay}) => ({
|
||||
cell: cellEditorOverlay.cell,
|
||||
axes: cellEditorOverlay.cell.axes,
|
||||
})
|
||||
|
||||
export default connect(mstp, null)(DisplayOptions)
|
|
@ -1,7 +1,16 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {SFC, ChangeEvent} from 'react'
|
||||
|
||||
const DisplayOptionsInput = ({
|
||||
interface Props {
|
||||
name: string
|
||||
id: string
|
||||
value: string
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
labelText?: string
|
||||
colWidth?: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
const DisplayOptionsInput: SFC<Props> = ({
|
||||
id,
|
||||
name,
|
||||
value,
|
||||
|
@ -24,22 +33,10 @@ const DisplayOptionsInput = ({
|
|||
</div>
|
||||
)
|
||||
|
||||
const {func, string} = PropTypes
|
||||
|
||||
DisplayOptionsInput.defaultProps = {
|
||||
value: '',
|
||||
colWidth: 'col-sm-6',
|
||||
placeholder: '',
|
||||
}
|
||||
|
||||
DisplayOptionsInput.propTypes = {
|
||||
name: string.isRequired,
|
||||
id: string.isRequired,
|
||||
value: string.isRequired,
|
||||
onChange: func.isRequired,
|
||||
labelText: string,
|
||||
colWidth: string,
|
||||
placeholder: string,
|
||||
}
|
||||
|
||||
export default DisplayOptionsInput
|
|
@ -1,32 +1,23 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import ThresholdsList from 'shared/components/ThresholdsList'
|
||||
import ThresholdsListTypeToggle from 'shared/components/ThresholdsListTypeToggle'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import ThresholdsList from 'src/shared/components/ThresholdsList'
|
||||
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
|
||||
|
||||
import {updateAxes} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {Axes} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
handleUpdateAxes: (axes: Axes) => void
|
||||
axes: Axes
|
||||
onResetFocus: () => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class SingleStatOptions extends Component {
|
||||
handleUpdatePrefix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, prefix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleUpdateSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, suffix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
render() {
|
||||
class SingleStatOptions extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {
|
||||
axes: {
|
||||
y: {prefix, suffix},
|
||||
|
@ -50,7 +41,7 @@ class SingleStatOptions extends Component {
|
|||
placeholder="%, MPH, etc."
|
||||
defaultValue={prefix}
|
||||
onChange={this.handleUpdatePrefix}
|
||||
maxLength="5"
|
||||
maxLength={5}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
|
@ -60,7 +51,7 @@ class SingleStatOptions extends Component {
|
|||
placeholder="%, MPH, etc."
|
||||
defaultValue={suffix}
|
||||
onChange={this.handleUpdateSuffix}
|
||||
maxLength="5"
|
||||
maxLength={5}
|
||||
/>
|
||||
</div>
|
||||
<ThresholdsListTypeToggle containerClass="form-group col-xs-6" />
|
||||
|
@ -69,26 +60,28 @@ class SingleStatOptions extends Component {
|
|||
</FancyScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
private handleUpdatePrefix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, prefix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
private handleUpdateSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, suffix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape} = PropTypes
|
||||
const mstp = ({cellEditorOverlay}) => ({
|
||||
axes: cellEditorOverlay.cell.axes,
|
||||
})
|
||||
|
||||
SingleStatOptions.propTypes = {
|
||||
handleUpdateAxes: func.isRequired,
|
||||
axes: shape({}).isRequired,
|
||||
onResetFocus: func.isRequired,
|
||||
const mdtp = {
|
||||
handleUpdateAxes: updateAxes,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
cellEditorOverlay: {
|
||||
cell: {axes},
|
||||
},
|
||||
}) => ({
|
||||
axes,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleUpdateAxes: bindActionCreators(updateAxes, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SingleStatOptions)
|
||||
export default connect(mstp, mdtp)(SingleStatOptions)
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
|
||||
|
||||
export const Tabber = ({labelText, children, tipID, tipContent}) => (
|
||||
<div className="form-group col-md-6">
|
||||
<label>
|
||||
{labelText}
|
||||
{tipID ? (
|
||||
<QuestionMarkTooltip tipID={tipID} tipContent={tipContent} />
|
||||
) : null}
|
||||
</label>
|
||||
<ul className="nav nav-tablist nav-tablist-sm">{children}</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const Tab = ({isActive, onClickTab, text}) => (
|
||||
<li className={isActive ? 'active' : ''} onClick={onClickTab}>
|
||||
{text}
|
||||
</li>
|
||||
)
|
||||
|
||||
const {bool, func, node, string} = PropTypes
|
||||
|
||||
Tabber.propTypes = {
|
||||
children: node.isRequired,
|
||||
labelText: string,
|
||||
tipID: string,
|
||||
tipContent: string,
|
||||
}
|
||||
|
||||
Tab.propTypes = {
|
||||
onClickTab: func.isRequired,
|
||||
isActive: bool.isRequired,
|
||||
text: string.isRequired,
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React, {SFC, MouseEvent} from 'react'
|
||||
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
|
||||
|
||||
interface TabberProps {
|
||||
labelText?: string
|
||||
children: JSX.Element[]
|
||||
tipID?: string
|
||||
tipContent?: string
|
||||
}
|
||||
|
||||
export const Tabber: SFC<TabberProps> = ({
|
||||
children,
|
||||
tipID = '',
|
||||
labelText = '',
|
||||
tipContent = '',
|
||||
}) => (
|
||||
<div className="form-group col-md-6">
|
||||
<label>
|
||||
{labelText}
|
||||
{!!tipID && <QuestionMarkTooltip tipID={tipID} tipContent={tipContent} />}
|
||||
</label>
|
||||
<ul className="nav nav-tablist nav-tablist-sm">{children}</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
interface TabProps {
|
||||
onClickTab: (e: MouseEvent<HTMLLIElement>) => void
|
||||
isActive: boolean
|
||||
text: string
|
||||
}
|
||||
|
||||
export const Tab: SFC<TabProps> = ({isActive, onClickTab, text}) => (
|
||||
<li className={isActive ? 'active' : ''} onClick={onClickTab}>
|
||||
{text}
|
||||
</li>
|
||||
)
|
|
@ -4,7 +4,7 @@ import onClickOutside from 'src/shared/components/OnClickOutside'
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
min: string
|
||||
min?: string
|
||||
id: string
|
||||
type: string
|
||||
customPlaceholder: string
|
||||
|
@ -25,7 +25,7 @@ class ClickOutsideInput extends Component<Props> {
|
|||
public render() {
|
||||
const {
|
||||
id,
|
||||
min,
|
||||
min = '',
|
||||
type,
|
||||
onFocus,
|
||||
onChange,
|
||||
|
|
|
@ -290,7 +290,7 @@ class Gauge extends Component {
|
|||
ctx.textAlign = 'center'
|
||||
|
||||
const textY = radius
|
||||
const textContent = `${prefix}${gaugePosition.toString()}${suffix}`
|
||||
const textContent = `${prefix}${gaugePosition.toLocaleString()}${suffix}`
|
||||
ctx.fillText(textContent, 0, textY)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,44 +1,82 @@
|
|||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import Dygraph from 'src/shared/components/Dygraph'
|
||||
import _ from 'lodash'
|
||||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Dygraph from 'shared/components/Dygraph'
|
||||
|
||||
import SingleStat from 'src/shared/components/SingleStat'
|
||||
import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers'
|
||||
import {
|
||||
timeSeriesToDygraph,
|
||||
TimeSeriesToDyGraphReturnType,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
|
||||
import {colorsStringSchema} from 'shared/schemas'
|
||||
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||
import InvalidData from 'src/shared/components/InvalidData'
|
||||
import {Query, Axes, RuleValues, TimeRange} from 'src/types'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Data} from 'src/types/dygraphs'
|
||||
|
||||
const validateTimeSeries = timeseries => {
|
||||
return _.every(timeseries, r =>
|
||||
const validateTimeSeries = ts => {
|
||||
return _.every(ts, r =>
|
||||
_.every(
|
||||
r,
|
||||
(v, i) => (i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v)
|
||||
(v, i: number) =>
|
||||
(i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
title: string
|
||||
cellID: string
|
||||
cellHeight: number
|
||||
isFetchingInitially: boolean
|
||||
isRefreshing: boolean
|
||||
isGraphFilled: boolean
|
||||
isBarGraph: boolean
|
||||
staticLegend: boolean
|
||||
showSingleStat: boolean
|
||||
displayOptions: {
|
||||
stepPlot: boolean
|
||||
stackedGraph: boolean
|
||||
animatedZooms: boolean
|
||||
}
|
||||
activeQueryIndex: number
|
||||
ruleValues: RuleValues
|
||||
timeRange: TimeRange
|
||||
isInDataExplorer: boolean
|
||||
onZoom: () => void
|
||||
data: Data
|
||||
queries: Query[]
|
||||
colors: ColorString[]
|
||||
underlayCallback?: () => void
|
||||
setResolution: () => void
|
||||
handleSetHoverTime: () => void
|
||||
}
|
||||
|
||||
@ErrorHandlingWith(InvalidData)
|
||||
class LineGraph extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.isValidData = true
|
||||
class LineGraph extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
underlayCallback: () => {},
|
||||
isGraphFilled: true,
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
private isValidData: boolean = true
|
||||
private timeSeries: TimeSeriesToDyGraphReturnType
|
||||
|
||||
public componentWillMount() {
|
||||
const {data, isInDataExplorer} = this.props
|
||||
this.parseTimeSeries(data, isInDataExplorer)
|
||||
}
|
||||
|
||||
parseTimeSeries(data, isInDataExplorer) {
|
||||
this._timeSeries = timeSeriesToDygraph(data, isInDataExplorer)
|
||||
public parseTimeSeries(data, isInDataExplorer) {
|
||||
this.timeSeries = timeSeriesToDygraph(data, isInDataExplorer)
|
||||
this.isValidData = validateTimeSeries(
|
||||
_.get(this._timeSeries, 'timeSeries', [])
|
||||
_.get(this.timeSeries, 'timeSeries', [])
|
||||
)
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
public componentWillUpdate(nextProps) {
|
||||
const {data, activeQueryIndex} = this.props
|
||||
if (
|
||||
data !== nextProps.data ||
|
||||
|
@ -48,7 +86,7 @@ class LineGraph extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (!this.isValidData) {
|
||||
return <InvalidData />
|
||||
}
|
||||
|
@ -76,7 +114,7 @@ class LineGraph extends Component {
|
|||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {labels, timeSeries, dygraphSeries} = this._timeSeries
|
||||
const {labels, timeSeries, dygraphSeries} = this.timeSeries
|
||||
|
||||
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
|
||||
if (isFetchingInitially) {
|
||||
|
@ -99,19 +137,9 @@ class LineGraph extends Component {
|
|||
connectSeparatedPoints: true,
|
||||
}
|
||||
|
||||
const containerStyle = {
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 16px)',
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
}
|
||||
|
||||
const prefix = axes ? axes.y.prefix : ''
|
||||
const suffix = axes ? axes.y.suffix : ''
|
||||
|
||||
return (
|
||||
<div className="dygraph graph--hasYLabel" style={{height: '100%'}}>
|
||||
{isRefreshing ? <GraphLoadingDots /> : null}
|
||||
<div className="dygraph graph--hasYLabel" style={this.style}>
|
||||
{isRefreshing && <GraphLoadingDots />}
|
||||
<Dygraph
|
||||
axes={axes}
|
||||
cellID={cellID}
|
||||
|
@ -124,27 +152,61 @@ class LineGraph extends Component {
|
|||
isBarGraph={isBarGraph}
|
||||
timeSeries={timeSeries}
|
||||
ruleValues={ruleValues}
|
||||
staticLegend={staticLegend}
|
||||
dygraphSeries={dygraphSeries}
|
||||
setResolution={setResolution}
|
||||
containerStyle={this.containerStyle}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
containerStyle={containerStyle}
|
||||
staticLegend={staticLegend}
|
||||
isGraphFilled={showSingleStat ? false : isGraphFilled}
|
||||
>
|
||||
{showSingleStat && (
|
||||
<SingleStat
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
data={data}
|
||||
lineGraph={true}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
suffix={this.suffix}
|
||||
cellHeight={cellHeight}
|
||||
isFetchingInitially={isFetchingInitially}
|
||||
/>
|
||||
)}
|
||||
</Dygraph>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get style(): CSSProperties {
|
||||
return {height: '100%'}
|
||||
}
|
||||
|
||||
private get prefix(): string {
|
||||
const {axes} = this.props
|
||||
|
||||
if (!axes) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return axes.y.prefix
|
||||
}
|
||||
|
||||
private get suffix(): string {
|
||||
const {axes} = this.props
|
||||
|
||||
if (!axes) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return axes.y.suffix
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
return {
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 16px)',
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GraphLoadingDots = () => (
|
||||
|
@ -161,52 +223,4 @@ const GraphSpinner = () => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const {array, arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
LineGraph.defaultProps = {
|
||||
underlayCallback: () => {},
|
||||
isGraphFilled: true,
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
LineGraph.propTypes = {
|
||||
cellID: string,
|
||||
axes: shape({
|
||||
y: shape({
|
||||
bounds: array,
|
||||
label: string,
|
||||
}),
|
||||
y2: shape({
|
||||
bounds: array,
|
||||
label: string,
|
||||
}),
|
||||
}),
|
||||
handleSetHoverTime: func,
|
||||
title: string,
|
||||
isFetchingInitially: bool,
|
||||
isRefreshing: bool,
|
||||
underlayCallback: func,
|
||||
isGraphFilled: bool,
|
||||
isBarGraph: bool,
|
||||
staticLegend: bool,
|
||||
showSingleStat: bool,
|
||||
displayOptions: shape({
|
||||
stepPlot: bool,
|
||||
stackedGraph: bool,
|
||||
animatedZooms: bool,
|
||||
}),
|
||||
activeQueryIndex: number,
|
||||
ruleValues: shape({}),
|
||||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
isInDataExplorer: bool,
|
||||
setResolution: func,
|
||||
cellHeight: number,
|
||||
onZoom: func,
|
||||
queries: arrayOf(shape({}).isRequired).isRequired,
|
||||
data: arrayOf(shape({}).isRequired).isRequired,
|
||||
colors: colorsStringSchema,
|
||||
}
|
||||
|
||||
export default LineGraph
|
|
@ -7,11 +7,11 @@ import ClickOutsideInput from 'src/shared/components/ClickOutsideInput'
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
min: string
|
||||
fixedPlaceholder: string
|
||||
fixedValue: string
|
||||
customPlaceholder: string
|
||||
customValue: string
|
||||
min?: string
|
||||
fixedPlaceholder?: string
|
||||
fixedValue?: string
|
||||
customPlaceholder?: string
|
||||
customValue?: string
|
||||
onSetValue: (value: string) => void
|
||||
type: string | number
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ interface State {
|
|||
@ErrorHandling
|
||||
export default class OptIn extends Component<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
min: '',
|
||||
fixedValue: '',
|
||||
customPlaceholder: 'Custom Value',
|
||||
fixedPlaceholder: 'auto',
|
||||
customValue: '',
|
||||
fixedPlaceholder: 'auto',
|
||||
customPlaceholder: 'Custom Value',
|
||||
}
|
||||
|
||||
private id: string
|
||||
|
|
|
@ -6,7 +6,7 @@ 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 {ColorString} from 'src/types/colors'
|
||||
import {CellType} from 'src/types/dashboards'
|
||||
import {Data} from 'src/types/dygraphs'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -14,11 +14,11 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
interface Props {
|
||||
isFetchingInitially: boolean
|
||||
cellHeight: number
|
||||
colors: ColorNumber[]
|
||||
colors: ColorString[]
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
lineGraph: boolean
|
||||
staticLegendHeight: number
|
||||
staticLegendHeight?: number
|
||||
data: Data
|
||||
}
|
||||
|
||||
|
@ -71,8 +71,9 @@ class SingleStat extends PureComponent<Props> {
|
|||
const lastValue = lastValues[firstAlphabeticalindex]
|
||||
const HUNDRED = 100.0
|
||||
const roundedValue = Math.round(+lastValue * HUNDRED) / HUNDRED
|
||||
const localeFormatted = roundedValue.toLocaleString()
|
||||
|
||||
return `${roundedValue}`
|
||||
return `${localeFormatted}`
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
|
@ -101,11 +102,11 @@ class SingleStat extends PureComponent<Props> {
|
|||
const {lastValues, series} = getLastValues(data)
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
|
||||
const firstAlphabeticalindex = _.indexOf(
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
const lastValue = lastValues[firstAlphabeticalindex]
|
||||
const lastValue = lastValues[firstAlphabeticalIndex]
|
||||
|
||||
const {bgColor, textColor} = generateThresholdsListHexs({
|
||||
colors,
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Label {
|
|||
responseIndex: number
|
||||
}
|
||||
|
||||
interface TimeSeriesToDyGraphReturnType {
|
||||
export interface TimeSeriesToDyGraphReturnType {
|
||||
labels: string[]
|
||||
timeSeries: DygraphValue[][]
|
||||
dygraphSeries: DygraphSeries
|
||||
|
|
Loading…
Reference in New Issue