Visualize check view with check plot (#14599)

* Use CheckPlot to visualize check view

* Revert to add threshold types

* Disable view type dropdown in checkView

* Add proper equality

* Add errors in default cases
pull/14605/head
Deniz Kusefoglu 2019-08-08 12:25:58 -07:00 committed by GitHub
parent cb9d8eafe7
commit 17c40a75de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 203 additions and 85 deletions

View File

@ -8938,6 +8938,11 @@ components:
type: array type: array
items: items:
$ref: "#/components/schemas/Threshold" $ref: "#/components/schemas/Threshold"
Threshold:
oneOf:
- $ref: "#/components/schemas/GreaterThreshold"
- $ref: "#/components/schemas/LesserThreshold"
- $ref: "#/components/schemas/RangeThreshold"
DeadmanCheck: DeadmanCheck:
allOf: allOf:
- $ref: "#/components/schemas/CheckBase" - $ref: "#/components/schemas/CheckBase"
@ -8954,19 +8959,54 @@ components:
type: boolean type: boolean
level: level:
$ref: "#/components/schemas/CheckStatusLevel" $ref: "#/components/schemas/CheckStatusLevel"
Threshold: ThresholdBase:
properties: properties:
level: level:
$ref: "#/components/schemas/CheckStatusLevel" $ref: "#/components/schemas/CheckStatusLevel"
allValues: allValues:
description: if true, only alert if all values meet threshold description: if true, only alert if all values meet threshold
type: boolean type: boolean
lowerBound: GreaterThreshold:
type: number allOf:
format: float - $ref: "#/components/schemas/ThresholdBase"
upperBound: - type: object
type: number required: [type, value]
format: float properties:
type:
type: string
enum: [greater]
value:
type: number
format: float
LesserThreshold:
allOf:
- $ref: "#/components/schemas/ThresholdBase"
- type: object
required: [type, value]
properties:
type:
type: string
enum: [lesser]
value:
type: number
format: float
RangeThreshold:
allOf:
- $ref: "#/components/schemas/ThresholdBase"
- type: object
required: [type, min, max, within]
properties:
type:
type: string
enum: [range]
min:
type: number
format: float
max:
type: number
format: float
within:
type: boolean
CheckStatusLevel: CheckStatusLevel:
description: the state to record if check matches a criteria description: the state to record if check matches a criteria
type: string type: string

View File

@ -23,7 +23,10 @@ export const DEFAULT_THRESHOLD_CHECK: Partial<ThresholdCheck> = {
name: DEFAULT_CHECK_NAME, name: DEFAULT_CHECK_NAME,
type: 'threshold', type: 'threshold',
status: 'active', status: 'active',
thresholds: [], thresholds: [
{type: 'greater', value: 40, level: 'INFO'},
{type: 'lesser', value: 20, level: 'OK'},
],
every: DEFAULT_CHECK_EVERY, every: DEFAULT_CHECK_EVERY,
offset: DEFAULT_CHECK_OFFSET, offset: DEFAULT_CHECK_OFFSET,
} }
@ -59,9 +62,9 @@ export const check1: Check = {
statusMessageTemplate: 'this is a great message template', statusMessageTemplate: 'this is a great message template',
thresholds: [ thresholds: [
{ {
level: 'UNKNOWN', level: 'CRIT',
lowerBound: 20, type: 'lesser',
allValues: false, value: 10,
}, },
], ],
} }
@ -81,9 +84,9 @@ export const check2: Check = {
statusMessageTemplate: 'this is a great message template', statusMessageTemplate: 'this is a great message template',
thresholds: [ thresholds: [
{ {
level: 'UNKNOWN', level: 'INFO',
lowerBound: 20, type: 'greater',
allValues: false, value: 10,
}, },
], ],
} }

View File

@ -1,5 +1,6 @@
// Libraries // Libraries
import React, {useState, FunctionComponent} from 'react' import React, {FunctionComponent} from 'react'
import {connect} from 'react-redux'
import {Config, Table} from '@influxdata/giraffe' import {Config, Table} from '@influxdata/giraffe'
import {flatMap} from 'lodash' import {flatMap} from 'lodash'
@ -21,21 +22,25 @@ import {
RemoteDataState, RemoteDataState,
CheckViewProperties, CheckViewProperties,
TimeZone, TimeZone,
AppState,
Check,
Threshold, Threshold,
} from 'src/types' } from 'src/types'
import {updateCurrentCheck} from 'src/alerting/actions/checks'
const X_COLUMN = '_time' const X_COLUMN = '_time'
const Y_COLUMN = '_value' const Y_COLUMN = '_value'
const THRESHOLDS: Threshold[] = [ interface DispatchProps {
{ updateCurrentCheck: typeof updateCurrentCheck
level: 'UNKNOWN', }
lowerBound: 20,
allValues: false,
},
]
interface Props { interface StateProps {
check: Partial<Check>
thresholds: Threshold[]
}
interface OwnProps {
table: Table table: Table
fluxGroupKeyUnion: string[] fluxGroupKeyUnion: string[]
loading: RemoteDataState loading: RemoteDataState
@ -44,14 +49,20 @@ interface Props {
children: (config: Config) => JSX.Element children: (config: Config) => JSX.Element
} }
type Props = OwnProps & DispatchProps & StateProps
const CheckPlot: FunctionComponent<Props> = ({ const CheckPlot: FunctionComponent<Props> = ({
updateCurrentCheck,
table, table,
thresholds,
fluxGroupKeyUnion, fluxGroupKeyUnion,
loading, loading,
children, children,
timeZone, timeZone,
}) => { }) => {
const [thresholds, setThresholds] = useState(THRESHOLDS) const updateCheckThresholds = (thresholds: Threshold[]) => {
updateCurrentCheck({thresholds})
}
const [yDomain, onSetYDomain, onResetYDomain] = useVisDomainSettings( const [yDomain, onSetYDomain, onResetYDomain] = useVisDomainSettings(
[0, 100], [0, 100],
@ -115,7 +126,7 @@ const CheckPlot: FunctionComponent<Props> = ({
<ThresholdMarkers <ThresholdMarkers
key="custom" key="custom"
thresholds={thresholds} thresholds={thresholds}
onSetThresholds={setThresholds} onSetThresholds={updateCheckThresholds}
yScale={yScale} yScale={yScale}
yDomain={yDomain} yDomain={yDomain}
/> />
@ -132,4 +143,26 @@ const CheckPlot: FunctionComponent<Props> = ({
) )
} }
export default CheckPlot const mstp = (state: AppState): StateProps => {
const {
checks: {
current: {check},
},
} = state
let thresholds = []
if (check.type === 'threshold') {
thresholds = check.thresholds
}
return {check, thresholds}
}
const mdtp: DispatchProps = {
updateCurrentCheck: updateCurrentCheck,
}
export default connect<StateProps, DispatchProps, {}>(
mstp,
mdtp
)(CheckPlot)

View File

@ -11,29 +11,29 @@ import {isInDomain, clamp} from 'src/shared/utils/vis'
import {DragEvent} from 'src/shared/utils/useDragBehavior' import {DragEvent} from 'src/shared/utils/useDragBehavior'
// Types // Types
import {Threshold} from 'src/types' import {GreaterThreshold} from 'src/types'
interface Props { interface Props {
yScale: Scale<number, number> yScale: Scale<number, number>
yDomain: number[] yDomain: number[]
threshold: Threshold threshold: GreaterThreshold
onChangePos: (e: DragEvent) => void onChangePos: (e: DragEvent) => void
} }
const GreaterThresholdMarker: FunctionComponent<Props> = ({ const GreaterThresholdMarker: FunctionComponent<Props> = ({
yDomain, yDomain,
yScale, yScale,
threshold: {level, lowerBound}, threshold: {level, value},
onChangePos, onChangePos,
}) => { }) => {
const y = yScale(clamp(lowerBound, yDomain)) const y = yScale(clamp(value, yDomain))
return ( return (
<> <>
{isInDomain(lowerBound, yDomain) && ( {isInDomain(value, yDomain) && (
<ThresholdMarker level={level} y={y} onDrag={onChangePos} /> <ThresholdMarker level={level} y={y} onDrag={onChangePos} />
)} )}
{lowerBound <= yDomain[1] && ( {value <= yDomain[1] && (
<ThresholdMarkerArea <ThresholdMarkerArea
level={level} level={level}
top={yScale(yDomain[1])} top={yScale(yDomain[1])}

View File

@ -11,29 +11,29 @@ import {clamp, isInDomain} from 'src/shared/utils/vis'
import {DragEvent} from 'src/shared/utils/useDragBehavior' import {DragEvent} from 'src/shared/utils/useDragBehavior'
// Types // Types
import {Threshold} from 'src/types' import {LesserThreshold} from 'src/types'
interface Props { interface Props {
yScale: Scale<number, number> yScale: Scale<number, number>
yDomain: number[] yDomain: number[]
threshold: Threshold threshold: LesserThreshold
onChangePos: (e: DragEvent) => void onChangePos: (e: DragEvent) => void
} }
const LessThresholdMarker: FunctionComponent<Props> = ({ const LessThresholdMarker: FunctionComponent<Props> = ({
yScale, yScale,
yDomain, yDomain,
threshold: {level, upperBound}, threshold: {level, value},
onChangePos, onChangePos,
}) => { }) => {
const y = yScale(clamp(upperBound, yDomain)) const y = yScale(clamp(value, yDomain))
return ( return (
<> <>
{isInDomain(upperBound, yDomain) && ( {isInDomain(value, yDomain) && (
<ThresholdMarker level={level} y={y} onDrag={onChangePos} /> <ThresholdMarker level={level} y={y} onDrag={onChangePos} />
)} )}
{upperBound >= yDomain[0] && ( {value >= yDomain[0] && (
<ThresholdMarkerArea <ThresholdMarkerArea
level={level} level={level}
top={y} top={y}

View File

@ -11,12 +11,12 @@ import {isInDomain, clamp} from 'src/shared/utils/vis'
import {DragEvent} from 'src/shared/utils/useDragBehavior' import {DragEvent} from 'src/shared/utils/useDragBehavior'
// Types // Types
import {Threshold} from 'src/types' import {RangeThreshold} from 'src/types'
interface Props { interface Props {
yScale: Scale<number, number> yScale: Scale<number, number>
yDomain: number[] yDomain: number[]
threshold: Threshold threshold: RangeThreshold
onChangeMaxPos: (e: DragEvent) => void onChangeMaxPos: (e: DragEvent) => void
onChangeMinPos: (e: DragEvent) => void onChangeMinPos: (e: DragEvent) => void
} }
@ -24,33 +24,33 @@ interface Props {
const RangeThresholdMarkers: FunctionComponent<Props> = ({ const RangeThresholdMarkers: FunctionComponent<Props> = ({
yScale, yScale,
yDomain, yDomain,
threshold: {level, lowerBound, upperBound}, threshold: {level, within, min, max},
onChangeMinPos, onChangeMinPos,
onChangeMaxPos, onChangeMaxPos,
}) => { }) => {
const minY = yScale(clamp(lowerBound, yDomain)) const minY = yScale(clamp(min, yDomain))
const maxY = yScale(clamp(upperBound, yDomain)) const maxY = yScale(clamp(max, yDomain))
return ( return (
<> <>
{isInDomain(lowerBound, yDomain) && ( {isInDomain(min, yDomain) && (
<ThresholdMarker level={level} y={minY} onDrag={onChangeMinPos} /> <ThresholdMarker level={level} y={minY} onDrag={onChangeMinPos} />
)} )}
{isInDomain(upperBound, yDomain) && ( {isInDomain(max, yDomain) && (
<ThresholdMarker level={level} y={maxY} onDrag={onChangeMaxPos} /> <ThresholdMarker level={level} y={maxY} onDrag={onChangeMaxPos} />
)} )}
{lowerBound > upperBound ? ( {within ? (
<ThresholdMarkerArea level={level} top={maxY} height={minY - maxY} /> <ThresholdMarkerArea level={level} top={maxY} height={minY - maxY} />
) : ( ) : (
<> <>
{upperBound <= yDomain[1] && ( {max <= yDomain[1] && (
<ThresholdMarkerArea <ThresholdMarkerArea
level={level} level={level}
top={yScale(yDomain[1])} top={yScale(yDomain[1])}
height={maxY - yScale(yDomain[1])} height={maxY - yScale(yDomain[1])}
/> />
)} )}
{lowerBound >= yDomain[0] && ( {min >= yDomain[0] && (
<ThresholdMarkerArea <ThresholdMarkerArea
level={level} level={level}
top={minY} top={minY}

View File

@ -36,6 +36,18 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
[field]: yValue, [field]: yValue,
} }
if (
nextThreshold.type === 'range' &&
nextThreshold.min > nextThreshold.max
) {
// If the user drags the min past the max or vice versa, we swap the
// values that are set so that the min is always at most the max
const maxValue = nextThreshold.min
nextThreshold.min = nextThreshold.max
nextThreshold.max = maxValue
}
const nextThresholds = thresholds.map((t, i) => const nextThresholds = thresholds.map((t, i) =>
i === index ? nextThreshold : t i === index ? nextThreshold : t
) )
@ -50,41 +62,40 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
const onChangeMaxPos = ({y}) => handleDrag(index, 'maxValue', y) const onChangeMaxPos = ({y}) => handleDrag(index, 'maxValue', y)
const onChangeMinPos = ({y}) => handleDrag(index, 'minValue', y) const onChangeMinPos = ({y}) => handleDrag(index, 'minValue', y)
if (threshold.lowerBound && threshold.upperBound) { switch (threshold.type) {
return ( case 'greater':
<RangeThresholdMarkers return (
key={index} <GreaterThresholdMarker
yScale={yScale} key={index}
yDomain={yDomain} yScale={yScale}
threshold={threshold} yDomain={yDomain}
onChangeMinPos={onChangeMinPos} threshold={threshold}
onChangeMaxPos={onChangeMaxPos} onChangePos={onChangePos}
/> />
) )
} case 'lesser':
return (
if (threshold.lowerBound) { <LessThresholdMarker
return ( key={index}
<GreaterThresholdMarker yScale={yScale}
key={index} yDomain={yDomain}
yScale={yScale} threshold={threshold}
yDomain={yDomain} onChangePos={onChangePos}
threshold={threshold} />
onChangePos={onChangePos} )
/> case 'range':
) return (
} <RangeThresholdMarkers
key={index}
if (threshold.upperBound) { yScale={yScale}
return ( yDomain={yDomain}
<LessThresholdMarker threshold={threshold}
key={index} onChangeMinPos={onChangeMinPos}
yScale={yScale} onChangeMaxPos={onChangeMaxPos}
yDomain={yDomain} />
threshold={threshold} )
onChangePos={onChangePos} default:
/> throw new Error('Unknown threshold type in <ThresholdMarkers /> ')
)
} }
})} })}
</div> </div>

View File

@ -12,6 +12,7 @@ import FluxTablesTransform from 'src/shared/components/FluxTablesTransform'
import XYPlot from 'src/shared/components/XYPlot' import XYPlot from 'src/shared/components/XYPlot'
import ScatterPlot from 'src/shared/components/ScatterPlot' import ScatterPlot from 'src/shared/components/ScatterPlot'
import LatestValueTransform from 'src/shared/components/LatestValueTransform' import LatestValueTransform from 'src/shared/components/LatestValueTransform'
import CheckPlot from 'src/shared/components/CheckPlot'
// Types // Types
import { import {
@ -155,8 +156,21 @@ const ViewSwitcher: FunctionComponent<Props> = ({
</ScatterPlot> </ScatterPlot>
) )
case 'check':
return (
<CheckPlot
table={table}
fluxGroupKeyUnion={fluxGroupKeyUnion}
loading={loading}
timeZone={timeZone}
viewProperties={properties}
>
{config => <Plot config={config} />}
</CheckPlot>
)
default: default:
return <div /> throw new Error('Unknown view type in <ViewSwitcher /> ')
} }
} }

View File

@ -16,6 +16,7 @@ import {VIS_GRAPHICS} from 'src/timeMachine/constants/visGraphics'
// Types // Types
import {View, NewView, AppState, ViewType} from 'src/types' import {View, NewView, AppState, ViewType} from 'src/types'
import {ComponentStatus} from 'src/clockface'
interface DispatchProps { interface DispatchProps {
onUpdateType: typeof setType onUpdateType: typeof setType
@ -34,7 +35,11 @@ class ViewTypeDropdown extends PureComponent<Props> {
widthPixels={215} widthPixels={215}
className="view-type-dropdown" className="view-type-dropdown"
button={(active, onClick) => ( button={(active, onClick) => (
<Dropdown.Button active={active} onClick={onClick}> <Dropdown.Button
active={active}
onClick={onClick}
status={this.dropdownStatus}
>
{this.getVewTypeGraphic(this.selectedView)} {this.getVewTypeGraphic(this.selectedView)}
</Dropdown.Button> </Dropdown.Button>
)} )}
@ -67,6 +72,15 @@ class ViewTypeDropdown extends PureComponent<Props> {
)) ))
} }
private get dropdownStatus(): ComponentStatus {
const {view} = this.props
if (view.properties.type === 'check') {
return ComponentStatus.Disabled
}
return ComponentStatus.Valid
}
private get selectedView(): ViewType { private get selectedView(): ViewType {
const {view} = this.props const {view} = this.props

View File

@ -37,6 +37,9 @@ export {
LevelRule, LevelRule,
TagRule, TagRule,
CheckStatusLevel, CheckStatusLevel,
GreaterThreshold,
LesserThreshold,
RangeThreshold,
ThresholdCheck, ThresholdCheck,
DeadmanCheck, DeadmanCheck,
NotificationEndpoint, NotificationEndpoint,