Merge pull request #3904 from influxdata/refactor/auto-refresh
Refactor AutoRefresh as singletonpull/10616/head
commit
102b0f2a15
|
@ -17,6 +17,7 @@ import {
|
|||
import {ColorString, ColorNumber} from 'src/types/colors'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
type: CellType
|
||||
source: Source
|
||||
autoRefresh: number
|
||||
|
@ -24,7 +25,6 @@ interface Props {
|
|||
timeRange: TimeRange
|
||||
queryConfigs: QueryConfig[]
|
||||
editQueryStatus: () => void
|
||||
axes: Axes
|
||||
tableOptions: TableOptions
|
||||
timeFormat: string
|
||||
decimalPlaces: DecimalPlaces
|
||||
|
|
|
@ -63,6 +63,7 @@ export const NEW_DEFAULT_DASHBOARD_CELL: NewDefaultCell = {
|
|||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
decimalPlaces: DEFAULT_DECIMAL_PLACES,
|
||||
fieldOptions: [DEFAULT_TIME_FIELD],
|
||||
inView: true,
|
||||
}
|
||||
|
||||
interface EmptyDefaultDashboardCell {
|
||||
|
|
|
@ -25,6 +25,7 @@ import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
|
|||
import {millisecondTimeRange} from 'src/dashboards/utils/time'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {updateDashboardLinks} from 'src/dashboards/utils/dashboardSwitcherLinks'
|
||||
import AutoRefresh from 'src/utils/AutoRefresh'
|
||||
|
||||
// APIs
|
||||
import {loadDashboardLinks} from 'src/dashboards/apis'
|
||||
|
@ -110,39 +111,33 @@ interface Props extends ManualRefreshProps, WithRouterProps {
|
|||
}
|
||||
|
||||
interface State {
|
||||
isEditMode: boolean
|
||||
selectedCell: DashboardsModels.Cell | null
|
||||
scrollTop: number
|
||||
isEditMode: boolean
|
||||
windowHeight: number
|
||||
selectedCell: DashboardsModels.Cell | null
|
||||
dashboardLinks: DashboardsModels.DashboardSwitcherLinks
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class DashboardPage extends Component<Props, State> {
|
||||
private intervalID: number
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
scrollTop: 0,
|
||||
isEditMode: false,
|
||||
selectedCell: null,
|
||||
scrollTop: 0,
|
||||
windowHeight: window.innerHeight,
|
||||
dashboardLinks: EMPTY_LINKS,
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {source, getAnnotationsAsync, timeRange, autoRefresh} = this.props
|
||||
|
||||
const annotationRange = millisecondTimeRange(timeRange)
|
||||
getAnnotationsAsync(source.links.annotations, annotationRange)
|
||||
const {autoRefresh} = this.props
|
||||
|
||||
if (autoRefresh) {
|
||||
this.intervalID = window.setInterval(() => {
|
||||
getAnnotationsAsync(source.links.annotations, annotationRange)
|
||||
}, autoRefresh)
|
||||
AutoRefresh.poll(autoRefresh)
|
||||
AutoRefresh.subscribe(this.fetchAnnotations)
|
||||
}
|
||||
|
||||
window.addEventListener('resize', this.handleWindowResize, true)
|
||||
|
@ -152,45 +147,37 @@ class DashboardPage extends Component<Props, State> {
|
|||
this.getDashboardLinks()
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
const {source, getAnnotationsAsync, timeRange} = this.props
|
||||
if (this.props.autoRefresh !== nextProps.autoRefresh) {
|
||||
clearInterval(this.intervalID)
|
||||
this.intervalID = null
|
||||
const annotationRange = millisecondTimeRange(timeRange)
|
||||
if (nextProps.autoRefresh) {
|
||||
this.intervalID = window.setInterval(() => {
|
||||
getAnnotationsAsync(source.links.annotations, annotationRange)
|
||||
}, nextProps.autoRefresh)
|
||||
}
|
||||
}
|
||||
public fetchAnnotations = () => {
|
||||
const {source, timeRange, getAnnotationsAsync} = this.props
|
||||
const rangeMs = millisecondTimeRange(timeRange)
|
||||
getAnnotationsAsync(source.links.annotations, rangeMs)
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
const {dashboard, autoRefresh} = this.props
|
||||
|
||||
const prevPath = getDeep(prevProps.location, 'pathname', null)
|
||||
const thisPath = getDeep(this.props.location, 'pathname', null)
|
||||
|
||||
const templates = getDeep<TempVarsModels.Template[]>(
|
||||
this.props.dashboard,
|
||||
'templates',
|
||||
[]
|
||||
).map(t => t.tempVar)
|
||||
const prevTemplates = getDeep<TempVarsModels.Template[]>(
|
||||
prevProps.dashboard,
|
||||
'templates',
|
||||
[]
|
||||
).map(t => t.tempVar)
|
||||
const isTemplateDeleted: boolean =
|
||||
_.intersection(templates, prevTemplates).length !== prevTemplates.length
|
||||
const templates = this.parseTempVar(dashboard)
|
||||
const prevTemplates = this.parseTempVar(prevProps.dashboard)
|
||||
|
||||
const intersection = _.intersection(templates, prevTemplates)
|
||||
const isTemplateDeleted = intersection.length !== prevTemplates.length
|
||||
|
||||
if ((prevPath && thisPath && prevPath !== thisPath) || isTemplateDeleted) {
|
||||
this.getDashboard()
|
||||
}
|
||||
|
||||
if (autoRefresh !== prevProps.autoRefresh) {
|
||||
AutoRefresh.poll(autoRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
clearInterval(this.intervalID)
|
||||
this.intervalID = null
|
||||
AutoRefresh.stopPolling()
|
||||
AutoRefresh.unsubscribe(this.fetchAnnotations)
|
||||
|
||||
window.removeEventListener('resize', this.handleWindowResize, true)
|
||||
this.props.handleDismissEditingAnnotation()
|
||||
}
|
||||
|
@ -217,7 +204,6 @@ class DashboardPage extends Component<Props, State> {
|
|||
cellQueryStatus,
|
||||
thresholdsListType,
|
||||
thresholdsListColors,
|
||||
|
||||
inPresentationMode,
|
||||
handleChooseAutoRefresh,
|
||||
handleShowCellEditorOverlay,
|
||||
|
@ -346,6 +332,12 @@ class DashboardPage extends Component<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
public parseTempVar(
|
||||
dashboard: DashboardsModels.Dashboard
|
||||
): TempVarsModels.Template[] {
|
||||
return getDeep(dashboard, 'templates', []).map(t => t.tempVar)
|
||||
}
|
||||
|
||||
private handleWindowResize = (): void => {
|
||||
this.setState({windowHeight: window.innerHeight})
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import Table from './Table'
|
|||
import RefreshingGraph from 'src/shared/components/RefreshingGraph'
|
||||
import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
|
||||
|
||||
import {Source, Query, Template} from 'src/types'
|
||||
import {Source, Query, Template, CellType} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
view: string
|
||||
|
@ -39,18 +39,18 @@ const DataExplorerVisView: SFC<Props> = ({
|
|||
return (
|
||||
<Table
|
||||
query={query}
|
||||
editQueryStatus={editQueryStatus}
|
||||
source={source}
|
||||
templates={templates}
|
||||
editQueryStatus={editQueryStatus}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<RefreshingGraph
|
||||
type="line-graph"
|
||||
source={source}
|
||||
queries={queries}
|
||||
type={CellType.Line}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
colors={DEFAULT_LINE_COLORS}
|
||||
|
|
|
@ -19,6 +19,7 @@ import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown'
|
|||
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
|
||||
import GraphTips from 'src/shared/components/GraphTips'
|
||||
import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader'
|
||||
import AutoRefresh from 'src/utils/AutoRefresh'
|
||||
|
||||
import {VIS_VIEWS, AUTO_GROUP_BY, TEMPLATES} from 'src/shared/constants'
|
||||
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
|
||||
|
@ -63,8 +64,11 @@ export class DataExplorer extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {source} = this.props
|
||||
const {source, autoRefresh} = this.props
|
||||
const {query} = qs.parse(location.search, {ignoreQueryPrefix: true})
|
||||
|
||||
AutoRefresh.poll(autoRefresh)
|
||||
|
||||
if (query && query.length) {
|
||||
const qc = this.props.queryConfigs[0]
|
||||
this.props.queryConfigActions.editRawTextAsync(
|
||||
|
@ -75,6 +79,13 @@ export class DataExplorer extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
const {autoRefresh} = this.props
|
||||
if (autoRefresh !== prevProps.autoRefresh) {
|
||||
AutoRefresh.poll(autoRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
const {router} = this.props
|
||||
const {queryConfigs, timeRange} = nextProps
|
||||
|
@ -89,6 +100,10 @@ export class DataExplorer extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
AutoRefresh.stopPolling()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
source,
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {AlertRule} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
rule: AlertRule
|
||||
updateDetails: (id: string, value: string) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class RuleDetailsText extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleUpdateDetails = e => {
|
||||
const {rule, updateDetails} = this.props
|
||||
updateDetails(rule.id, e.target.value)
|
||||
}
|
||||
|
||||
render() {
|
||||
class RuleDetailsText extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {rule} = this.props
|
||||
return (
|
||||
<div className="rule-builder--details">
|
||||
|
@ -27,13 +24,10 @@ class RuleDetailsText extends Component {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleUpdateDetails = e => {
|
||||
const {rule, updateDetails} = this.props
|
||||
updateDetails(rule.id, e.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
const {shape, func} = PropTypes
|
||||
|
||||
RuleDetailsText.propTypes = {
|
||||
rule: shape().isRequired,
|
||||
updateDetails: func.isRequired,
|
||||
}
|
||||
|
||||
export default RuleDetailsText
|
|
@ -1,71 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import buildInfluxQLQuery from 'utils/influxql'
|
||||
import AutoRefresh from 'shared/components/AutoRefresh'
|
||||
import LineGraph from 'shared/components/LineGraph'
|
||||
import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
|
||||
import underlayCallback from 'src/kapacitor/helpers/ruleGraphUnderlay'
|
||||
|
||||
const RefreshingLineGraph = AutoRefresh(LineGraph)
|
||||
|
||||
import {LINE_COLORS_RULE_GRAPH} from 'src/shared/constants/graphColorPalettes'
|
||||
|
||||
const {shape, string, func} = PropTypes
|
||||
const RuleGraph = ({
|
||||
query,
|
||||
source,
|
||||
timeRange: {lower},
|
||||
timeRange,
|
||||
rule,
|
||||
onChooseTimeRange,
|
||||
}) => {
|
||||
const autoRefreshMs = 30000
|
||||
const queryText = buildInfluxQLQuery({lower}, query)
|
||||
const queries = [{host: source.links.proxy, text: queryText}]
|
||||
|
||||
if (!queryText) {
|
||||
return (
|
||||
<div className="rule-builder--graph-empty">
|
||||
<p>
|
||||
Select a <strong>Time-Series</strong> to preview on a graph
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rule-builder--graph">
|
||||
<div className="rule-builder--graph-options">
|
||||
<p>Preview Data from</p>
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
selected={timeRange}
|
||||
preventCustomTimeRange={true}
|
||||
/>
|
||||
</div>
|
||||
<RefreshingLineGraph
|
||||
source={source}
|
||||
queries={queries}
|
||||
isGraphFilled={false}
|
||||
ruleValues={rule.values}
|
||||
autoRefresh={autoRefreshMs}
|
||||
colors={LINE_COLORS_RULE_GRAPH}
|
||||
underlayCallback={underlayCallback(rule)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
RuleGraph.propTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
query: shape({}).isRequired,
|
||||
rule: shape({}).isRequired,
|
||||
timeRange: shape({}).isRequired,
|
||||
onChooseTimeRange: func.isRequired,
|
||||
}
|
||||
|
||||
export default RuleGraph
|
|
@ -0,0 +1,127 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
|
||||
|
||||
// Components
|
||||
import Dygraph from 'src/shared/components/Dygraph'
|
||||
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
|
||||
|
||||
// Utils
|
||||
import buildInfluxQLQuery from 'src/utils/influxql'
|
||||
import buildQueries from 'src/utils/buildQueriesForGraphs'
|
||||
import underlayCallback from 'src/kapacitor/helpers/ruleGraphUnderlay'
|
||||
import {timeSeriesToDygraph} from 'src/utils/timeSeriesTransformers'
|
||||
|
||||
// Constants
|
||||
import {LINE_COLORS_RULE_GRAPH} from 'src/shared/constants/graphColorPalettes'
|
||||
|
||||
// Types
|
||||
import {Source, AlertRule, QueryConfig, Query, TimeRange} from 'src/types'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
source: Source
|
||||
query: QueryConfig
|
||||
rule: AlertRule
|
||||
timeRange: TimeRange
|
||||
onChooseTimeRange: (tR: TimeRange) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class RuleGraph extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {source, onChooseTimeRange, timeRange, rule} = this.props
|
||||
|
||||
if (!this.queryText) {
|
||||
return (
|
||||
<div className="rule-builder--graph-empty">
|
||||
<p>
|
||||
Select a <strong>Time-Series</strong> to preview on a graph
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rule-builder--graph">
|
||||
<div className="rule-builder--graph-options">
|
||||
<p>Preview Data from</p>
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
selected={timeRange}
|
||||
preventCustomTimeRange={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="dygraph graph--hasYLabel" style={this.style}>
|
||||
<TimeSeries source={source} queries={this.queries}>
|
||||
{data => {
|
||||
const {labels, timeSeries, dygraphSeries} = timeSeriesToDygraph(
|
||||
data.timeSeries,
|
||||
'rule-builder'
|
||||
)
|
||||
|
||||
return (
|
||||
<Dygraph
|
||||
labels={labels}
|
||||
staticLegend={false}
|
||||
isGraphFilled={false}
|
||||
ruleValues={rule.values}
|
||||
options={this.options}
|
||||
timeRange={timeRange}
|
||||
queries={this.queries}
|
||||
timeSeries={timeSeries}
|
||||
dygraphSeries={dygraphSeries}
|
||||
colors={LINE_COLORS_RULE_GRAPH}
|
||||
containerStyle={this.containerStyle}
|
||||
underlayCallback={underlayCallback(rule)}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</TimeSeries>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get options() {
|
||||
return {
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
labelsKMB: true,
|
||||
fillGraph: true,
|
||||
axisLabelWidth: 60,
|
||||
animatedZooms: true,
|
||||
drawAxesAtZero: true,
|
||||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
connectSeparatedPoints: true,
|
||||
}
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
return {
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 16px)',
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
}
|
||||
}
|
||||
|
||||
private get style(): CSSProperties {
|
||||
return {height: '100%'}
|
||||
}
|
||||
|
||||
private get queryText(): string {
|
||||
const {timeRange, query} = this.props
|
||||
const lower = timeRange.lower
|
||||
return buildInfluxQLQuery({lower}, query)
|
||||
}
|
||||
|
||||
private get queries(): Query[] {
|
||||
const {query, timeRange} = this.props
|
||||
return buildQueries([query], timeRange)
|
||||
}
|
||||
}
|
||||
|
||||
export default RuleGraph
|
|
@ -98,6 +98,17 @@ export const OUTSIDE_RANGE: string = 'outside range'
|
|||
export const EQUAL_TO_OR_GREATER_THAN: string = 'equal to or greater'
|
||||
export const EQUAL_TO_OR_LESS_THAN: string = 'equal to or less than'
|
||||
|
||||
export enum ThresholdOperators {
|
||||
EqualTo = 'equal to',
|
||||
LessThan = 'less than',
|
||||
GreaterThan = 'greater than',
|
||||
NotEqualTo = 'not equal to',
|
||||
InsideRange = 'inside range',
|
||||
OutsideRange = 'outside range',
|
||||
EqualToOrGreaterThan = 'equal to or greater',
|
||||
EqualToOrLessThan = 'equal to or less than',
|
||||
}
|
||||
|
||||
export const THRESHOLD_OPERATORS: string[] = [
|
||||
GREATER_THAN,
|
||||
EQUAL_TO_OR_GREATER_THAN,
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
import {
|
||||
EQUAL_TO,
|
||||
LESS_THAN,
|
||||
NOT_EQUAL_TO,
|
||||
GREATER_THAN,
|
||||
INSIDE_RANGE,
|
||||
OUTSIDE_RANGE,
|
||||
EQUAL_TO_OR_LESS_THAN,
|
||||
EQUAL_TO_OR_GREATER_THAN,
|
||||
} from 'src/kapacitor/constants'
|
||||
import {ThresholdOperators} from 'src/kapacitor/constants'
|
||||
|
||||
const HIGHLIGHT = 'rgba(78, 216, 160, 0.3)'
|
||||
const BACKGROUND = 'rgba(41, 41, 51, 1)'
|
||||
|
||||
const getFillColor = operator => {
|
||||
const getFillColor = (operator: ThresholdOperators) => {
|
||||
const backgroundColor = BACKGROUND
|
||||
const highlightColor = HIGHLIGHT
|
||||
|
||||
if (operator === OUTSIDE_RANGE) {
|
||||
if (operator === ThresholdOperators.OutsideRange) {
|
||||
return backgroundColor
|
||||
}
|
||||
|
||||
if (operator === NOT_EQUAL_TO) {
|
||||
if (operator === ThresholdOperators.NotEqualTo) {
|
||||
return backgroundColor
|
||||
}
|
||||
|
||||
return highlightColor
|
||||
}
|
||||
|
||||
const underlayCallback = rule => (canvas, area, dygraph) => {
|
||||
interface Area {
|
||||
x: number
|
||||
y: number
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
|
||||
const underlayCallback = rule => (
|
||||
canvas: CanvasRenderingContext2D,
|
||||
area: Area,
|
||||
dygraph: Dygraph
|
||||
) => {
|
||||
const {values} = rule
|
||||
const {operator, value} = values
|
||||
|
||||
|
@ -40,21 +42,21 @@ const underlayCallback = rule => (canvas, area, dygraph) => {
|
|||
let highlightEnd = 0
|
||||
|
||||
switch (operator) {
|
||||
case `${EQUAL_TO_OR_GREATER_THAN}`:
|
||||
case `${GREATER_THAN}`: {
|
||||
case ThresholdOperators.EqualToOrGreaterThan:
|
||||
case ThresholdOperators.GreaterThan: {
|
||||
highlightStart = value
|
||||
highlightEnd = dygraph.yAxisRange()[1]
|
||||
break
|
||||
}
|
||||
|
||||
case `${EQUAL_TO_OR_LESS_THAN}`:
|
||||
case `${LESS_THAN}`: {
|
||||
case ThresholdOperators.EqualToOrLessThan:
|
||||
case ThresholdOperators.LessThan: {
|
||||
highlightStart = dygraph.yAxisRange()[0]
|
||||
highlightEnd = value
|
||||
break
|
||||
}
|
||||
|
||||
case `${EQUAL_TO}`: {
|
||||
case ThresholdOperators.LessThan: {
|
||||
const width =
|
||||
theOnePercent * (dygraph.yAxisRange()[1] - dygraph.yAxisRange()[0])
|
||||
highlightStart = +value - width
|
||||
|
@ -62,7 +64,7 @@ const underlayCallback = rule => (canvas, area, dygraph) => {
|
|||
break
|
||||
}
|
||||
|
||||
case `${NOT_EQUAL_TO}`: {
|
||||
case ThresholdOperators.NotEqualTo: {
|
||||
const width =
|
||||
theOnePercent * (dygraph.yAxisRange()[1] - dygraph.yAxisRange()[0])
|
||||
highlightStart = +value - width
|
||||
|
@ -73,7 +75,7 @@ const underlayCallback = rule => (canvas, area, dygraph) => {
|
|||
break
|
||||
}
|
||||
|
||||
case `${OUTSIDE_RANGE}`: {
|
||||
case ThresholdOperators.OutsideRange: {
|
||||
highlightStart = Math.min(+value, +values.rangeValue)
|
||||
highlightEnd = Math.max(+value, +values.rangeValue)
|
||||
|
||||
|
@ -82,7 +84,7 @@ const underlayCallback = rule => (canvas, area, dygraph) => {
|
|||
break
|
||||
}
|
||||
|
||||
case `${INSIDE_RANGE}`: {
|
||||
case ThresholdOperators.InsideRange: {
|
||||
highlightStart = Math.min(+value, +values.rangeValue)
|
||||
highlightEnd = Math.max(+value, +values.rangeValue)
|
||||
break
|
|
@ -1,23 +1,28 @@
|
|||
// Libraries
|
||||
import React, {Component, CSSProperties, MouseEvent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
import NanoDate from 'nano-date'
|
||||
import ReactResizeDetector from 'react-resize-detector'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
|
||||
// Components
|
||||
import D from 'src/external/dygraph'
|
||||
import DygraphLegend from 'src/shared/components/DygraphLegend'
|
||||
import StaticLegend from 'src/shared/components/StaticLegend'
|
||||
import Annotations from 'src/shared/components/Annotations'
|
||||
import Crosshair from 'src/shared/components/Crosshair'
|
||||
|
||||
// Utils
|
||||
import getRange, {getStackedRange} from 'src/shared/parsing/getRangeForDygraph'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {numberValueFormatter} from 'src/utils/formatting'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
AXES_SCALE_OPTIONS,
|
||||
DEFAULT_AXIS,
|
||||
} from 'src/dashboards/constants/cellEditor'
|
||||
import {buildDefaultYLabel} from 'src/shared/presenters'
|
||||
import {numberValueFormatter} from 'src/utils/formatting'
|
||||
import {NULL_HOVER_TIME} from 'src/shared/constants/tableGraph'
|
||||
import {
|
||||
OPTIONS,
|
||||
|
@ -26,14 +31,16 @@ import {
|
|||
CHAR_PIXELS,
|
||||
barPlotter,
|
||||
} from 'src/shared/graphs/helpers'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {getLineColorsHexes} from 'src/shared/constants/graphColorPalettes'
|
||||
const {LOG, BASE_10, BASE_2} = AXES_SCALE_OPTIONS
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Types
|
||||
import {
|
||||
Axes,
|
||||
Query,
|
||||
CellType,
|
||||
RuleValues,
|
||||
TimeRange,
|
||||
DygraphData,
|
||||
|
@ -46,6 +53,7 @@ import {LineColor} from 'src/types/colors'
|
|||
const Dygraphs = D as Constructable<DygraphClass>
|
||||
|
||||
interface Props {
|
||||
type: CellType
|
||||
cellID: string
|
||||
queries: Query[]
|
||||
timeSeries: DygraphData
|
||||
|
@ -59,11 +67,11 @@ interface Props {
|
|||
ruleValues?: RuleValues
|
||||
axes?: Axes
|
||||
isGraphFilled?: boolean
|
||||
isBarGraph?: boolean
|
||||
staticLegend?: boolean
|
||||
setResolution?: (w: number) => void
|
||||
onZoom?: (timeRange: TimeRange) => void
|
||||
mode?: string
|
||||
underlayCallback?: () => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -95,6 +103,7 @@ class Dygraph extends Component<Props, State> {
|
|||
staticLegend: false,
|
||||
setResolution: () => {},
|
||||
handleSetHoverTime: () => {},
|
||||
underlayCallback: () => {},
|
||||
}
|
||||
|
||||
private graphRef: React.RefObject<HTMLDivElement>
|
||||
|
@ -113,11 +122,12 @@ class Dygraph extends Component<Props, State> {
|
|||
|
||||
public componentDidMount() {
|
||||
const {
|
||||
axes: {y, y2},
|
||||
isGraphFilled: fillGraph,
|
||||
isBarGraph,
|
||||
type,
|
||||
options,
|
||||
labels,
|
||||
axes: {y, y2},
|
||||
isGraphFilled: fillGraph,
|
||||
underlayCallback,
|
||||
} = this.props
|
||||
|
||||
const timeSeries = this.timeSeries
|
||||
|
@ -150,10 +160,11 @@ class Dygraph extends Component<Props, State> {
|
|||
zoomCallback: (lower: number, upper: number) =>
|
||||
this.handleZoom(lower, upper),
|
||||
drawCallback: () => this.handleDraw(),
|
||||
underlayCallback,
|
||||
highlightCircleSize: 3,
|
||||
}
|
||||
|
||||
if (isBarGraph) {
|
||||
if (type === CellType.Bar) {
|
||||
defaultOptions = {
|
||||
...defaultOptions,
|
||||
plotter: barPlotter,
|
||||
|
@ -183,7 +194,8 @@ class Dygraph extends Component<Props, State> {
|
|||
labels,
|
||||
axes: {y, y2},
|
||||
options,
|
||||
isBarGraph,
|
||||
type,
|
||||
underlayCallback,
|
||||
} = this.props
|
||||
|
||||
const dygraph = this.dygraph
|
||||
|
@ -229,7 +241,8 @@ class Dygraph extends Component<Props, State> {
|
|||
},
|
||||
colors: LINE_COLORS,
|
||||
series: this.colorDygraphSeries,
|
||||
plotter: isBarGraph ? barPlotter : null,
|
||||
plotter: type === CellType.Bar ? barPlotter : null,
|
||||
underlayCallback,
|
||||
}
|
||||
|
||||
dygraph.updateOptions(updateOptions)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import getLastValues, {TimeSeriesResponse} from 'src/shared/parsing/lastValues'
|
||||
import getLastValues from 'src/shared/parsing/lastValues'
|
||||
import Gauge from 'src/shared/components/Gauge'
|
||||
|
||||
import {DEFAULT_GAUGE_COLORS} from 'src/shared/constants/thresholds'
|
||||
|
@ -9,6 +9,7 @@ import {stringifyColorValues} from 'src/shared/constants/colorOperations'
|
|||
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'src/shared/constants'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
interface Color {
|
||||
type: string
|
||||
|
@ -19,9 +20,8 @@ interface Color {
|
|||
}
|
||||
|
||||
interface Props {
|
||||
data: TimeSeriesResponse[]
|
||||
data: TimeSeriesServerResponse[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
isFetchingInitially: boolean
|
||||
cellID: string
|
||||
cellHeight?: number
|
||||
colors?: Color[]
|
||||
|
@ -37,15 +37,7 @@ class GaugeChart extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {isFetchingInitially, colors, prefix, suffix} = this.props
|
||||
|
||||
if (isFetchingInitially) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<h3 className="graph-spinner" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const {colors, prefix, suffix} = this.props
|
||||
|
||||
return (
|
||||
<div className="single-stat">
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import WidgetCell from 'shared/components/WidgetCell'
|
||||
import LayoutCell from 'shared/components/LayoutCell'
|
||||
import RefreshingGraph from 'shared/components/RefreshingGraph'
|
||||
import {buildQueriesForLayouts} from 'utils/buildQueriesForLayouts'
|
||||
import {IS_STATIC_LEGEND, defaultIntervalValue} from 'src/shared/constants'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
import {colorsStringSchema} from 'shared/schemas'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
const getSource = (cell, source, sources, defaultSource) => {
|
||||
const s = _.get(cell, ['queries', '0', 'source'], null)
|
||||
if (!s) {
|
||||
return source
|
||||
}
|
||||
|
||||
return sources.find(src => src.links.self === s) || defaultSource
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class LayoutState extends Component {
|
||||
state = {
|
||||
cellData: [],
|
||||
resolution: defaultIntervalValue,
|
||||
}
|
||||
|
||||
grabDataForDownload = cellData => {
|
||||
this.setState({cellData})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Layout
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
grabDataForDownload={this.grabDataForDownload}
|
||||
onSetResolution={this.handleSetResolution}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
handleSetResolution = resolution => {
|
||||
this.setState({resolution})
|
||||
}
|
||||
}
|
||||
|
||||
const Layout = (
|
||||
{
|
||||
host,
|
||||
cell,
|
||||
cell: {
|
||||
h: cellHeight,
|
||||
axes,
|
||||
type,
|
||||
colors,
|
||||
legend,
|
||||
timeFormat,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
decimalPlaces,
|
||||
},
|
||||
source,
|
||||
sources,
|
||||
onZoom,
|
||||
cellData,
|
||||
resolution,
|
||||
templates,
|
||||
timeRange,
|
||||
isEditable,
|
||||
isDragging,
|
||||
onEditCell,
|
||||
onCloneCell,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
onSetResolution,
|
||||
onStopAddAnnotation,
|
||||
onSummonOverlayTechnologies,
|
||||
grabDataForDownload,
|
||||
},
|
||||
{source: defaultSource}
|
||||
) => (
|
||||
<LayoutCell
|
||||
cell={cell}
|
||||
cellData={cellData}
|
||||
templates={templates}
|
||||
isEditable={isEditable}
|
||||
resolution={resolution}
|
||||
onEditCell={onEditCell}
|
||||
onCloneCell={onCloneCell}
|
||||
onDeleteCell={onDeleteCell}
|
||||
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
|
||||
>
|
||||
{cell.isWidget ? (
|
||||
<WidgetCell cell={cell} timeRange={timeRange} source={source} />
|
||||
) : (
|
||||
<RefreshingGraph
|
||||
axes={axes}
|
||||
type={type}
|
||||
cellHeight={cellHeight}
|
||||
onZoom={onZoom}
|
||||
colors={colors}
|
||||
sources={sources}
|
||||
inView={cell.inView}
|
||||
timeRange={timeRange}
|
||||
templates={templates}
|
||||
isDragging={isDragging}
|
||||
timeFormat={timeFormat}
|
||||
autoRefresh={autoRefresh}
|
||||
tableOptions={tableOptions}
|
||||
fieldOptions={fieldOptions}
|
||||
decimalPlaces={decimalPlaces}
|
||||
manualRefresh={manualRefresh}
|
||||
onSetResolution={onSetResolution}
|
||||
staticLegend={IS_STATIC_LEGEND(legend)}
|
||||
onStopAddAnnotation={onStopAddAnnotation}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
queries={buildQueriesForLayouts(cell, timeRange, host)}
|
||||
source={getSource(cell, source, sources, defaultSource)}
|
||||
/>
|
||||
)}
|
||||
</LayoutCell>
|
||||
)
|
||||
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
const propTypes = {
|
||||
isDragging: bool,
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
cell: shape({
|
||||
// isWidget cells will not have queries
|
||||
isWidget: bool,
|
||||
queries: arrayOf(
|
||||
shape({
|
||||
label: string,
|
||||
text: string,
|
||||
query: string,
|
||||
}).isRequired
|
||||
),
|
||||
x: number.isRequired,
|
||||
y: number.isRequired,
|
||||
w: number.isRequired,
|
||||
h: number.isRequired,
|
||||
i: string.isRequired,
|
||||
name: string.isRequired,
|
||||
type: string.isRequired,
|
||||
colors: colorsStringSchema,
|
||||
tableOptions: shape({
|
||||
verticalTimeAxis: bool.isRequired,
|
||||
sortBy: shape({
|
||||
internalName: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
visible: bool.isRequired,
|
||||
}).isRequired,
|
||||
wrapping: string.isRequired,
|
||||
fixFirstColumn: bool.isRequired,
|
||||
}),
|
||||
timeFormat: string,
|
||||
decimalPlaces: shape({
|
||||
isEnforced: bool.isRequired,
|
||||
digits: number.isRequired,
|
||||
}),
|
||||
fieldOptions: arrayOf(
|
||||
shape({
|
||||
internalName: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
visible: bool.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
}).isRequired,
|
||||
templates: arrayOf(shape()),
|
||||
host: string,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onPositionChange: func,
|
||||
onEditCell: func,
|
||||
onDeleteCell: func,
|
||||
onCloneCell: func,
|
||||
onSummonOverlayTechnologies: func,
|
||||
isStatusPage: bool,
|
||||
isEditable: bool,
|
||||
onZoom: func,
|
||||
sources: arrayOf(shape()),
|
||||
}
|
||||
|
||||
LayoutState.propTypes = {...propTypes}
|
||||
Layout.propTypes = {
|
||||
...propTypes,
|
||||
grabDataForDownload: func,
|
||||
cellData: arrayOf(shape({})),
|
||||
}
|
||||
|
||||
export default LayoutState
|
|
@ -0,0 +1,112 @@
|
|||
// Libraries
|
||||
import React, {Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import WidgetCell from 'src/shared/components/WidgetCell'
|
||||
import LayoutCell from 'src/shared/components/LayoutCell'
|
||||
import RefreshingGraph from 'src/shared/components/RefreshingGraph'
|
||||
|
||||
// Utils
|
||||
import {buildQueriesForLayouts} from 'src/utils/buildQueriesForLayouts'
|
||||
|
||||
// Constants
|
||||
import {IS_STATIC_LEGEND} from 'src/shared/constants'
|
||||
|
||||
// Types
|
||||
import {TimeRange, Cell, Template, Source} from 'src/types'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
cell: Cell
|
||||
timeRange: TimeRange
|
||||
templates: Template[]
|
||||
source: Source
|
||||
sources: Source[]
|
||||
host: string
|
||||
autoRefresh: number
|
||||
isEditable: boolean
|
||||
manualRefresh: number
|
||||
onZoom: () => void
|
||||
onDeleteCell: () => void
|
||||
onCloneCell: () => void
|
||||
onSummonOverlayTechnologies: () => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class Layout extends Component<Props> {
|
||||
public state = {
|
||||
cellData: [],
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
cell,
|
||||
host,
|
||||
source,
|
||||
sources,
|
||||
onZoom,
|
||||
timeRange,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
templates,
|
||||
isEditable,
|
||||
onCloneCell,
|
||||
onDeleteCell,
|
||||
onSummonOverlayTechnologies,
|
||||
} = this.props
|
||||
const {cellData} = this.state
|
||||
|
||||
return (
|
||||
<LayoutCell
|
||||
cell={cell}
|
||||
cellData={cellData}
|
||||
templates={templates}
|
||||
isEditable={isEditable}
|
||||
onCloneCell={onCloneCell}
|
||||
onDeleteCell={onDeleteCell}
|
||||
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
|
||||
>
|
||||
{cell.isWidget ? (
|
||||
<WidgetCell cell={cell} timeRange={timeRange} source={source} />
|
||||
) : (
|
||||
<RefreshingGraph
|
||||
onZoom={onZoom}
|
||||
axes={cell.axes}
|
||||
type={cell.type}
|
||||
inView={cell.inView}
|
||||
colors={cell.colors}
|
||||
tableOptions={cell.tableOptions}
|
||||
fieldOptions={cell.fieldOptions}
|
||||
decimalPlaces={cell.decimalPlaces}
|
||||
timeRange={timeRange}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
staticLegend={IS_STATIC_LEGEND(cell.legend)}
|
||||
grabDataForDownload={this.grabDataForDownload}
|
||||
queries={buildQueriesForLayouts(cell, timeRange, host)}
|
||||
source={this.getSource(cell, source, sources, source)}
|
||||
/>
|
||||
)}
|
||||
</LayoutCell>
|
||||
)
|
||||
}
|
||||
|
||||
private grabDataForDownload = cellData => {
|
||||
this.setState({cellData})
|
||||
}
|
||||
|
||||
private getSource = (cell, source, sources, defaultSource) => {
|
||||
const s = _.get(cell, ['queries', '0', 'source'], null)
|
||||
|
||||
if (!s) {
|
||||
return source
|
||||
}
|
||||
|
||||
return sources.find(src => src.links.self === s) || defaultSource
|
||||
}
|
||||
}
|
||||
|
||||
export default Layout
|
|
@ -24,7 +24,6 @@ interface Props {
|
|||
isEditable: boolean
|
||||
cellData: TimeSeriesServerResponse[]
|
||||
templates: Template[]
|
||||
resolution: number
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {SFC} from 'react'
|
||||
import {isCellUntitled} from 'src/dashboards/utils/cellGetters'
|
||||
|
||||
const LayoutCellHeader = ({isEditable, cellName}) => {
|
||||
interface Props {
|
||||
isEditable: boolean
|
||||
cellName: string
|
||||
}
|
||||
|
||||
const LayoutCellHeader: SFC<Props> = ({isEditable, cellName}) => {
|
||||
const headingClass = `dash-graph--heading ${
|
||||
isEditable ? 'dash-graph--draggable dash-graph--heading-draggable' : ''
|
||||
}`
|
||||
|
@ -22,11 +26,4 @@ const LayoutCellHeader = ({isEditable, cellName}) => {
|
|||
)
|
||||
}
|
||||
|
||||
const {bool, string} = PropTypes
|
||||
|
||||
LayoutCellHeader.propTypes = {
|
||||
isEditable: bool,
|
||||
cellName: string,
|
||||
}
|
||||
|
||||
export default LayoutCellHeader
|
|
@ -1,12 +1,16 @@
|
|||
// Libraries
|
||||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ReactGridLayout, {WidthProvider} from 'react-grid-layout'
|
||||
|
||||
// Components
|
||||
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
import Layout from 'src/shared/components/Layout'
|
||||
const GridLayout = WidthProvider(ReactGridLayout)
|
||||
|
||||
// Utils
|
||||
import {fastMap} from 'src/utils/fast'
|
||||
|
||||
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
import Layout from 'src/shared/components/Layout'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
// TODO: get these const values dynamically
|
||||
STATUS_PAGE_ROW_COUNT,
|
||||
|
@ -14,13 +18,37 @@ import {
|
|||
PAGE_CONTAINER_MARGIN,
|
||||
LAYOUT_MARGIN,
|
||||
DASHBOARD_LAYOUT_ROW_HEIGHT,
|
||||
} from 'shared/constants'
|
||||
} from 'src/shared/constants'
|
||||
|
||||
// Types
|
||||
import {TimeRange, Cell, Template, Source} from 'src/types'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
const GridLayout = WidthProvider(ReactGridLayout)
|
||||
interface Props {
|
||||
source: Source
|
||||
cells: Cell[]
|
||||
timeRange: TimeRange
|
||||
templates: Template[]
|
||||
sources: Source[]
|
||||
host: string
|
||||
autoRefresh: number
|
||||
manualRefresh: number
|
||||
isStatusPage: boolean
|
||||
isEditable: boolean
|
||||
onZoom?: () => void
|
||||
onCloneCell?: () => void
|
||||
onDeleteCell?: () => void
|
||||
onSummonOverlayTechnologies?: () => void
|
||||
onPositionChange?: (cells: Cell[]) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
rowHeight: number
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class LayoutRenderer extends Component {
|
||||
class LayoutRenderer extends Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
|
@ -29,7 +57,80 @@ class LayoutRenderer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleLayoutChange = layout => {
|
||||
public render() {
|
||||
const {
|
||||
host,
|
||||
cells,
|
||||
source,
|
||||
sources,
|
||||
onZoom,
|
||||
templates,
|
||||
timeRange,
|
||||
isEditable,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
onCloneCell,
|
||||
onSummonOverlayTechnologies,
|
||||
} = this.props
|
||||
|
||||
const {rowHeight} = this.state
|
||||
const isDashboard = !!this.props.onPositionChange
|
||||
|
||||
return (
|
||||
<Authorized
|
||||
requiredRole={EDITOR_ROLE}
|
||||
propsOverride={{
|
||||
isDraggable: false,
|
||||
isResizable: false,
|
||||
draggableHandle: null,
|
||||
}}
|
||||
>
|
||||
<GridLayout
|
||||
layout={cells}
|
||||
cols={12}
|
||||
rowHeight={rowHeight}
|
||||
margin={[LAYOUT_MARGIN, LAYOUT_MARGIN]}
|
||||
containerPadding={[0, 0]}
|
||||
useCSSTransforms={false}
|
||||
onLayoutChange={this.handleLayoutChange}
|
||||
draggableHandle={'.dash-graph--draggable'}
|
||||
isDraggable={isDashboard}
|
||||
isResizable={isDashboard}
|
||||
>
|
||||
{fastMap(cells, cell => (
|
||||
<div key={cell.i}>
|
||||
<Authorized
|
||||
requiredRole={EDITOR_ROLE}
|
||||
propsOverride={{
|
||||
isEditable: false,
|
||||
}}
|
||||
>
|
||||
<Layout
|
||||
key={cell.i}
|
||||
cell={cell}
|
||||
host={host}
|
||||
source={source}
|
||||
onZoom={onZoom}
|
||||
sources={sources}
|
||||
templates={templates}
|
||||
timeRange={timeRange}
|
||||
isEditable={isEditable}
|
||||
autoRefresh={autoRefresh}
|
||||
onDeleteCell={onDeleteCell}
|
||||
onCloneCell={onCloneCell}
|
||||
manualRefresh={manualRefresh}
|
||||
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
|
||||
/>
|
||||
</Authorized>
|
||||
</div>
|
||||
))}
|
||||
</GridLayout>
|
||||
</Authorized>
|
||||
)
|
||||
}
|
||||
|
||||
private handleLayoutChange = layout => {
|
||||
if (!this.props.onPositionChange) {
|
||||
return
|
||||
}
|
||||
|
@ -67,7 +168,7 @@ class LayoutRenderer extends Component {
|
|||
}
|
||||
|
||||
// ensures that Status Page height fits the window
|
||||
calculateRowHeight = () => {
|
||||
private calculateRowHeight = () => {
|
||||
const {isStatusPage} = this.props
|
||||
|
||||
return isStatusPage
|
||||
|
@ -79,147 +180,6 @@ class LayoutRenderer extends Component {
|
|||
STATUS_PAGE_ROW_COUNT
|
||||
: DASHBOARD_LAYOUT_ROW_HEIGHT
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
host,
|
||||
cells,
|
||||
source,
|
||||
sources,
|
||||
onZoom,
|
||||
templates,
|
||||
timeRange,
|
||||
isEditable,
|
||||
onEditCell,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
onCloneCell,
|
||||
onSummonOverlayTechnologies,
|
||||
} = this.props
|
||||
|
||||
const {rowHeight} = this.state
|
||||
const isDashboard = !!this.props.onPositionChange
|
||||
|
||||
return (
|
||||
<Authorized
|
||||
requiredRole={EDITOR_ROLE}
|
||||
propsOverride={{
|
||||
isDraggable: false,
|
||||
isResizable: false,
|
||||
draggableHandle: null,
|
||||
}}
|
||||
>
|
||||
<GridLayout
|
||||
layout={cells}
|
||||
cols={12}
|
||||
rowHeight={rowHeight}
|
||||
margin={[LAYOUT_MARGIN, LAYOUT_MARGIN]}
|
||||
containerPadding={[0, 0]}
|
||||
useCSSTransforms={false}
|
||||
onResize={this.handleCellResize}
|
||||
onLayoutChange={this.handleLayoutChange}
|
||||
draggableHandle={'.dash-graph--draggable'}
|
||||
isDraggable={isDashboard}
|
||||
isResizable={isDashboard}
|
||||
>
|
||||
{fastMap(cells, cell => (
|
||||
<div key={cell.i}>
|
||||
<Authorized
|
||||
requiredRole={EDITOR_ROLE}
|
||||
propsOverride={{
|
||||
isEditable: false,
|
||||
}}
|
||||
>
|
||||
<Layout
|
||||
key={cell.i}
|
||||
cell={cell}
|
||||
host={host}
|
||||
source={source}
|
||||
onZoom={onZoom}
|
||||
sources={sources}
|
||||
templates={templates}
|
||||
timeRange={timeRange}
|
||||
isEditable={isEditable}
|
||||
onEditCell={onEditCell}
|
||||
autoRefresh={autoRefresh}
|
||||
onDeleteCell={onDeleteCell}
|
||||
onCloneCell={onCloneCell}
|
||||
manualRefresh={manualRefresh}
|
||||
onStopAddAnnotation={this.handleStopAddAnnotation}
|
||||
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
|
||||
/>
|
||||
</Authorized>
|
||||
</div>
|
||||
))}
|
||||
</GridLayout>
|
||||
</Authorized>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
LayoutRenderer.propTypes = {
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
cells: arrayOf(
|
||||
shape({
|
||||
// isWidget cells will not have queries
|
||||
isWidget: bool,
|
||||
queries: arrayOf(
|
||||
shape({
|
||||
label: string,
|
||||
text: string,
|
||||
query: string,
|
||||
}).isRequired
|
||||
),
|
||||
x: number.isRequired,
|
||||
y: number.isRequired,
|
||||
w: number.isRequired,
|
||||
h: number.isRequired,
|
||||
i: string.isRequired,
|
||||
name: string.isRequired,
|
||||
type: string.isRequired,
|
||||
timeFormat: string,
|
||||
tableOptions: shape({
|
||||
verticalTimeAxis: bool.isRequired,
|
||||
sortBy: shape({
|
||||
internalName: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
visible: bool.isRequired,
|
||||
}).isRequired,
|
||||
wrapping: string.isRequired,
|
||||
fixFirstColumn: bool.isRequired,
|
||||
}),
|
||||
fieldOptions: arrayOf(
|
||||
shape({
|
||||
internalName: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
visible: bool.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
}).isRequired
|
||||
),
|
||||
templates: arrayOf(shape()),
|
||||
host: string,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onPositionChange: func,
|
||||
onEditCell: func,
|
||||
onDeleteCell: func,
|
||||
onCloneCell: func,
|
||||
onSummonOverlayTechnologies: func,
|
||||
isStatusPage: bool,
|
||||
isEditable: bool,
|
||||
onZoom: func,
|
||||
sources: arrayOf(shape({})),
|
||||
}
|
||||
|
||||
export default LayoutRenderer
|
|
@ -1,65 +1,48 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import Dygraph from 'src/shared/components/Dygraph'
|
||||
import {withRouter, RouteComponentProps} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import SingleStat from 'src/shared/components/SingleStat'
|
||||
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||
import InvalidData from 'src/shared/components/InvalidData'
|
||||
|
||||
// Utils
|
||||
import {
|
||||
timeSeriesToDygraph,
|
||||
TimeSeriesToDyGraphReturnType,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
|
||||
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||
import InvalidData from 'src/shared/components/InvalidData'
|
||||
import {Query, Axes, RuleValues, TimeRange} from 'src/types'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
// Types
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Data} from 'src/types/dygraphs'
|
||||
|
||||
const validateTimeSeries = ts => {
|
||||
return _.every(ts, r =>
|
||||
_.every(
|
||||
r,
|
||||
(v, i: number) =>
|
||||
(i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v)
|
||||
)
|
||||
)
|
||||
}
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
import {Query, Axes, TimeRange, RemoteDataState, CellType} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
title: string
|
||||
type: CellType
|
||||
queries: Query[]
|
||||
timeRange: TimeRange
|
||||
colors: ColorString[]
|
||||
loading: RemoteDataState
|
||||
decimalPlaces: DecimalPlaces
|
||||
data: TimeSeriesServerResponse[]
|
||||
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[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
underlayCallback?: () => void
|
||||
setResolution: () => void
|
||||
handleSetHoverTime: () => void
|
||||
activeQueryIndex?: number
|
||||
}
|
||||
|
||||
type LineGraphProps = Props & RouteComponentProps<any, any>
|
||||
|
||||
@ErrorHandlingWith(InvalidData)
|
||||
class LineGraph extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
underlayCallback: () => {},
|
||||
isGraphFilled: true,
|
||||
class LineGraph extends PureComponent<LineGraphProps> {
|
||||
public static defaultProps: Partial<LineGraphProps> = {
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
|
@ -67,15 +50,16 @@ class LineGraph extends PureComponent<Props> {
|
|||
private timeSeries: TimeSeriesToDyGraphReturnType
|
||||
|
||||
public componentWillMount() {
|
||||
const {data, isInDataExplorer} = this.props
|
||||
this.parseTimeSeries(data, isInDataExplorer)
|
||||
const {data} = this.props
|
||||
this.parseTimeSeries(data)
|
||||
}
|
||||
|
||||
public parseTimeSeries(data, isInDataExplorer) {
|
||||
this.timeSeries = timeSeriesToDygraph(data, isInDataExplorer)
|
||||
this.isValidData = validateTimeSeries(
|
||||
_.get(this.timeSeries, 'timeSeries', [])
|
||||
)
|
||||
public parseTimeSeries(data) {
|
||||
const {location} = this.props
|
||||
|
||||
this.timeSeries = timeSeriesToDygraph(data, location.pathname)
|
||||
const timeSeries = _.get(this.timeSeries, 'timeSeries', [])
|
||||
this.isValidData = this.validateTimeSeries(timeSeries)
|
||||
}
|
||||
|
||||
public componentWillUpdate(nextProps) {
|
||||
|
@ -84,7 +68,7 @@ class LineGraph extends PureComponent<Props> {
|
|||
data !== nextProps.data ||
|
||||
activeQueryIndex !== nextProps.activeQueryIndex
|
||||
) {
|
||||
this.parseTimeSeries(nextProps.data, nextProps.isInDataExplorer)
|
||||
this.parseTimeSeries(nextProps.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,54 +80,41 @@ class LineGraph extends PureComponent<Props> {
|
|||
const {
|
||||
data,
|
||||
axes,
|
||||
title,
|
||||
type,
|
||||
colors,
|
||||
cellID,
|
||||
onZoom,
|
||||
loading,
|
||||
queries,
|
||||
timeRange,
|
||||
cellHeight,
|
||||
ruleValues,
|
||||
isBarGraph,
|
||||
isRefreshing,
|
||||
setResolution,
|
||||
isGraphFilled,
|
||||
showSingleStat,
|
||||
displayOptions,
|
||||
staticLegend,
|
||||
decimalPlaces,
|
||||
underlayCallback,
|
||||
isFetchingInitially,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
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) {
|
||||
return <GraphSpinner />
|
||||
}
|
||||
|
||||
const options = {
|
||||
...displayOptions,
|
||||
title,
|
||||
labels,
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
labelsKMB: true,
|
||||
fillGraph: true,
|
||||
underlayCallback,
|
||||
axisLabelWidth: 60,
|
||||
animatedZooms: true,
|
||||
drawAxesAtZero: true,
|
||||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
connectSeparatedPoints: true,
|
||||
stepPlot: type === 'line-stepplot',
|
||||
stackedGraph: type === 'line-stacked',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dygraph graph--hasYLabel" style={this.style}>
|
||||
{isRefreshing && <GraphLoadingDots />}
|
||||
{loading === RemoteDataState.Loading && <GraphLoadingDots />}
|
||||
<Dygraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
|
@ -152,17 +123,14 @@ class LineGraph extends PureComponent<Props> {
|
|||
queries={queries}
|
||||
options={options}
|
||||
timeRange={timeRange}
|
||||
isBarGraph={isBarGraph}
|
||||
timeSeries={timeSeries}
|
||||
ruleValues={ruleValues}
|
||||
staticLegend={staticLegend}
|
||||
dygraphSeries={dygraphSeries}
|
||||
setResolution={setResolution}
|
||||
isGraphFilled={this.isGraphFilled}
|
||||
containerStyle={this.containerStyle}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
isGraphFilled={showSingleStat ? false : isGraphFilled}
|
||||
>
|
||||
{showSingleStat && (
|
||||
{type === CellType.LinePlusSingleStat && (
|
||||
<SingleStat
|
||||
data={data}
|
||||
lineGraph={true}
|
||||
|
@ -171,7 +139,6 @@ class LineGraph extends PureComponent<Props> {
|
|||
suffix={this.suffix}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
isFetchingInitially={isFetchingInitially}
|
||||
/>
|
||||
)}
|
||||
</Dygraph>
|
||||
|
@ -179,6 +146,26 @@ class LineGraph extends PureComponent<Props> {
|
|||
)
|
||||
}
|
||||
|
||||
private validateTimeSeries = ts => {
|
||||
return _.every(ts, r =>
|
||||
_.every(
|
||||
r,
|
||||
(v, i: number) =>
|
||||
(i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private get isGraphFilled(): boolean {
|
||||
const {type} = this.props
|
||||
|
||||
if (type === CellType.LinePlusSingleStat) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private get style(): CSSProperties {
|
||||
return {height: '100%'}
|
||||
}
|
||||
|
@ -221,10 +208,4 @@ const GraphLoadingDots = () => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const GraphSpinner = () => (
|
||||
<div className="graph-fetching">
|
||||
<div className="graph-spinner" />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default LineGraph
|
||||
export default withRouter<Props>(LineGraph)
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import {emptyGraphCopy} from 'src/shared/copy/cell'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import AutoRefresh from 'shared/components/AutoRefresh'
|
||||
import LineGraph from 'shared/components/LineGraph'
|
||||
import SingleStat from 'shared/components/SingleStat'
|
||||
import GaugeChart from 'shared/components/GaugeChart'
|
||||
import TableGraph from 'shared/components/TableGraph'
|
||||
|
||||
import {colorsStringSchema} from 'shared/schemas'
|
||||
import {setHoverTime} from 'src/dashboards/actions'
|
||||
import {
|
||||
DEFAULT_TIME_FORMAT,
|
||||
DEFAULT_DECIMAL_PLACES,
|
||||
} from 'src/dashboards/constants'
|
||||
|
||||
const RefreshingLineGraph = AutoRefresh(LineGraph)
|
||||
const RefreshingSingleStat = AutoRefresh(SingleStat)
|
||||
const RefreshingGaugeChart = AutoRefresh(GaugeChart)
|
||||
const RefreshingTableGraph = AutoRefresh(TableGraph)
|
||||
|
||||
const RefreshingGraph = ({
|
||||
axes,
|
||||
inView,
|
||||
type,
|
||||
colors,
|
||||
onZoom,
|
||||
cellID,
|
||||
queries,
|
||||
source,
|
||||
templates,
|
||||
timeRange,
|
||||
cellHeight,
|
||||
autoRefresh,
|
||||
fieldOptions,
|
||||
timeFormat,
|
||||
tableOptions,
|
||||
decimalPlaces,
|
||||
onSetResolution,
|
||||
resizerTopHeight,
|
||||
staticLegend,
|
||||
manualRefresh, // when changed, re-mounts the component
|
||||
editQueryStatus,
|
||||
handleSetHoverTime,
|
||||
grabDataForDownload,
|
||||
isInCEO,
|
||||
}) => {
|
||||
const prefix = (axes && axes.y.prefix) || ''
|
||||
const suffix = (axes && axes.y.suffix) || ''
|
||||
if (!queries.length) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p data-test="data-explorer-no-results">{emptyGraphCopy}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'single-stat') {
|
||||
return (
|
||||
<RefreshingSingleStat
|
||||
type={type}
|
||||
source={source}
|
||||
colors={colors}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
inView={inView}
|
||||
key={manualRefresh}
|
||||
templates={templates}
|
||||
queries={[queries[0]]}
|
||||
cellHeight={cellHeight}
|
||||
autoRefresh={autoRefresh}
|
||||
decimalPlaces={decimalPlaces}
|
||||
editQueryStatus={editQueryStatus}
|
||||
onSetResolution={onSetResolution}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'gauge') {
|
||||
return (
|
||||
<RefreshingGaugeChart
|
||||
type={type}
|
||||
source={source}
|
||||
cellID={cellID}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
inView={inView}
|
||||
colors={colors}
|
||||
key={manualRefresh}
|
||||
queries={[queries[0]]}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
editQueryStatus={editQueryStatus}
|
||||
onSetResolution={onSetResolution}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'table') {
|
||||
return (
|
||||
<RefreshingTableGraph
|
||||
type={type}
|
||||
source={source}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
inView={inView}
|
||||
isInCEO={isInCEO}
|
||||
key={manualRefresh}
|
||||
queries={queries}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
cellHeight={cellHeight}
|
||||
tableOptions={tableOptions}
|
||||
fieldOptions={fieldOptions}
|
||||
timeFormat={timeFormat}
|
||||
decimalPlaces={decimalPlaces}
|
||||
editQueryStatus={editQueryStatus}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
onSetResolution={onSetResolution}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const displayOptions = {
|
||||
stepPlot: type === 'line-stepplot',
|
||||
stackedGraph: type === 'line-stacked',
|
||||
}
|
||||
|
||||
return (
|
||||
<RefreshingLineGraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
source={source}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
inView={inView}
|
||||
key={manualRefresh}
|
||||
templates={templates}
|
||||
timeRange={timeRange}
|
||||
cellHeight={cellHeight}
|
||||
autoRefresh={autoRefresh}
|
||||
isBarGraph={type === 'bar'}
|
||||
decimalPlaces={decimalPlaces}
|
||||
staticLegend={staticLegend}
|
||||
displayOptions={displayOptions}
|
||||
editQueryStatus={editQueryStatus}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
showSingleStat={type === 'line-plus-single-stat'}
|
||||
onSetResolution={onSetResolution}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
RefreshingGraph.propTypes = {
|
||||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
templates: arrayOf(shape()),
|
||||
type: string.isRequired,
|
||||
cellHeight: number,
|
||||
resizerTopHeight: number,
|
||||
axes: shape(),
|
||||
queries: arrayOf(shape()).isRequired,
|
||||
editQueryStatus: func,
|
||||
staticLegend: bool,
|
||||
onZoom: func,
|
||||
grabDataForDownload: func,
|
||||
colors: colorsStringSchema,
|
||||
cellID: string,
|
||||
inView: bool,
|
||||
tableOptions: shape({
|
||||
verticalTimeAxis: bool.isRequired,
|
||||
sortBy: shape({
|
||||
internalName: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
visible: bool.isRequired,
|
||||
}).isRequired,
|
||||
wrapping: string.isRequired,
|
||||
fixFirstColumn: bool.isRequired,
|
||||
}),
|
||||
fieldOptions: arrayOf(
|
||||
shape({
|
||||
internalName: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
visible: bool.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
timeFormat: string.isRequired,
|
||||
decimalPlaces: shape({
|
||||
isEnforced: bool.isRequired,
|
||||
digits: number.isRequired,
|
||||
}).isRequired,
|
||||
handleSetHoverTime: func.isRequired,
|
||||
isInCEO: bool,
|
||||
onSetResolution: func,
|
||||
source: shape().isRequired,
|
||||
}
|
||||
|
||||
RefreshingGraph.defaultProps = {
|
||||
manualRefresh: 0,
|
||||
staticLegend: false,
|
||||
inView: true,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
decimalPlaces: DEFAULT_DECIMAL_PLACES,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({annotations: {mode}}) => ({
|
||||
mode,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleSetHoverTime: bindActionCreators(setHoverTime, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(RefreshingGraph)
|
|
@ -0,0 +1,238 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import LineGraph from 'src/shared/components/LineGraph'
|
||||
import GaugeChart from 'src/shared/components/GaugeChart'
|
||||
import TableGraph from 'src/shared/components/TableGraph'
|
||||
import SingleStat from 'src/shared/components/SingleStat'
|
||||
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
|
||||
|
||||
// Constants
|
||||
import {emptyGraphCopy} from 'src/shared/copy/cell'
|
||||
import {
|
||||
DEFAULT_TIME_FORMAT,
|
||||
DEFAULT_DECIMAL_PLACES,
|
||||
} from 'src/dashboards/constants'
|
||||
|
||||
// Actions
|
||||
import {setHoverTime} from 'src/dashboards/actions'
|
||||
|
||||
// Types
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Source, Axes, TimeRange, Template, Query, CellType} from 'src/types'
|
||||
import {TableOptions, FieldOption, DecimalPlaces} from 'src/types/dashboards'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
source: Source
|
||||
queries: Query[]
|
||||
timeRange: TimeRange
|
||||
colors: ColorString[]
|
||||
templates: Template[]
|
||||
tableOptions: TableOptions
|
||||
fieldOptions: FieldOption[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
type: CellType
|
||||
cellID: string
|
||||
inView: boolean
|
||||
isInCEO: boolean
|
||||
timeFormat: string
|
||||
cellHeight: number
|
||||
autoRefresh: number
|
||||
staticLegend: boolean
|
||||
manualRefresh: number
|
||||
resizerTopHeight: number
|
||||
onZoom: () => void
|
||||
editQueryStatus: () => void
|
||||
onSetResolution: () => void
|
||||
grabDataForDownload: () => void
|
||||
handleSetHoverTime: () => void
|
||||
}
|
||||
|
||||
class RefreshingGraph extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
inView: true,
|
||||
manualRefresh: 0,
|
||||
staticLegend: false,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
decimalPlaces: DEFAULT_DECIMAL_PLACES,
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {inView, type, queries, source, templates} = this.props
|
||||
|
||||
if (!queries.length) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p data-test="data-explorer-no-results">{emptyGraphCopy}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TimeSeries
|
||||
source={source}
|
||||
inView={inView}
|
||||
queries={this.queries}
|
||||
templates={templates}
|
||||
>
|
||||
{({timeSeries, loading}) => {
|
||||
switch (type) {
|
||||
case CellType.SingleStat:
|
||||
return this.singleStat(timeSeries)
|
||||
case CellType.Table:
|
||||
return this.table(timeSeries)
|
||||
case CellType.Gauge:
|
||||
return this.gauge(timeSeries)
|
||||
default:
|
||||
return this.lineGraph(timeSeries, loading)
|
||||
}
|
||||
}}
|
||||
</TimeSeries>
|
||||
)
|
||||
}
|
||||
|
||||
private singleStat = (data): JSX.Element => {
|
||||
const {colors, cellHeight, decimalPlaces, manualRefresh} = this.props
|
||||
|
||||
return (
|
||||
<SingleStat
|
||||
data={data}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
suffix={this.suffix}
|
||||
lineGraph={false}
|
||||
key={manualRefresh}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private table = (data): JSX.Element => {
|
||||
const {
|
||||
colors,
|
||||
fieldOptions,
|
||||
timeFormat,
|
||||
tableOptions,
|
||||
decimalPlaces,
|
||||
manualRefresh,
|
||||
handleSetHoverTime,
|
||||
grabDataForDownload,
|
||||
isInCEO,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<TableGraph
|
||||
data={data}
|
||||
colors={colors}
|
||||
isInCEO={isInCEO}
|
||||
key={manualRefresh}
|
||||
tableOptions={tableOptions}
|
||||
fieldOptions={fieldOptions}
|
||||
timeFormat={timeFormat}
|
||||
decimalPlaces={decimalPlaces}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private gauge = (data): JSX.Element => {
|
||||
const {
|
||||
colors,
|
||||
cellID,
|
||||
cellHeight,
|
||||
decimalPlaces,
|
||||
manualRefresh,
|
||||
resizerTopHeight,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<GaugeChart
|
||||
data={data}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
suffix={this.suffix}
|
||||
key={manualRefresh}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private lineGraph = (data, loading): JSX.Element => {
|
||||
const {
|
||||
axes,
|
||||
type,
|
||||
colors,
|
||||
onZoom,
|
||||
cellID,
|
||||
queries,
|
||||
timeRange,
|
||||
cellHeight,
|
||||
decimalPlaces,
|
||||
staticLegend,
|
||||
manualRefresh,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<LineGraph
|
||||
data={data}
|
||||
type={type}
|
||||
axes={axes}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
loading={loading}
|
||||
key={manualRefresh}
|
||||
timeRange={timeRange}
|
||||
cellHeight={cellHeight}
|
||||
staticLegend={staticLegend}
|
||||
decimalPlaces={decimalPlaces}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private get queries(): Query[] {
|
||||
const {queries, type} = this.props
|
||||
if (type === CellType.SingleStat) {
|
||||
return [queries[0]]
|
||||
}
|
||||
|
||||
if (type === CellType.Gauge) {
|
||||
return [queries[0]]
|
||||
}
|
||||
|
||||
return queries
|
||||
}
|
||||
|
||||
private get prefix(): string {
|
||||
const {axes} = this.props
|
||||
|
||||
return _.get(axes, 'y.prefix', '')
|
||||
}
|
||||
|
||||
private get suffix(): string {
|
||||
const {axes} = this.props
|
||||
return _.get(axes, 'y.suffix', '')
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({annotations: {mode}}) => ({
|
||||
mode,
|
||||
})
|
||||
|
||||
const mdtp = {
|
||||
handleSetHoverTime: setHoverTime,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mdtp)(RefreshingGraph)
|
|
@ -8,11 +8,10 @@ import {DYGRAPH_CONTAINER_V_MARGIN} from 'src/shared/constants'
|
|||
import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {CellType, DecimalPlaces} from 'src/types/dashboards'
|
||||
import {Data} from 'src/types/dygraphs'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
isFetchingInitially: boolean
|
||||
decimalPlaces: DecimalPlaces
|
||||
cellHeight: number
|
||||
colors: ColorString[]
|
||||
|
@ -20,7 +19,7 @@ interface Props {
|
|||
suffix?: string
|
||||
lineGraph: boolean
|
||||
staticLegendHeight?: number
|
||||
data: Data
|
||||
data: TimeSeriesServerResponse[]
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -31,16 +30,6 @@ class SingleStat extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
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.containerStyle}>
|
||||
{this.resizerBox}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
// Library
|
||||
import React, {Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// API
|
||||
import {fetchTimeSeries} from 'src/shared/apis/query'
|
||||
|
||||
// Types
|
||||
import {Template, Source, Query, RemoteDataState} from 'src/types'
|
||||
import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series'
|
||||
|
||||
// Utils
|
||||
import AutoRefresh from 'src/utils/AutoRefresh'
|
||||
|
||||
export const DEFAULT_TIME_SERIES = [{response: {results: []}}]
|
||||
|
||||
interface RenderProps {
|
||||
timeSeries: TimeSeriesServerResponse[]
|
||||
loading: RemoteDataState
|
||||
}
|
||||
|
||||
interface Props {
|
||||
source: Source
|
||||
queries: Query[]
|
||||
children: (r: RenderProps) => JSX.Element
|
||||
inView?: boolean
|
||||
templates?: Template[]
|
||||
}
|
||||
|
||||
interface State {
|
||||
loading: RemoteDataState
|
||||
isFirstFetch: boolean
|
||||
timeSeries: TimeSeriesServerResponse[]
|
||||
}
|
||||
|
||||
class TimeSeries extends Component<Props, State> {
|
||||
public static defaultProps = {
|
||||
inView: true,
|
||||
templates: [],
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
timeSeries: DEFAULT_TIME_SERIES,
|
||||
loading: RemoteDataState.NotStarted,
|
||||
isFirstFetch: false,
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const isFirstFetch = true
|
||||
this.executeQueries(isFirstFetch)
|
||||
AutoRefresh.subscribe(this.executeQueries)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
AutoRefresh.unsubscribe(this.executeQueries)
|
||||
}
|
||||
|
||||
public async componentDidUpdate(prevProps: Props) {
|
||||
if (!this.isPropsDifferent(prevProps)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.executeQueries()
|
||||
}
|
||||
|
||||
public executeQueries = async (isFirstFetch: boolean = false) => {
|
||||
const {source, inView, queries, templates} = this.props
|
||||
|
||||
if (!inView) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!queries.length) {
|
||||
return this.setState({timeSeries: DEFAULT_TIME_SERIES})
|
||||
}
|
||||
|
||||
this.setState({loading: RemoteDataState.Loading, isFirstFetch})
|
||||
|
||||
const TEMP_RES = 300
|
||||
|
||||
try {
|
||||
const timeSeries = await fetchTimeSeries(
|
||||
source,
|
||||
queries,
|
||||
TEMP_RES,
|
||||
templates
|
||||
)
|
||||
|
||||
const newSeries = timeSeries.map((response: TimeSeriesResponse) => ({
|
||||
response,
|
||||
}))
|
||||
|
||||
this.setState({
|
||||
timeSeries: newSeries,
|
||||
loading: RemoteDataState.Done,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {timeSeries, loading, isFirstFetch} = this.state
|
||||
|
||||
const hasValues = _.some(timeSeries, s => {
|
||||
const results = _.get(s, 'response.results', [])
|
||||
const v = _.some(results, r => r.series)
|
||||
return v
|
||||
})
|
||||
|
||||
if (isFirstFetch && loading === RemoteDataState.Loading) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<h3 className="graph-spinner" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasValues) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p>No Results</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children({timeSeries, loading})
|
||||
}
|
||||
|
||||
private isPropsDifferent(nextProps: Props) {
|
||||
const isSourceDifferent = !_.isEqual(this.props.source, nextProps.source)
|
||||
|
||||
return (
|
||||
this.props.inView !== nextProps.inView ||
|
||||
!!this.queryDifference(this.props.queries, nextProps.queries).length ||
|
||||
!_.isEqual(this.props.templates, nextProps.templates) ||
|
||||
isSourceDifferent
|
||||
)
|
||||
}
|
||||
|
||||
private queryDifference = (left, right) => {
|
||||
const mapper = q => `${q.text}`
|
||||
const l = left.map(mapper)
|
||||
const r = right.map(mapper)
|
||||
return _.difference(_.union(l, r), _.intersection(l, r))
|
||||
}
|
||||
}
|
||||
|
||||
export default TimeSeries
|
|
@ -1,3 +1,4 @@
|
|||
// TODO: Delete me!
|
||||
export const DEFAULT_TIME_SERIES = [
|
||||
{
|
||||
response: {
|
||||
|
|
|
@ -1,31 +1,14 @@
|
|||
import _ from 'lodash'
|
||||
import {Data} from 'src/types/dygraphs'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
interface Result {
|
||||
lastValues: number[]
|
||||
series: string[]
|
||||
}
|
||||
|
||||
type SeriesValue = number | string
|
||||
|
||||
interface Series {
|
||||
name: string
|
||||
values: SeriesValue[][] | null
|
||||
columns: string[] | null
|
||||
}
|
||||
|
||||
interface TimeSeriesResult {
|
||||
series: Series[]
|
||||
}
|
||||
|
||||
export interface TimeSeriesResponse {
|
||||
response: {
|
||||
results: TimeSeriesResult[]
|
||||
}
|
||||
}
|
||||
|
||||
export default function(
|
||||
timeSeriesResponse: TimeSeriesResponse[] | Data | null
|
||||
timeSeriesResponse: TimeSeriesServerResponse[] | Data | null
|
||||
): Result {
|
||||
const values = _.get(
|
||||
timeSeriesResponse,
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
// Libraries
|
||||
import React, {Component} from 'react'
|
||||
|
||||
// Components
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import LayoutRenderer from 'src/shared/components/LayoutRenderer'
|
||||
import {STATUS_PAGE_TIME_RANGE} from 'src/shared/data/timeRanges'
|
||||
import {AUTOREFRESH_DEFAULT} from 'src/shared/constants'
|
||||
import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader'
|
||||
|
||||
// Constants
|
||||
import {AUTOREFRESH_DEFAULT} from 'src/shared/constants'
|
||||
import {STATUS_PAGE_TIME_RANGE} from 'src/shared/data/timeRanges'
|
||||
import {fixtureStatusPageCells} from 'src/status/fixtures'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {
|
||||
TEMP_VAR_DASHBOARD_TIME,
|
||||
TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
} from 'src/shared/constants'
|
||||
import {Source, Cell} from 'src/types'
|
||||
|
||||
// Types
|
||||
import {
|
||||
Source,
|
||||
Template,
|
||||
Cell,
|
||||
TemplateType,
|
||||
TemplateValueType,
|
||||
} from 'src/types'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface State {
|
||||
cells: Cell[]
|
||||
|
@ -39,36 +51,6 @@ class StatusPage extends Component<Props, State> {
|
|||
const {source} = this.props
|
||||
const {cells} = this.state
|
||||
|
||||
const dashboardTime = {
|
||||
id: 'dashtime',
|
||||
tempVar: TEMP_VAR_DASHBOARD_TIME,
|
||||
type: 'constant',
|
||||
values: [
|
||||
{
|
||||
value: timeRange.lower,
|
||||
type: 'constant',
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const upperDashboardTime = {
|
||||
id: 'upperdashtime',
|
||||
tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
type: 'constant',
|
||||
values: [
|
||||
{
|
||||
value: 'now()',
|
||||
type: 'constant',
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const templates = [dashboardTime, upperDashboardTime]
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<PageHeader
|
||||
|
@ -80,14 +62,16 @@ class StatusPage extends Component<Props, State> {
|
|||
<div className="dashboard container-fluid full-width">
|
||||
{cells.length ? (
|
||||
<LayoutRenderer
|
||||
autoRefresh={autoRefresh}
|
||||
timeRange={timeRange}
|
||||
host=""
|
||||
sources={[]}
|
||||
cells={cells}
|
||||
templates={templates}
|
||||
source={source}
|
||||
shouldNotBeEditable={true}
|
||||
isStatusPage={true}
|
||||
manualRefresh={0}
|
||||
isEditable={false}
|
||||
isStatusPage={true}
|
||||
timeRange={timeRange}
|
||||
templates={this.templates}
|
||||
autoRefresh={autoRefresh}
|
||||
/>
|
||||
) : (
|
||||
<span>Loading Status Page...</span>
|
||||
|
@ -97,6 +81,40 @@ class StatusPage extends Component<Props, State> {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get templates(): Template[] {
|
||||
const dashboardTime = {
|
||||
id: 'dashtime',
|
||||
tempVar: TEMP_VAR_DASHBOARD_TIME,
|
||||
type: TemplateType.Constant,
|
||||
label: '',
|
||||
values: [
|
||||
{
|
||||
value: timeRange.lower,
|
||||
type: TemplateValueType.Constant,
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const upperDashboardTime = {
|
||||
id: 'upperdashtime',
|
||||
tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
type: TemplateType.Constant,
|
||||
label: '',
|
||||
values: [
|
||||
{
|
||||
value: 'now()',
|
||||
type: TemplateValueType.Constant,
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return [dashboardTime, upperDashboardTime]
|
||||
}
|
||||
}
|
||||
|
||||
export default StatusPage
|
||||
|
|
|
@ -2,8 +2,7 @@ import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
|
|||
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
|
||||
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants/index'
|
||||
import {DEFAULT_AXIS} from 'src/dashboards/constants/cellEditor'
|
||||
import {Cell, CellQuery, Axes} from 'src/types'
|
||||
import {CellType} from 'src/types/dashboards'
|
||||
import {Cell, CellQuery, Axes, CellType} from 'src/types'
|
||||
|
||||
const emptyQuery: CellQuery = {
|
||||
query: '',
|
||||
|
|
|
@ -76,6 +76,7 @@ export interface Cell {
|
|||
links: CellLinks
|
||||
legend: Legend
|
||||
isWidget?: boolean
|
||||
inView: boolean
|
||||
}
|
||||
|
||||
export enum CellType {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
type func = (...args: any[]) => any
|
||||
|
||||
class AutoRefresh {
|
||||
public subscribers: func[] = []
|
||||
|
||||
private intervalID: NodeJS.Timer
|
||||
|
||||
public subscribe(fn: func) {
|
||||
this.subscribers = [...this.subscribers, fn]
|
||||
}
|
||||
|
||||
public unsubscribe(fn: func) {
|
||||
this.subscribers = this.subscribers.filter(f => f !== fn)
|
||||
}
|
||||
|
||||
public poll(refreshMs: number) {
|
||||
this.clearInterval()
|
||||
|
||||
if (refreshMs) {
|
||||
this.intervalID = setInterval(this.refresh, refreshMs)
|
||||
}
|
||||
}
|
||||
|
||||
public stopPolling() {
|
||||
this.clearInterval()
|
||||
}
|
||||
|
||||
private clearInterval() {
|
||||
if (!this.intervalID) {
|
||||
return
|
||||
}
|
||||
|
||||
clearInterval(this.intervalID)
|
||||
this.intervalID = null
|
||||
}
|
||||
|
||||
private refresh = () => {
|
||||
this.subscribers.forEach(fn => fn())
|
||||
}
|
||||
}
|
||||
|
||||
export default new AutoRefresh()
|
|
@ -27,9 +27,10 @@ interface TimeSeriesToTableGraphReturnType {
|
|||
|
||||
export const timeSeriesToDygraph = (
|
||||
raw: TimeSeriesServerResponse[],
|
||||
isInDataExplorer: boolean
|
||||
pathname: string = ''
|
||||
): TimeSeriesToDyGraphReturnType => {
|
||||
const isTable = false
|
||||
const isInDataExplorer = pathname.includes('data-explorer')
|
||||
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
|
||||
raw,
|
||||
isTable
|
||||
|
|
|
@ -185,6 +185,7 @@ export const cell: Cell = {
|
|||
self:
|
||||
'/chronograf/v1/dashboards/9/cells/67435af2-17bf-4caa-a5fc-0dd1ffb40dab',
|
||||
},
|
||||
inView: true,
|
||||
}
|
||||
|
||||
export const fullTimeRange = {
|
||||
|
|
|
@ -698,4 +698,5 @@ export const cell: Cell = {
|
|||
'/chronograf/v1/dashboards/10/cells/8b3b7897-49b1-422c-9443-e9b778bcbf12',
|
||||
},
|
||||
legend: {},
|
||||
inView: true,
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -30,6 +31,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -71,6 +73,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -100,6 +103,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -109,6 +113,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -124,6 +129,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm3',
|
||||
|
@ -160,6 +166,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -175,6 +182,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -212,6 +220,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -227,6 +236,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
|
@ -240,8 +250,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
},
|
||||
]
|
||||
|
||||
const isInDataExplorer = true
|
||||
const actual = timeSeriesToDygraph(influxResponse, isInDataExplorer)
|
||||
const actual = timeSeriesToDygraph(influxResponse, 'data-explorer')
|
||||
|
||||
const expected = {}
|
||||
|
||||
|
@ -254,6 +263,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mb',
|
||||
|
@ -263,6 +273,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'ma',
|
||||
|
@ -272,6 +283,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
|
@ -281,6 +293,7 @@ describe('timeSeriesToDygraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
|
@ -309,6 +322,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mb',
|
||||
|
@ -318,6 +332,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'ma',
|
||||
|
@ -327,6 +342,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
|
@ -336,6 +352,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
|
@ -349,38 +366,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
},
|
||||
]
|
||||
|
||||
const qASTs = [
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const expected = [
|
||||
['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2'],
|
||||
[1000, 1, 1, null, null],
|
||||
|
@ -397,6 +383,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mb',
|
||||
|
@ -406,6 +393,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'ma',
|
||||
|
@ -415,6 +403,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
|
@ -424,6 +413,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
|
@ -437,38 +427,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
},
|
||||
]
|
||||
|
||||
const qASTs = [
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2']
|
||||
|
||||
expect(actual.data[0]).toEqual(expected)
|
||||
|
@ -476,10 +435,7 @@ describe('timeSeriesToTableGraph', () => {
|
|||
|
||||
it('returns an array of an empty array if there is an empty response', () => {
|
||||
const influxResponse = []
|
||||
|
||||
const qASTs = []
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const expected = [[]]
|
||||
|
||||
expect(actual.data).toEqual(expected)
|
||||
|
@ -535,7 +491,12 @@ describe('transformTableData', () => {
|
|||
[3000, 2000, 1000],
|
||||
]
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const tableOptions = {verticalTimeAxis: true}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
|
@ -552,6 +513,7 @@ describe('transformTableData', () => {
|
|||
timeFormat,
|
||||
decimalPlaces
|
||||
)
|
||||
|
||||
const expected = [
|
||||
['time', 'f1', 'f2'],
|
||||
[2000, 1000, 3000],
|
||||
|
@ -570,7 +532,12 @@ describe('transformTableData', () => {
|
|||
[3000, 2000, 1000],
|
||||
]
|
||||
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
|
||||
const tableOptions = {verticalTimeAxis: true}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
|
@ -602,7 +569,12 @@ describe('transformTableData', () => {
|
|||
]
|
||||
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const tableOptions = {verticalTimeAxis: true}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
|
@ -636,7 +608,12 @@ describe('if verticalTimeAxis is false', () => {
|
|||
]
|
||||
|
||||
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
|
||||
const tableOptions = {verticalTimeAxis: false}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
verticalTimeAxis: false,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
|
@ -672,7 +649,12 @@ describe('if verticalTimeAxis is false', () => {
|
|||
]
|
||||
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const tableOptions = {verticalTimeAxis: false}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
verticalTimeAxis: false,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
Loading…
Reference in New Issue