Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(durations): Add duration input that checks input validity as duration #16385

Merged
merged 12 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions ui/src/alerting/actions/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import * as api from 'src/client'
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
import {incrementCloneName} from 'src/utils/naming'
import {reportError} from 'src/shared/utils/errors'
import {isDurationParseable} from 'src/shared/utils/duration'
import {checkThresholdsValid} from '../utils/checkValidate'
import {createView} from 'src/shared/utils/view'
import {getOrg} from 'src/organizations/selectors'

// Actions
import {
Expand Down Expand Up @@ -44,10 +48,6 @@ import {
DeadmanCheck,
} from 'src/types'

// Utils
import {createView} from 'src/shared/utils/view'
import {getOrg} from 'src/organizations/selectors'

export type Action =
| ReturnType<typeof setAllChecks>
| ReturnType<typeof setCheck>
Expand Down Expand Up @@ -187,6 +187,7 @@ export const saveCheckFromTimeMachine = () => async (
tags,
thresholds,
} as ThresholdCheck
checkThresholdsValid(thresholds)
} else if (check.type === 'deadman') {
check = {
...check,
Expand All @@ -199,6 +200,16 @@ export const saveCheckFromTimeMachine = () => async (
tags,
timeSince,
} as DeadmanCheck
if (!isDurationParseable(timeSince) || !isDurationParseable(staleTime)) {
throw new Error('Duration fields must contain valid duration')
}
}

if (!isDurationParseable(offset)) {
throw new Error('Check offset must be a valid duration')
}
if (!isDurationParseable(every)) {
throw new Error('Check every must be a valid duration')
}

const resp = check.id
Expand Down
6 changes: 6 additions & 0 deletions ui/src/alerting/actions/notifications/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ export const createRule = (rule: NotificationRuleDraft) => async (
export const updateRule = (rule: NotificationRuleDraft) => async (
dispatch: Dispatch<Action | NotificationAction>
) => {
if (rule.offset == '') {
throw new Error('Notification Rule offset field can not be empty')
}
if (rule.every == '') {
throw new Error('Notification Rule every field can not be empty')
}
const resp = await api.putNotificationRule({
ruleID: rule.id,
data: draftRuleToPostRule(rule),
Expand Down
21 changes: 11 additions & 10 deletions ui/src/alerting/components/builder/CheckMetaCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {connect} from 'react-redux'
import {Form, ComponentSize, ComponentColor, Grid} from '@influxdata/clockface'
import DashedButton from 'src/shared/components/dashed_button/DashedButton'
import CheckTagRow from 'src/alerting/components/builder/CheckTagRow'
import DurationSelector from 'src/shared/components/DurationSelector'
import BuilderCard from 'src/timeMachine/components/builderCard/BuilderCard'
import DurationInput from 'src/shared/components/DurationInput'

// Actions
import {
Expand All @@ -18,7 +18,8 @@ import {
} from 'src/alerting/actions/alertBuilder'

// Constants
import {CHECK_EVERY_OPTIONS, CHECK_OFFSET_OPTIONS} from 'src/alerting/constants'
import {CHECK_OFFSET_OPTIONS} from 'src/alerting/constants'
import {DURATION_STRINGS} from 'src/timeMachine/constants/queryBuilder'

// Types
import {AppState, CheckTagSet} from 'src/types'
Expand Down Expand Up @@ -63,19 +64,19 @@ const CheckMetaCard: FC<Props> = ({
<Grid.Row>
<Grid.Column widthSM={6}>
<Form.Element label="Schedule Every">
<DurationSelector
selectedDuration={every}
durations={CHECK_EVERY_OPTIONS}
onSelectDuration={onSelectCheckEvery}
<DurationInput
value={every}
suggestions={DURATION_STRINGS}
onSubmit={onSelectCheckEvery}
/>
</Form.Element>
</Grid.Column>
<Grid.Column widthSM={6}>
<Form.Element label="Offset">
<DurationSelector
selectedDuration={offset}
durations={CHECK_OFFSET_OPTIONS}
onSelectDuration={onSetOffset}
<DurationInput
value={offset}
suggestions={CHECK_OFFSET_OPTIONS}
onSubmit={onSetOffset}
/>
</Form.Element>
</Grid.Column>
Expand Down
26 changes: 10 additions & 16 deletions ui/src/alerting/components/builder/DeadmanConditions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
ComponentSize,
PanelBody,
TextBlock,
InputType,
Input,
FlexDirection,
InfluxColors,
} from '@influxdata/clockface'
Expand All @@ -25,6 +23,8 @@ import {

// Types
import {CheckStatusLevel, AppState} from 'src/types'
import DurationInput from 'src/shared/components/DurationInput'
import {CHECK_OFFSET_OPTIONS} from 'src/alerting/constants'

interface DispatchProps {
onSetStaleTime: typeof setStaleTime
Expand Down Expand Up @@ -77,14 +77,11 @@ const DeadmanConditions: FC<Props> = ({
>
<TextBlock testID="when-value-text-block" text="for" />
<FlexBox.Child testID="component-spacer--flex-child">
<Input
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onSetTimeSince(e.target.value)
}
name="timeSince"
testID="input-field"
type={InputType.Text}
<DurationInput
suggestions={CHECK_OFFSET_OPTIONS}
onSubmit={onSetTimeSince}
value={timeSince}
showDivider={false}
/>
</FlexBox.Child>
<TextBlock testID="set-status-to-text-block" text="set status to" />
Expand All @@ -106,14 +103,11 @@ const DeadmanConditions: FC<Props> = ({
text="And stop checking after"
/>
<FlexBox.Child testID="component-spacer--flex-child">
<Input
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onSetStaleTime(e.target.value)
}
name="staleTime"
testID="input-field"
type={InputType.Text}
<DurationInput
suggestions={CHECK_OFFSET_OPTIONS}
onSubmit={onSetStaleTime}
value={staleTime}
showDivider={false}
/>
</FlexBox.Child>
</FlexBox>
Expand Down
4 changes: 2 additions & 2 deletions ui/src/alerting/components/notifications/EditRuleOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface StateProps {

interface DispatchProps {
onNotify: typeof notify
onUpdateRule: (rule: NotificationRuleDraft) => Promise<void>
onUpdateRule: typeof updateRule
}

type Props = WithRouterProps & StateProps & DispatchProps
Expand Down Expand Up @@ -84,7 +84,7 @@ const mstp = ({rules}: AppState, {params}: Props): StateProps => {

const mdtp = {
onNotify: notify,
onUpdateRule: updateRule as any,
onUpdateRule: updateRule,
}

export default connect<StateProps, DispatchProps>(
Expand Down
11 changes: 10 additions & 1 deletion ui/src/alerting/components/notifications/RuleOverlayContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ const RuleOverlayContents: FC<Props> = ({saveButtonText, onSave}) => {
})
}

const handleChangeParameter = (key: keyof NotificationRuleDraft) => (
value: string
) => {
dispatch({
type: 'UPDATE_RULE',
rule: {...rule, [key]: value} as NotificationRuleDraft,
})
}

return (
<Grid>
<Form>
Expand All @@ -57,7 +66,7 @@ const RuleOverlayContents: FC<Props> = ({saveButtonText, onSave}) => {
onChange={handleChange}
/>
</Form.Element>
<RuleSchedule rule={rule} onChange={handleChange} />
<RuleSchedule rule={rule} onChange={handleChangeParameter} />
</Panel.Body>
</Panel>
</Grid.Column>
Expand Down
30 changes: 16 additions & 14 deletions ui/src/alerting/components/notifications/RuleSchedule.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
// Libraries
import React, {FC, ChangeEvent} from 'react'
import React, {FC} from 'react'

// Components
import {Form, Input, InputType, Grid, Columns} from '@influxdata/clockface'
import {Form, Grid, Columns} from '@influxdata/clockface'
import DurationInput from 'src/shared/components/DurationInput'

// Types
import {RuleState} from './RuleOverlay.reducer'
import {DURATION_STRINGS} from 'src/timeMachine/constants/queryBuilder'
import {NotificationRuleDraft} from 'src/types'
import {CHECK_OFFSET_OPTIONS} from 'src/alerting/constants'

interface Props {
rule: RuleState
onChange: (e: ChangeEvent) => void
onChange: (key: keyof NotificationRuleDraft) => (value: string) => void
}

const RuleSchedule: FC<Props> = ({rule, onChange}) => {
Expand All @@ -19,25 +23,23 @@ const RuleSchedule: FC<Props> = ({rule, onChange}) => {
<Grid.Row>
<Grid.Column widthXS={Columns.Four}>
<Form.Element label="Schedule Every">
<Input
value={every}
name="every"
type={InputType.Text}
<DurationInput
value={every || ''}
onSubmit={onChange('every')}
suggestions={DURATION_STRINGS}
placeholder="1d3h30s"
onChange={onChange}
testID="rule-schedule-every--input"
/>
</Form.Element>
</Grid.Column>

<Grid.Column widthXS={Columns.Four}>
<Form.Element label="Offset">
<Input
name="offset"
type={InputType.Text}
value={offset}
placeholder="20m"
onChange={onChange}
<DurationInput
value={offset || ''}
onSubmit={onChange('offset')}
suggestions={CHECK_OFFSET_OPTIONS}
placeholder="10m"
testID="rule-schedule-offset--input"
/>
</Form.Element>
Expand Down
2 changes: 2 additions & 0 deletions ui/src/alerting/components/notifications/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const changeStatusRule = (
export const initRuleDraft = (orgID: string): NotificationRuleDraft => ({
type: 'http',
every: '10m',
offset: '0s',
url: '',
orgID,
name: '',
Expand Down Expand Up @@ -130,6 +131,7 @@ export const ruleToDraftRule = (
const tagRules = rule.tagRules || []
return {
...rule,
offset: rule.offset || '',
statusRules: statusRules.map(value => ({cid: uuid.v4(), value})),
tagRules: tagRules.map(value => ({cid: uuid.v4(), value})),
}
Expand Down
33 changes: 14 additions & 19 deletions ui/src/alerting/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {DURATIONS} from 'src/timeMachine/constants/queryBuilder'

import {ThresholdCheck, TagRuleDraft} from 'src/types'
import {NotificationEndpoint, CheckStatusLevel} from 'src/client'
import {ComponentColor, InfluxColors} from '@influxdata/clockface'
import {DurationOption} from 'src/shared/components/DurationSelector'

export const DEFAULT_CHECK_NAME = 'Name this Check'
export const DEFAULT_NOTIFICATION_RULE_NAME = 'Name this Notification Rule'
Expand All @@ -17,22 +14,20 @@ export const DEFAULT_DEADMAN_LEVEL: CheckStatusLevel = 'CRIT'
export const DEFAULT_STATUS_MESSAGE =
'Check: ${ r._check_name } is: ${ r._level }'

export const CHECK_EVERY_OPTIONS: DurationOption[] = DURATIONS

export const CHECK_OFFSET_OPTIONS: DurationOption[] = [
{duration: '0s', displayText: 'None'},
{duration: '5s', displayText: '5 seconds'},
{duration: '15s', displayText: '15 seconds'},
{duration: '1m', displayText: '1 minute'},
{duration: '5m', displayText: '5 minutes'},
{duration: '15m', displayText: '15 minutes'},
{duration: '1h', displayText: '1 hour'},
{duration: '6h', displayText: '6 hours'},
{duration: '12h', displayText: '12 hours'},
{duration: '24h', displayText: '24 hours'},
{duration: '2d', displayText: '2 days'},
{duration: '7d', displayText: '7 days'},
{duration: '30d', displayText: '30 days'},
export const CHECK_OFFSET_OPTIONS = [
'0s',
'5s',
'15s',
'1m',
'5m',
'15m',
'1h',
'6h',
'12h',
'24h',
'2d',
'7d',
'30d',
]

export const MONITORING_BUCKET = '_monitoring'
Expand Down
15 changes: 15 additions & 0 deletions ui/src/alerting/utils/checkValidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Threshold} from 'src/types'

export const checkThresholdsValid = (thresholds: Threshold[]) => {
thresholds.forEach(t => {
if (t.type === 'greater' && isNaN(t.value)) {
throw new Error('Threshold must have defined value')
}
if (t.type === 'lesser' && isNaN(t.value)) {
throw new Error('Threshold must have defined value')
}
if (t.type === 'range' && (isNaN(t.min) || isNaN(t.min))) {
throw new Error('Threshold must have defined min and max values')
}
})
}
4 changes: 2 additions & 2 deletions ui/src/dashboards/utils/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {CustomTimeRange, TimeRange, DurationTimeRange} from 'src/types/queries'

import {SELECTABLE_TIME_RANGES} from 'src/shared/constants/timeRanges'
import {isDateParseable} from 'src/variables/utils/getTimeRangeVars'
import {isDurationParseable} from 'src/shared/utils/duration'
import {isDurationWithNowParseable} from 'src/shared/utils/duration'

interface InputTimeRange {
seconds?: number
Expand Down Expand Up @@ -47,7 +47,7 @@ export const validateAndTypeRange = (timeRange: {
} as CustomTimeRange
}

if (isDurationParseable(lower)) {
if (isDurationWithNowParseable(lower)) {
const selectableTimeRange = SELECTABLE_TIME_RANGES.find(
r => r.lower === lower
)
Expand Down
Loading