2021-04-14 15:57:36 +03:00
import React , { FC , useState } from 'react' ;
import { css } from '@emotion/css' ;
2021-05-18 19:14:57 +03:00
import { GrafanaTheme , parseDuration , addDurationToDate } from '@grafana/data' ;
2021-05-04 16:57:11 +03:00
import { Field , InlineLabel , Input , InputControl , Select , Switch , useStyles } from '@grafana/ui' ;
2021-04-29 16:54:38 +03:00
import { useFormContext , RegisterOptions } from 'react-hook-form' ;
2021-05-04 16:57:11 +03:00
import { RuleFormType , RuleFormValues } from '../../types/rule-form' ;
import { timeOptions , timeValidationPattern } from '../../utils/time' ;
2021-04-14 15:57:36 +03:00
import { ConditionField } from './ConditionField' ;
import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker' ;
2021-05-04 16:57:11 +03:00
import { RuleEditorSection } from './RuleEditorSection' ;
2021-05-26 10:06:28 +02:00
import { PreviewRule } from './PreviewRule' ;
2021-04-14 15:57:36 +03:00
2021-05-18 19:14:57 +03:00
const MIN_TIME_RANGE_STEP_S = 10 ; // 10 seconds
2021-04-29 16:54:38 +03:00
const timeRangeValidationOptions : RegisterOptions = {
2021-04-14 15:57:36 +03:00
required : {
value : true ,
message : 'Required.' ,
} ,
2021-05-04 16:57:11 +03:00
pattern : timeValidationPattern ,
2021-05-18 19:14:57 +03:00
validate : ( value : string ) = > {
const duration = parseDuration ( value ) ;
if ( Object . keys ( duration ) . length ) {
const from = new Date ( ) ;
const to = addDurationToDate ( from , duration ) ;
const diff = to . getTime ( ) - from . getTime ( ) ;
if ( diff < MIN_TIME_RANGE_STEP_S * 1000 ) {
return ` Cannot be less than ${ MIN_TIME_RANGE_STEP_S } seconds. ` ;
}
if ( diff % ( MIN_TIME_RANGE_STEP_S * 1000 ) !== 0 ) {
return ` Must be a multiple of ${ MIN_TIME_RANGE_STEP_S } seconds. ` ;
}
}
return true ;
} ,
2021-04-14 15:57:36 +03:00
} ;
export const ConditionsStep : FC = ( ) = > {
const styles = useStyles ( getStyles ) ;
const [ showErrorHandling , setShowErrorHandling ] = useState ( false ) ;
2021-04-29 16:54:38 +03:00
const {
register ,
control ,
watch ,
formState : { errors } ,
} = useFormContext < RuleFormValues > ( ) ;
2021-04-14 15:57:36 +03:00
const type = watch ( 'type' ) ;
return (
< RuleEditorSection stepNo = { 3 } title = "Define alert conditions" >
2021-05-18 19:14:57 +03:00
{ type === RuleFormType . grafana && (
2021-04-14 15:57:36 +03:00
< >
< ConditionField / >
< Field label = "Evaluate" >
< div className = { styles . flexRow } >
< InlineLabel width = { 16 } tooltip = "How often the alert will be evaluated to see if it fires" >
Evaluate every
< / InlineLabel >
< Field
className = { styles . inlineField }
error = { errors . evaluateEvery ? . message }
invalid = { ! ! errors . evaluateEvery ? . message }
2021-05-18 19:14:57 +03:00
validationMessageHorizontalOverflow = { true }
2021-04-14 15:57:36 +03:00
>
2021-04-29 16:54:38 +03:00
< Input width = { 8 } { ...register ( 'evaluateEvery' , timeRangeValidationOptions ) } / >
2021-04-14 15:57:36 +03:00
< / Field >
< InlineLabel
width = { 7 }
tooltip = 'Once condition is breached, alert will go into pending state. If it is pending for longer than the "for" value, it will become a firing alert.'
>
for
< / InlineLabel >
< Field
className = { styles . inlineField }
error = { errors . evaluateFor ? . message }
invalid = { ! ! errors . evaluateFor ? . message }
2021-05-18 19:14:57 +03:00
validationMessageHorizontalOverflow = { true }
2021-04-14 15:57:36 +03:00
>
2021-04-29 16:54:38 +03:00
< Input width = { 8 } { ...register ( 'evaluateFor' , timeRangeValidationOptions ) } / >
2021-04-14 15:57:36 +03:00
< / Field >
< / div >
< / Field >
< Field label = "Configure no data and error handling" horizontal = { true } className = { styles . switchField } >
< Switch value = { showErrorHandling } onChange = { ( ) = > setShowErrorHandling ( ! showErrorHandling ) } / >
< / Field >
{ showErrorHandling && (
< >
< Field label = "Alert state if no data or all values are null" >
< InputControl
2021-04-29 16:54:38 +03:00
render = { ( { field : { onChange , ref , . . . field } } ) = > (
2021-05-18 19:14:57 +03:00
< GrafanaAlertStatePicker
{ . . . field }
width = { 42 }
includeNoData = { true }
onChange = { ( value ) = > onChange ( value ? . value ) }
/ >
2021-04-29 16:54:38 +03:00
) }
2021-04-14 15:57:36 +03:00
name = "noDataState"
/ >
< / Field >
< Field label = "Alert state if execution error or timeout" >
< InputControl
2021-04-29 16:54:38 +03:00
render = { ( { field : { onChange , ref , . . . field } } ) = > (
2021-05-18 19:14:57 +03:00
< GrafanaAlertStatePicker
{ . . . field }
width = { 42 }
includeNoData = { false }
onChange = { ( value ) = > onChange ( value ? . value ) }
/ >
2021-04-29 16:54:38 +03:00
) }
2021-04-14 15:57:36 +03:00
name = "execErrState"
/ >
< / Field >
< / >
) }
< / >
) }
2021-05-18 19:14:57 +03:00
{ type === RuleFormType . cloud && (
2021-04-14 15:57:36 +03:00
< >
< Field label = "For" description = "Expression has to be true for this long for the alert to be fired." >
< div className = { styles . flexRow } >
< Field invalid = { ! ! errors . forTime ? . message } error = { errors . forTime ? . message } className = { styles . inlineField } >
< Input
2021-04-29 16:54:38 +03:00
{ . . . register ( 'forTime' , { pattern : { value : /^\d+$/ , message : 'Must be a postive integer.' } } ) }
2021-04-14 15:57:36 +03:00
width = { 8 }
/ >
< / Field >
< InputControl
name = "forTimeUnit"
2021-04-29 16:54:38 +03:00
render = { ( { field : { onChange , ref , . . . field } } ) = > (
< Select
{ . . . field }
options = { timeOptions }
onChange = { ( value ) = > onChange ( value ? . value ) }
width = { 15 }
className = { styles . timeUnit }
/ >
) }
2021-04-14 15:57:36 +03:00
control = { control }
/ >
< / div >
< / Field >
< / >
) }
2021-05-26 10:06:28 +02:00
< PreviewRule / >
2021-04-14 15:57:36 +03:00
< / RuleEditorSection >
) ;
} ;
const getStyles = ( theme : GrafanaTheme ) = > ( {
inlineField : css `
margin - bottom : 0 ;
` ,
flexRow : css `
display : flex ;
flex - direction : row ;
justify - content : flex - start ;
align - items : flex - start ;
` ,
numberInput : css `
width : 200px ;
& + & {
margin - left : $ { theme . spacing . sm } ;
}
` ,
timeUnit : css `
margin - left : $ { theme . spacing . xs } ;
` ,
switchField : css `
display : inline - flex ;
flex - direction : row - reverse ;
margin - top : $ { theme . spacing . md } ;
& > div :first - child {
margin - left : $ { theme . spacing . sm } ;
}
` ,
} ) ;