Add alerting cards (#14429)
* bump to client 0.5.3 * export Query type from client and create alerting mock * Bump client to 0.5.5 * Merge all status enums to one type * Fix threshold visualization types * Add extra underscore to notification rules * Use check view type from client * Add description field to checks, notificationrules and endpoints * Add init check cards * Add check card actions * Correct component name * Add check card context and toggle * Add id to notification rule base * Add notification rule cards * add checks reducer tests * user immer for checks reducer * add tests and immer to notification rule reducerpull/14433/head
parent
da4615ea17
commit
613aef698f
|
@ -6497,12 +6497,7 @@ components:
|
|||
description: An optional description of the task.
|
||||
type: string
|
||||
status:
|
||||
description: The current status of the task. When updated to 'inactive', cancels all queued jobs of this task.
|
||||
default: active
|
||||
type: string
|
||||
enum:
|
||||
- active
|
||||
- inactive
|
||||
$ref: "#/components/schemas/TaskStatusType"
|
||||
labels:
|
||||
$ref: "#/components/schemas/Labels"
|
||||
authorizationID:
|
||||
|
@ -6557,6 +6552,9 @@ components:
|
|||
labels:
|
||||
$ref: "#/components/schemas/Link"
|
||||
required: [id, name, orgID, flux]
|
||||
TaskStatusType:
|
||||
type: string
|
||||
enum: [active, inactive]
|
||||
User:
|
||||
properties:
|
||||
id:
|
||||
|
@ -8684,10 +8682,7 @@ components:
|
|||
query:
|
||||
$ref: "#/components/schemas/DashboardQuery"
|
||||
status:
|
||||
description: The status of the check task.
|
||||
default: active
|
||||
type: string
|
||||
enum: ["active", "inactive"]
|
||||
$ref: "#/components/schemas/TaskStatusType"
|
||||
every:
|
||||
description: Check repetition interval
|
||||
type: string
|
||||
|
@ -8707,6 +8702,9 @@ components:
|
|||
type: string
|
||||
value:
|
||||
type: string
|
||||
description:
|
||||
description: An optional description of the check
|
||||
type: string
|
||||
statusMessageTemplate:
|
||||
description: template that is used to generate and write a status message
|
||||
type: string
|
||||
|
@ -8812,6 +8810,9 @@ components:
|
|||
NotificationRuleBase:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
readOnly: true
|
||||
type: string
|
||||
notifyEndpointID:
|
||||
type: string
|
||||
readOnly: true
|
||||
|
@ -8831,10 +8832,7 @@ components:
|
|||
format: date-time
|
||||
readOnly: true
|
||||
status:
|
||||
description: The status of the notification rule task.
|
||||
default: active
|
||||
type: string
|
||||
enum: ["active", "inactive"]
|
||||
$ref: "#/components/schemas/TaskStatusType"
|
||||
name:
|
||||
description: human-readable name describing the notification rule
|
||||
type: string
|
||||
|
@ -8864,6 +8862,9 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TagRule"
|
||||
description:
|
||||
description: An optional description of the notification rule
|
||||
type: string
|
||||
statusRules:
|
||||
description: list of status rules the notification rule attempts to match
|
||||
type: array
|
||||
|
@ -8970,6 +8971,9 @@ components:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
description:
|
||||
description: An optional description of the notification endpoint
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
|
|
|
@ -157,7 +157,7 @@
|
|||
"dependencies": {
|
||||
"@influxdata/clockface": "0.0.13",
|
||||
"@influxdata/giraffe": "0.16.0",
|
||||
"@influxdata/influx": "0.5.0",
|
||||
"@influxdata/influx": "0.5.5",
|
||||
"@influxdata/influxdb-templates": "0.3.0",
|
||||
"@influxdata/react-custom-scrollbars": "4.3.8",
|
||||
"axios": "^0.19.0",
|
||||
|
|
|
@ -17,28 +17,28 @@ import {Check, GetState} from 'src/types'
|
|||
|
||||
export type Action =
|
||||
| ReturnType<typeof setAllChecks>
|
||||
| ReturnType<typeof setChecksStatus>
|
||||
| ReturnType<typeof setCheck>
|
||||
| ReturnType<typeof setCheckStatus>
|
||||
| ReturnType<typeof removeCheck>
|
||||
| ReturnType<typeof setCurrentCheck>
|
||||
|
||||
const setAllChecks = (status: RemoteDataState, checks?: Check[]) => ({
|
||||
export const setAllChecks = (status: RemoteDataState, checks?: Check[]) => ({
|
||||
type: 'SET_ALL_CHECKS' as 'SET_ALL_CHECKS',
|
||||
payload: {status, checks},
|
||||
})
|
||||
|
||||
const setChecksStatus = (status: RemoteDataState) => ({
|
||||
type: 'SET_CHECKS_STATUS' as 'SET_CHECKS_STATUS',
|
||||
payload: {status},
|
||||
})
|
||||
|
||||
const setCheck = (status: RemoteDataState, check?: Check) => ({
|
||||
export const setCheck = (check: Check) => ({
|
||||
type: 'SET_CHECK' as 'SET_CHECK',
|
||||
payload: {status, check},
|
||||
payload: {check},
|
||||
})
|
||||
|
||||
const setCheckStatus = (status: RemoteDataState) => ({
|
||||
type: 'SET_CHECK_STATUS' as 'SET_CHECK_STATUS',
|
||||
payload: {status},
|
||||
export const removeCheck = (checkID: string) => ({
|
||||
type: 'REMOVE_CHECK' as 'REMOVE_CHECK',
|
||||
payload: {checkID},
|
||||
})
|
||||
|
||||
export const setCurrentCheck = (status: RemoteDataState, check?: Check) => ({
|
||||
type: 'SET_CURRENT_CHECK' as 'SET_CURRENT_CHECK',
|
||||
payload: {status, check},
|
||||
})
|
||||
|
||||
export const getChecks = () => async (
|
||||
|
@ -46,7 +46,7 @@ export const getChecks = () => async (
|
|||
getState: GetState
|
||||
) => {
|
||||
try {
|
||||
dispatch(setChecksStatus(RemoteDataState.Loading))
|
||||
dispatch(setAllChecks(RemoteDataState.Loading))
|
||||
const {
|
||||
orgs: {
|
||||
org: {id: orgID},
|
||||
|
@ -58,23 +58,58 @@ export const getChecks = () => async (
|
|||
dispatch(setAllChecks(RemoteDataState.Done, checks))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(setChecksStatus(RemoteDataState.Error))
|
||||
dispatch(setAllChecks(RemoteDataState.Error))
|
||||
dispatch(notify(copy.getChecksFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const getCheck = (checkID: string) => async (
|
||||
export const getCurrentCheck = (checkID: string) => async (
|
||||
dispatch: Dispatch<Action | NotificationAction>
|
||||
) => {
|
||||
try {
|
||||
dispatch(setCheckStatus(RemoteDataState.Loading))
|
||||
dispatch(setCurrentCheck(RemoteDataState.Loading))
|
||||
|
||||
const check = await client.checks.get(checkID)
|
||||
|
||||
dispatch(setCheck(RemoteDataState.Done, check))
|
||||
dispatch(setCurrentCheck(RemoteDataState.Done, check))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(setCheckStatus(RemoteDataState.Error))
|
||||
dispatch(setCurrentCheck(RemoteDataState.Error))
|
||||
dispatch(notify(copy.getCheckFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const createCheck = (check: Check) => async (
|
||||
dispatch: Dispatch<Action | NotificationAction>
|
||||
) => {
|
||||
try {
|
||||
client.checks.create(check)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.createCheckFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const updateCheck = (check: Partial<Check>) => async (
|
||||
dispatch: Dispatch<Action | NotificationAction>
|
||||
) => {
|
||||
try {
|
||||
const updatedCheck = await client.checks.update(check.id, check)
|
||||
dispatch(setCheck(updatedCheck))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.updateCheckFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteCheck = (checkID: string) => async (
|
||||
dispatch: Dispatch<Action | NotificationAction>
|
||||
) => {
|
||||
try {
|
||||
await client.checks.delete(checkID)
|
||||
dispatch(removeCheck(checkID))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.deleteCheckFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {Dispatch} from 'react'
|
|||
// Constants
|
||||
import * as copy from 'src/shared/copy/notifications'
|
||||
|
||||
//Actions
|
||||
// Actions
|
||||
import {
|
||||
notify,
|
||||
Action as NotificationAction,
|
||||
|
@ -17,34 +17,34 @@ import {NotificationRule, GetState} from 'src/types'
|
|||
|
||||
export type Action =
|
||||
| ReturnType<typeof setAllNotificationRules>
|
||||
| ReturnType<typeof setNotificationRulesStatus>
|
||||
| ReturnType<typeof setNotificationRule>
|
||||
| ReturnType<typeof setNotificationRuleStatus>
|
||||
| ReturnType<typeof setCurrentNotificationRule>
|
||||
| ReturnType<typeof removeNotificationRule>
|
||||
|
||||
const setAllNotificationRules = (
|
||||
export const setAllNotificationRules = (
|
||||
status: RemoteDataState,
|
||||
notificationRules?: NotificationRule[]
|
||||
) => ({
|
||||
type: 'SET_ALL_NOTIFICATIONRULES' as 'SET_ALL_NOTIFICATIONRULES',
|
||||
type: 'SET_ALL_NOTIFICATION_RULES' as 'SET_ALL_NOTIFICATION_RULES',
|
||||
payload: {status, notificationRules},
|
||||
})
|
||||
|
||||
const setNotificationRulesStatus = (status: RemoteDataState) => ({
|
||||
type: 'SET_NOTIFICATIONRULES_STATUS' as 'SET_NOTIFICATIONRULES_STATUS',
|
||||
payload: {status},
|
||||
export const setNotificationRule = (notificationRule: NotificationRule) => ({
|
||||
type: 'SET_NOTIFICATION_RULE' as 'SET_NOTIFICATION_RULE',
|
||||
payload: {notificationRule},
|
||||
})
|
||||
|
||||
const setNotificationRule = (
|
||||
export const setCurrentNotificationRule = (
|
||||
status: RemoteDataState,
|
||||
notificationRule?: NotificationRule
|
||||
) => ({
|
||||
type: 'SET_NOTIFICATIONRULE' as 'SET_NOTIFICATIONRULE',
|
||||
type: 'SET_CURRENT_NOTIFICATION_RULE' as 'SET_CURRENT_NOTIFICATION_RULE',
|
||||
payload: {status, notificationRule},
|
||||
})
|
||||
|
||||
const setNotificationRuleStatus = (status: RemoteDataState) => ({
|
||||
type: 'SET_NOTIFICATIONRULE_STATUS' as 'SET_NOTIFICATIONRULE_STATUS',
|
||||
payload: {status},
|
||||
export const removeNotificationRule = (notificationRuleID: string) => ({
|
||||
type: 'REMOVE_NOTIFICATION_RULE' as 'REMOVE_NOTIFICATION_RULE',
|
||||
payload: {notificationRuleID},
|
||||
})
|
||||
|
||||
export const getNotificationRules = () => async (
|
||||
|
@ -52,37 +52,77 @@ export const getNotificationRules = () => async (
|
|||
getState: GetState
|
||||
) => {
|
||||
try {
|
||||
dispatch(setNotificationRulesStatus(RemoteDataState.Loading))
|
||||
dispatch(setAllNotificationRules(RemoteDataState.Loading))
|
||||
const {
|
||||
orgs: {
|
||||
org: {id: orgID},
|
||||
},
|
||||
} = getState()
|
||||
|
||||
const notificationRules = await client.notificationRules.getAll(orgID)
|
||||
const notificationRules = (await client.notificationRules.getAll(
|
||||
orgID
|
||||
)) as NotificationRule[]
|
||||
|
||||
dispatch(setAllNotificationRules(RemoteDataState.Done, notificationRules))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(setNotificationRulesStatus(RemoteDataState.Error))
|
||||
dispatch(setAllNotificationRules(RemoteDataState.Error))
|
||||
dispatch(notify(copy.getNotificationRulesFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const getNotificationRule = (notificationRuleID: string) => async (
|
||||
dispatch: Dispatch<Action | NotificationAction>
|
||||
) => {
|
||||
export const getCurrentNotificationRule = (
|
||||
notificationRuleID: string
|
||||
) => async (dispatch: Dispatch<Action | NotificationAction>) => {
|
||||
try {
|
||||
dispatch(setNotificationRuleStatus(RemoteDataState.Loading))
|
||||
dispatch(setCurrentNotificationRule(RemoteDataState.Loading))
|
||||
|
||||
const notificationRule = await client.notificationRules.get(
|
||||
const notificationRule = (await client.notificationRules.get(
|
||||
notificationRuleID
|
||||
)
|
||||
)) as NotificationRule
|
||||
|
||||
dispatch(setNotificationRule(RemoteDataState.Done, notificationRule))
|
||||
dispatch(setCurrentNotificationRule(RemoteDataState.Done, notificationRule))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(setNotificationRuleStatus(RemoteDataState.Error))
|
||||
dispatch(setCurrentNotificationRule(RemoteDataState.Error))
|
||||
dispatch(notify(copy.getNotificationRuleFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const createNotificationRule = (
|
||||
notificationRule: NotificationRule
|
||||
) => async (dispatch: Dispatch<Action | NotificationAction>) => {
|
||||
try {
|
||||
client.notificationRules.create(notificationRule)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.createNotificationRuleFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const updateNotificationRule = (
|
||||
notificationRule: Partial<NotificationRule>
|
||||
) => async (dispatch: Dispatch<Action | NotificationAction>) => {
|
||||
try {
|
||||
const updatedNotificationRule = (await client.notificationRules.update(
|
||||
notificationRule.id,
|
||||
notificationRule
|
||||
)) as NotificationRule
|
||||
dispatch(setNotificationRule(updatedNotificationRule))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.updateNotificationRuleFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteNotificationRule = (notificationRuleID: string) => async (
|
||||
dispatch: Dispatch<Action | NotificationAction>
|
||||
) => {
|
||||
try {
|
||||
await client.notificationRules.delete(notificationRuleID)
|
||||
dispatch(removeNotificationRule(notificationRuleID))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.deleteNotificationRuleFailed(e.message)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
|
||||
// Components
|
||||
import {ResourceList} from 'src/clockface'
|
||||
import {SlideToggle, ComponentSize} from '@influxdata/clockface'
|
||||
import CheckCardContext from 'src/alerting/components/CheckCardContext'
|
||||
|
||||
// Constants
|
||||
import {DEFAULT_CHECK_NAME} from 'src/alerting/constants'
|
||||
|
||||
// Actions
|
||||
import {updateCheck, deleteCheck} from 'src/alerting/actions/checks'
|
||||
|
||||
// Types
|
||||
import {Check, CheckBase} from 'src/types'
|
||||
|
||||
interface DispatchProps {
|
||||
updateCheck: typeof updateCheck
|
||||
deleteCheck: typeof deleteCheck
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
check: Check
|
||||
}
|
||||
|
||||
type Props = OwnProps & DispatchProps & WithRouterProps
|
||||
|
||||
const CheckCard: FunctionComponent<Props> = ({
|
||||
check,
|
||||
updateCheck,
|
||||
deleteCheck,
|
||||
router,
|
||||
params: {orgID},
|
||||
}) => {
|
||||
const onUpdateName = (name: string) => {
|
||||
updateCheck({id: check.id, name})
|
||||
}
|
||||
|
||||
const onClickName = () => {
|
||||
router.push(`/orgs/${orgID}/checks/${check.id}`)
|
||||
}
|
||||
|
||||
const onDelete = () => {
|
||||
deleteCheck(check.id)
|
||||
}
|
||||
|
||||
const onExport = () => {}
|
||||
|
||||
const onClone = () => {}
|
||||
|
||||
const onToggle = () => {
|
||||
const status =
|
||||
check.status == CheckBase.StatusEnum.Active
|
||||
? CheckBase.StatusEnum.Inactive
|
||||
: CheckBase.StatusEnum.Active
|
||||
updateCheck({id: check.id, status})
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceList.Card
|
||||
key={`check-id--${check.id}`}
|
||||
testID="check-card"
|
||||
name={() => (
|
||||
<ResourceList.EditableName
|
||||
onUpdate={onUpdateName}
|
||||
onClick={onClickName}
|
||||
name={check.name}
|
||||
noNameString={DEFAULT_CHECK_NAME}
|
||||
parentTestID="check-card--name"
|
||||
buttonTestID="check-card--name-button"
|
||||
inputTestID="check-card--input"
|
||||
/>
|
||||
)}
|
||||
toggle={() => (
|
||||
<SlideToggle
|
||||
active={check.status == CheckBase.StatusEnum.Active}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
onChange={onToggle}
|
||||
testID="check-card--slide-toggle"
|
||||
/>
|
||||
)}
|
||||
// description
|
||||
// labels
|
||||
disabled={check.status == CheckBase.StatusEnum.Inactive}
|
||||
contextMenu={() => (
|
||||
<CheckCardContext
|
||||
onDelete={onDelete}
|
||||
onExport={onExport}
|
||||
onClone={onClone}
|
||||
/>
|
||||
)}
|
||||
updatedAt={check.updatedAt.toString()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
updateCheck: updateCheck,
|
||||
deleteCheck: deleteCheck,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, {}>(
|
||||
null,
|
||||
mdtp
|
||||
)(withRouter(CheckCard))
|
|
@ -0,0 +1,42 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
// Components
|
||||
import {Context, IconFont} from 'src/clockface'
|
||||
import {ComponentColor} from '@influxdata/clockface'
|
||||
|
||||
interface Props {
|
||||
onDelete: () => void
|
||||
onClone: () => void
|
||||
onExport: () => void
|
||||
}
|
||||
|
||||
const CheckCardContext: FunctionComponent<Props> = ({
|
||||
onDelete,
|
||||
onClone,
|
||||
onExport,
|
||||
}) => {
|
||||
return (
|
||||
<Context>
|
||||
<Context.Menu icon={IconFont.CogThick}>
|
||||
<Context.Item label="Export" action={onExport} />
|
||||
</Context.Menu>
|
||||
<Context.Menu icon={IconFont.Duplicate} color={ComponentColor.Secondary}>
|
||||
<Context.Item label="Clone" action={onClone} />
|
||||
</Context.Menu>
|
||||
<Context.Menu
|
||||
icon={IconFont.Trash}
|
||||
color={ComponentColor.Danger}
|
||||
testID="context-delete-menu"
|
||||
>
|
||||
<Context.Item
|
||||
label="Delete"
|
||||
action={onDelete}
|
||||
testID="context-delete-task"
|
||||
/>
|
||||
</Context.Menu>
|
||||
</Context>
|
||||
)
|
||||
}
|
||||
|
||||
export default CheckCardContext
|
|
@ -1,5 +1,10 @@
|
|||
//Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
//Components
|
||||
import CheckCard from 'src/alerting/components/CheckCard'
|
||||
import {ResourceList} from 'src/clockface'
|
||||
|
||||
// Types
|
||||
import {Check} from 'src/types'
|
||||
import {EmptyState, ComponentSize} from '@influxdata/clockface'
|
||||
|
@ -9,9 +14,20 @@ interface Props {
|
|||
}
|
||||
|
||||
const CheckCards: FunctionComponent<Props> = ({checks}) => {
|
||||
if (checks && checks.length) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ResourceList>
|
||||
<ResourceList.Body emptyState={<EmptyChecksList />}>
|
||||
{checks.map(check => (
|
||||
<CheckCard key={check.id} check={check} />
|
||||
))}
|
||||
</ResourceList.Body>
|
||||
</ResourceList>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const EmptyChecksList: FunctionComponent = () => {
|
||||
return (
|
||||
<EmptyState size={ComponentSize.ExtraSmall}>
|
||||
<EmptyState.Text
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
|
||||
// Components
|
||||
import {ResourceList} from 'src/clockface'
|
||||
import {SlideToggle, ComponentSize} from '@influxdata/clockface'
|
||||
import NotificationRuleCardContext from 'src/alerting/components/NotificationRuleCardContext'
|
||||
|
||||
// Constants
|
||||
import {DEFAULT_NOTIFICATION_RULE_NAME} from 'src/alerting/constants'
|
||||
|
||||
// Actions
|
||||
import {
|
||||
updateNotificationRule,
|
||||
deleteNotificationRule,
|
||||
} from 'src/alerting/actions/notificationRules'
|
||||
|
||||
// Types
|
||||
import {NotificationRule, NotificationRuleBase} from 'src/types'
|
||||
|
||||
interface DispatchProps {
|
||||
updateNotificationRule: typeof updateNotificationRule
|
||||
deleteNotificationRule: typeof deleteNotificationRule
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
notificationRule: NotificationRule
|
||||
}
|
||||
|
||||
type Props = OwnProps & DispatchProps & WithRouterProps
|
||||
|
||||
const NotificationRuleCard: FunctionComponent<Props> = ({
|
||||
notificationRule,
|
||||
updateNotificationRule,
|
||||
deleteNotificationRule,
|
||||
router,
|
||||
params: {orgID},
|
||||
}) => {
|
||||
const onUpdateName = (name: string) => {
|
||||
updateNotificationRule({id: notificationRule.id, name})
|
||||
}
|
||||
|
||||
const onClickName = () => {
|
||||
router.push(`/orgs/${orgID}/notificationRules/${notificationRule.id}`)
|
||||
}
|
||||
|
||||
const onDelete = () => {
|
||||
deleteNotificationRule(notificationRule.id)
|
||||
}
|
||||
|
||||
const onExport = () => {}
|
||||
|
||||
const onClone = () => {}
|
||||
|
||||
const onToggle = () => {
|
||||
const status =
|
||||
notificationRule.status == NotificationRuleBase.StatusEnum.Active
|
||||
? NotificationRuleBase.StatusEnum.Inactive
|
||||
: NotificationRuleBase.StatusEnum.Active
|
||||
updateNotificationRule({id: notificationRule.id, status})
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceList.Card
|
||||
key={`notificationRule-id--${notificationRule.id}`}
|
||||
testID="notificationRule-card"
|
||||
name={() => (
|
||||
<ResourceList.EditableName
|
||||
onUpdate={onUpdateName}
|
||||
onClick={onClickName}
|
||||
name={notificationRule.name}
|
||||
noNameString={DEFAULT_NOTIFICATION_RULE_NAME}
|
||||
parentTestID="notificationRule-card--name"
|
||||
buttonTestID="notificationRule-card--name-button"
|
||||
inputTestID="notificationRule-card--input"
|
||||
/>
|
||||
)}
|
||||
toggle={() => (
|
||||
<SlideToggle
|
||||
active={
|
||||
notificationRule.status == NotificationRuleBase.StatusEnum.Active
|
||||
}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
onChange={onToggle}
|
||||
testID="notificationRule-card--slide-toggle"
|
||||
/>
|
||||
)}
|
||||
// description
|
||||
// labels
|
||||
disabled={
|
||||
notificationRule.status == NotificationRuleBase.StatusEnum.Inactive
|
||||
}
|
||||
contextMenu={() => (
|
||||
<NotificationRuleCardContext
|
||||
onDelete={onDelete}
|
||||
onExport={onExport}
|
||||
onClone={onClone}
|
||||
/>
|
||||
)}
|
||||
updatedAt={notificationRule.updatedAt.toString()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
updateNotificationRule: updateNotificationRule,
|
||||
deleteNotificationRule: deleteNotificationRule,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, {}>(
|
||||
null,
|
||||
mdtp
|
||||
)(withRouter(NotificationRuleCard))
|
|
@ -0,0 +1,42 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
// Components
|
||||
import {Context, IconFont} from 'src/clockface'
|
||||
import {ComponentColor} from '@influxdata/clockface'
|
||||
|
||||
interface Props {
|
||||
onDelete: () => void
|
||||
onClone: () => void
|
||||
onExport: () => void
|
||||
}
|
||||
|
||||
const NotificationRuleCardContext: FunctionComponent<Props> = ({
|
||||
onDelete,
|
||||
onClone,
|
||||
onExport,
|
||||
}) => {
|
||||
return (
|
||||
<Context>
|
||||
<Context.Menu icon={IconFont.CogThick}>
|
||||
<Context.Item label="Export" action={onExport} />
|
||||
</Context.Menu>
|
||||
<Context.Menu icon={IconFont.Duplicate} color={ComponentColor.Secondary}>
|
||||
<Context.Item label="Clone" action={onClone} />
|
||||
</Context.Menu>
|
||||
<Context.Menu
|
||||
icon={IconFont.Trash}
|
||||
color={ComponentColor.Danger}
|
||||
testID="context-delete-menu"
|
||||
>
|
||||
<Context.Item
|
||||
label="Delete"
|
||||
action={onDelete}
|
||||
testID="context-delete-task"
|
||||
/>
|
||||
</Context.Menu>
|
||||
</Context>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationRuleCardContext
|
|
@ -0,0 +1,43 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
// Components
|
||||
import NotificationRuleCard from 'src/alerting/components/NotificationRuleCard'
|
||||
import {ResourceList} from 'src/clockface'
|
||||
|
||||
// Types
|
||||
import {NotificationRule} from 'src/types'
|
||||
import {EmptyState, ComponentSize} from '@influxdata/clockface'
|
||||
|
||||
interface Props {
|
||||
notificationRules: NotificationRule[]
|
||||
}
|
||||
|
||||
const NotificationRuleCards: FunctionComponent<Props> = ({
|
||||
notificationRules,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<ResourceList>
|
||||
<ResourceList.Body emptyState={<EmptyNotificationRulesList />}>
|
||||
{notificationRules.map(nr => (
|
||||
<NotificationRuleCard key={nr.id} notificationRule={nr} />
|
||||
))}
|
||||
</ResourceList.Body>
|
||||
</ResourceList>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const EmptyNotificationRulesList: FunctionComponent = () => {
|
||||
return (
|
||||
<EmptyState size={ComponentSize.ExtraSmall}>
|
||||
<EmptyState.Text
|
||||
text="Looks like you don’t have any Notification Rules, why not create one?"
|
||||
highlightWords={['Notification Rules']}
|
||||
/>
|
||||
</EmptyState>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationRuleCards
|
|
@ -4,6 +4,7 @@ import {connect} from 'react-redux'
|
|||
|
||||
// Types
|
||||
import {NotificationRule, AppState} from 'src/types'
|
||||
import NotificationRuleCards from 'src/alerting/components/NotificationRuleCards'
|
||||
|
||||
interface StateProps {
|
||||
notificationRules: NotificationRule[]
|
||||
|
@ -11,8 +12,15 @@ interface StateProps {
|
|||
|
||||
type Props = StateProps
|
||||
|
||||
const NotificationRulesColumn: FunctionComponent<Props> = () => {
|
||||
return <>NotificationRules</>
|
||||
const NotificationRulesColumn: FunctionComponent<Props> = ({
|
||||
notificationRules,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
NotificationRules
|
||||
<NotificationRuleCards notificationRules={notificationRules} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const mstp = (state: AppState) => {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import {
|
||||
Check,
|
||||
CheckType,
|
||||
DashboardQuery,
|
||||
QueryEditMode,
|
||||
CheckBase,
|
||||
NotificationRule,
|
||||
NotificationRuleBase,
|
||||
NotificationRuleType,
|
||||
CheckStatusLevel,
|
||||
ThresholdType,
|
||||
} from 'src/types'
|
||||
|
||||
export const DEFAULT_CHECK_NAME = 'Name this check'
|
||||
export const DEFAULT_NOTIFICATION_RULE_NAME = 'Name this notification rule'
|
||||
|
||||
export const query: DashboardQuery = {
|
||||
text: 'this is query',
|
||||
editMode: QueryEditMode.Advanced,
|
||||
builderConfig: null,
|
||||
name: 'great q',
|
||||
}
|
||||
|
||||
export const check1: Check = {
|
||||
id: '1',
|
||||
type: CheckType.Threshold,
|
||||
name: 'Amoozing check',
|
||||
orgID: 'lala',
|
||||
createdAt: new Date('December 17, 2019'),
|
||||
updatedAt: new Date('April 17, 2019'),
|
||||
query: query,
|
||||
status: CheckBase.StatusEnum.Active,
|
||||
every: '2d',
|
||||
offset: '1m',
|
||||
tags: [{key: 'a', value: 'b'}],
|
||||
statusMessageTemplate: 'this is a great message template',
|
||||
thresholds: [
|
||||
{
|
||||
level: CheckStatusLevel.WARN,
|
||||
allValues: false,
|
||||
type: ThresholdType.Greater,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const check2: Check = {
|
||||
id: '2',
|
||||
type: CheckType.Threshold,
|
||||
name: 'Another check',
|
||||
orgID: 'lala',
|
||||
createdAt: new Date('December 17, 2019'),
|
||||
updatedAt: new Date('April 17, 2019'),
|
||||
query: query,
|
||||
status: CheckBase.StatusEnum.Active,
|
||||
every: '2d',
|
||||
offset: '1m',
|
||||
tags: [{key: 'a', value: 'b'}],
|
||||
statusMessageTemplate: 'this is a great message template',
|
||||
thresholds: [
|
||||
{
|
||||
level: CheckStatusLevel.WARN,
|
||||
allValues: false,
|
||||
type: ThresholdType.Greater,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const checks: Array<Check> = [check1, check2]
|
||||
|
||||
export const notificationRule: NotificationRule = {
|
||||
id: '3',
|
||||
notifyEndpointID: '2',
|
||||
orgID: 'lala',
|
||||
createdAt: new Date('December 17, 2019'),
|
||||
updatedAt: new Date('April 17, 2019'),
|
||||
status: NotificationRuleBase.StatusEnum.Active,
|
||||
name: 'amazing notification rule',
|
||||
type: NotificationRuleType.Slack,
|
||||
every: '2d',
|
||||
offset: '5m',
|
||||
limitEvery: 1,
|
||||
limit: 5,
|
||||
tagRules: [],
|
||||
statusRules: [],
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import checksReducer, {defaultChecksState} from 'src/alerting/reducers/checks'
|
||||
import {
|
||||
setAllChecks,
|
||||
setCheck,
|
||||
setCurrentCheck,
|
||||
removeCheck,
|
||||
} from 'src/alerting/actions/checks'
|
||||
import {RemoteDataState} from 'src/types'
|
||||
import {check1, check2} from 'src/alerting/constants'
|
||||
|
||||
describe('checksReducer', () => {
|
||||
describe('setAllChecks', () => {
|
||||
it('sets list and status properties of state.', () => {
|
||||
const initialState = defaultChecksState
|
||||
|
||||
const actual = checksReducer(
|
||||
initialState,
|
||||
setAllChecks(RemoteDataState.Done, [check1, check2])
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultChecksState,
|
||||
list: [check1, check2],
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('setCheck', () => {
|
||||
it('adds check to list if it is new', () => {
|
||||
const initialState = defaultChecksState
|
||||
|
||||
const actual = checksReducer(initialState, setCheck(check2))
|
||||
|
||||
const expected = {
|
||||
...defaultChecksState,
|
||||
list: [check2],
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
it('updates check in list if it exists', () => {
|
||||
let initialState = defaultChecksState
|
||||
initialState.list = [check1]
|
||||
const actual = checksReducer(
|
||||
initialState,
|
||||
setCheck({...check1, name: check2.name})
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultChecksState,
|
||||
list: [{...check1, name: check2.name}],
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('removeCheck', () => {
|
||||
it('removes check from list', () => {
|
||||
const initialState = defaultChecksState
|
||||
initialState.list = [check1]
|
||||
const actual = checksReducer(initialState, removeCheck(check1.id))
|
||||
|
||||
const expected = {
|
||||
...defaultChecksState,
|
||||
list: [],
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('setCurrentCheck', () => {
|
||||
it('sets current check and status.', () => {
|
||||
const initialState = defaultChecksState
|
||||
|
||||
const actual = checksReducer(
|
||||
initialState,
|
||||
setCurrentCheck(RemoteDataState.Done, check1)
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultChecksState,
|
||||
current: {status: RemoteDataState.Done, check: check1},
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,3 +1,6 @@
|
|||
// Libraries
|
||||
import {produce} from 'immer'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState, Check} from 'src/types'
|
||||
import {Action} from 'src/alerting/actions/checks'
|
||||
|
@ -17,31 +20,38 @@ export const defaultChecksState: ChecksState = {
|
|||
export default (
|
||||
state: ChecksState = defaultChecksState,
|
||||
action: Action
|
||||
): ChecksState => {
|
||||
switch (action.type) {
|
||||
case 'SET_CHECKS_STATUS':
|
||||
return {
|
||||
...state,
|
||||
status: action.payload.status,
|
||||
}
|
||||
case 'SET_ALL_CHECKS':
|
||||
return {
|
||||
...state,
|
||||
list: action.payload.checks,
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
case 'SET_CHECK_STATUS':
|
||||
return {
|
||||
...state,
|
||||
current: {...state.current, status: action.payload.status},
|
||||
}
|
||||
case 'SET_CHECK':
|
||||
return {
|
||||
...state,
|
||||
current: {status: action.payload.status, check: action.payload.check},
|
||||
}
|
||||
): ChecksState =>
|
||||
produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case 'SET_ALL_CHECKS':
|
||||
const {status, checks} = action.payload
|
||||
draftState.status = status
|
||||
if (checks) {
|
||||
draftState.list = checks
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
case 'SET_CHECK':
|
||||
const newCheck = action.payload.check
|
||||
const checkIndex = state.list.findIndex(c => c.id == newCheck.id)
|
||||
|
||||
if (checkIndex == -1) {
|
||||
draftState.list.push(newCheck)
|
||||
} else {
|
||||
draftState.list[checkIndex] = newCheck
|
||||
}
|
||||
return
|
||||
|
||||
case 'REMOVE_CHECK':
|
||||
const {checkID} = action.payload
|
||||
draftState.list = draftState.list.filter(c => c.id != checkID)
|
||||
return
|
||||
|
||||
case 'SET_CURRENT_CHECK':
|
||||
draftState.current.status = action.payload.status
|
||||
if (action.payload.check) {
|
||||
draftState.current.check = action.payload.check
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// Libraries
|
||||
import {produce} from 'immer'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState, NotificationRule} from 'src/types'
|
||||
import {Action} from 'src/alerting/actions/notificationRules'
|
||||
|
@ -8,43 +11,51 @@ export interface NotificationRulesState {
|
|||
current: {status: RemoteDataState; notificationRule: NotificationRule}
|
||||
}
|
||||
|
||||
export const defaultNotificationRuleState: NotificationRulesState = {
|
||||
export const defaultNotificationRulesState: NotificationRulesState = {
|
||||
status: RemoteDataState.NotStarted,
|
||||
list: [],
|
||||
current: {status: RemoteDataState.NotStarted, notificationRule: null},
|
||||
}
|
||||
|
||||
export default (
|
||||
state: NotificationRulesState = defaultNotificationRuleState,
|
||||
state: NotificationRulesState = defaultNotificationRulesState,
|
||||
action: Action
|
||||
): NotificationRulesState => {
|
||||
switch (action.type) {
|
||||
case 'SET_NOTIFICATIONRULES_STATUS':
|
||||
return {
|
||||
...state,
|
||||
status: action.payload.status,
|
||||
}
|
||||
case 'SET_ALL_NOTIFICATIONRULES':
|
||||
return {
|
||||
...state,
|
||||
list: action.payload.notificationRules,
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
case 'SET_NOTIFICATIONRULE_STATUS':
|
||||
return {
|
||||
...state,
|
||||
current: {...state.current, status: action.payload.status},
|
||||
}
|
||||
case 'SET_NOTIFICATIONRULE':
|
||||
return {
|
||||
...state,
|
||||
current: {
|
||||
status: action.payload.status,
|
||||
notificationRule: action.payload.notificationRule,
|
||||
},
|
||||
}
|
||||
): NotificationRulesState =>
|
||||
produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case 'SET_ALL_NOTIFICATION_RULES':
|
||||
const {status, notificationRules} = action.payload
|
||||
draftState.status = status
|
||||
if (notificationRules) {
|
||||
draftState.list = notificationRules
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
case 'SET_NOTIFICATION_RULE':
|
||||
const newNotificationRule = action.payload.notificationRule
|
||||
const notificationRuleIndex = state.list.findIndex(
|
||||
nr => nr.id == newNotificationRule.id
|
||||
)
|
||||
|
||||
if (notificationRuleIndex == -1) {
|
||||
draftState.list.push(newNotificationRule)
|
||||
} else {
|
||||
draftState.list[notificationRuleIndex] = newNotificationRule
|
||||
}
|
||||
return
|
||||
|
||||
case 'REMOVE_NOTIFICATION_RULE':
|
||||
const {notificationRuleID} = action.payload
|
||||
draftState.list = draftState.list.filter(
|
||||
nr => nr.id != notificationRuleID
|
||||
)
|
||||
return
|
||||
|
||||
case 'SET_CURRENT_NOTIFICATION_RULE':
|
||||
draftState.current.status = action.payload.status
|
||||
if (action.payload.notificationRule) {
|
||||
draftState.current.notificationRule = action.payload.notificationRule
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import notificationRulesReducer, {
|
||||
defaultNotificationRulesState,
|
||||
} from 'src/alerting/reducers/notificationRules'
|
||||
import {
|
||||
setAllNotificationRules,
|
||||
setNotificationRule,
|
||||
setCurrentNotificationRule,
|
||||
removeNotificationRule,
|
||||
} from 'src/alerting/actions/notificationRules'
|
||||
import {RemoteDataState} from 'src/types'
|
||||
import {notificationRule} from 'src/alerting/constants'
|
||||
|
||||
describe('notificationRulesReducer', () => {
|
||||
describe('setAllNotificationRules', () => {
|
||||
it('sets list and status properties of state.', () => {
|
||||
const initialState = defaultNotificationRulesState
|
||||
|
||||
const actual = notificationRulesReducer(
|
||||
initialState,
|
||||
setAllNotificationRules(RemoteDataState.Done, [notificationRule])
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultNotificationRulesState,
|
||||
list: [notificationRule],
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('setNotificationRule', () => {
|
||||
it('adds notificationRule to list if it is new', () => {
|
||||
const initialState = defaultNotificationRulesState
|
||||
|
||||
const actual = notificationRulesReducer(
|
||||
initialState,
|
||||
setNotificationRule(notificationRule)
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultNotificationRulesState,
|
||||
list: [notificationRule],
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
it('updates notificationRule in list if it exists', () => {
|
||||
let initialState = defaultNotificationRulesState
|
||||
initialState.list = [notificationRule]
|
||||
|
||||
const actual = notificationRulesReducer(
|
||||
initialState,
|
||||
setNotificationRule({
|
||||
...notificationRule,
|
||||
name: 'moo',
|
||||
})
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultNotificationRulesState,
|
||||
list: [{...notificationRule, name: 'moo'}],
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('removeNotificationRule', () => {
|
||||
it('removes notificationRule from list', () => {
|
||||
const initialState = defaultNotificationRulesState
|
||||
initialState.list = [notificationRule]
|
||||
const actual = notificationRulesReducer(
|
||||
initialState,
|
||||
removeNotificationRule(notificationRule.id)
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultNotificationRulesState,
|
||||
list: [],
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('setCurrentNotificationRule', () => {
|
||||
it('sets current notificationRule and status.', () => {
|
||||
const initialState = defaultNotificationRulesState
|
||||
|
||||
const actual = notificationRulesReducer(
|
||||
initialState,
|
||||
setCurrentNotificationRule(RemoteDataState.Done, notificationRule)
|
||||
)
|
||||
|
||||
const expected = {
|
||||
...defaultNotificationRulesState,
|
||||
current: {
|
||||
status: RemoteDataState.Done,
|
||||
notificationRule: notificationRule,
|
||||
},
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -29,7 +29,7 @@ interface Props {
|
|||
onAddScraper: () => void
|
||||
}
|
||||
|
||||
export default class MemberContextMenu extends PureComponent<Props> {
|
||||
export default class BucketContextMenu extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {
|
||||
bucket,
|
||||
|
|
|
@ -17,16 +17,23 @@ import {VIS_THEME} from 'src/shared/constants'
|
|||
import {INVALID_DATA_COPY} from 'src/shared/copy/cell'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState, CheckView, TimeZone, ThresholdConfig} from 'src/types'
|
||||
import {
|
||||
RemoteDataState,
|
||||
CheckView,
|
||||
TimeZone,
|
||||
CheckThreshold,
|
||||
ThresholdType,
|
||||
CheckStatusLevel,
|
||||
} from 'src/types'
|
||||
|
||||
const X_COLUMN = '_time'
|
||||
const Y_COLUMN = '_value'
|
||||
|
||||
const THRESHOLDS: ThresholdConfig[] = [
|
||||
const THRESHOLDS: CheckThreshold[] = [
|
||||
{
|
||||
type: 'less',
|
||||
type: ThresholdType.Greater,
|
||||
allValues: false,
|
||||
level: 'UNKNOWN',
|
||||
level: CheckStatusLevel.UNKNOWN,
|
||||
value: 20,
|
||||
},
|
||||
]
|
||||
|
@ -46,12 +53,11 @@ const CheckPlot: FunctionComponent<Props> = ({
|
|||
loading,
|
||||
children,
|
||||
timeZone,
|
||||
viewProperties: {yDomain: storedYDomain},
|
||||
}) => {
|
||||
const [thresholds, setThresholds] = useState(THRESHOLDS)
|
||||
|
||||
const [yDomain, onSetYDomain, onResetYDomain] = useVisDomainSettings(
|
||||
storedYDomain,
|
||||
[0, 100],
|
||||
table.getColumn(Y_COLUMN, 'number')
|
||||
)
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ import {isInDomain, clamp} from 'src/shared/utils/vis'
|
|||
import {DragEvent} from 'src/shared/utils/useDragBehavior'
|
||||
|
||||
// Types
|
||||
import {GreaterThresholdConfig} from 'src/types'
|
||||
import {GreaterThreshold} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
yScale: Scale<number, number>
|
||||
yDomain: number[]
|
||||
threshold: GreaterThresholdConfig
|
||||
threshold: GreaterThreshold
|
||||
onChangePos: (e: DragEvent) => void
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ import {clamp, isInDomain} from 'src/shared/utils/vis'
|
|||
import {DragEvent} from 'src/shared/utils/useDragBehavior'
|
||||
|
||||
// Types
|
||||
import {LessThresholdConfig} from 'src/types'
|
||||
import {LesserThreshold} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
yScale: Scale<number, number>
|
||||
yDomain: number[]
|
||||
threshold: LessThresholdConfig
|
||||
threshold: LesserThreshold
|
||||
onChangePos: (e: DragEvent) => void
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ import {isInDomain, clamp} from 'src/shared/utils/vis'
|
|||
import {DragEvent} from 'src/shared/utils/useDragBehavior'
|
||||
|
||||
// Types
|
||||
import {RangeThresholdConfig} from 'src/types'
|
||||
import {RangeThreshold} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
yScale: Scale<number, number>
|
||||
yDomain: number[]
|
||||
threshold: RangeThresholdConfig
|
||||
threshold: RangeThreshold
|
||||
onChangeMaxPos: (e: DragEvent) => void
|
||||
onChangeMinPos: (e: DragEvent) => void
|
||||
}
|
||||
|
@ -24,33 +24,33 @@ interface Props {
|
|||
const RangeThresholdMarkers: FunctionComponent<Props> = ({
|
||||
yScale,
|
||||
yDomain,
|
||||
threshold: {level, within, minValue, maxValue},
|
||||
threshold: {level, within, min, max},
|
||||
onChangeMinPos,
|
||||
onChangeMaxPos,
|
||||
}) => {
|
||||
const minY = yScale(clamp(minValue, yDomain))
|
||||
const maxY = yScale(clamp(maxValue, yDomain))
|
||||
const minY = yScale(clamp(min, yDomain))
|
||||
const maxY = yScale(clamp(max, yDomain))
|
||||
|
||||
return (
|
||||
<>
|
||||
{isInDomain(minValue, yDomain) && (
|
||||
{isInDomain(min, yDomain) && (
|
||||
<ThresholdMarker level={level} y={minY} onDrag={onChangeMinPos} />
|
||||
)}
|
||||
{isInDomain(maxValue, yDomain) && (
|
||||
{isInDomain(max, yDomain) && (
|
||||
<ThresholdMarker level={level} y={maxY} onDrag={onChangeMaxPos} />
|
||||
)}
|
||||
{within ? (
|
||||
<ThresholdMarkerArea level={level} top={maxY} height={minY - maxY} />
|
||||
) : (
|
||||
<>
|
||||
{maxValue <= yDomain[1] && (
|
||||
{max <= yDomain[1] && (
|
||||
<ThresholdMarkerArea
|
||||
level={level}
|
||||
top={yScale(yDomain[1])}
|
||||
height={maxY - yScale(yDomain[1])}
|
||||
/>
|
||||
)}
|
||||
{minValue >= yDomain[0] && (
|
||||
{min >= yDomain[0] && (
|
||||
<ThresholdMarkerArea
|
||||
level={level}
|
||||
top={minY}
|
||||
|
|
|
@ -24,12 +24,13 @@ import {
|
|||
VariableAssignment,
|
||||
QueryViewProperties,
|
||||
ViewType,
|
||||
CheckViewProperties,
|
||||
} from 'src/types'
|
||||
|
||||
interface OwnProps {
|
||||
timeRange: TimeRange
|
||||
manualRefresh: number
|
||||
properties: QueryViewProperties
|
||||
properties: QueryViewProperties | CheckViewProperties
|
||||
dashboardID: string
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@ import GreaterThresholdMarker from 'src/shared/components/GreaterThresholdMarker
|
|||
import {clamp} from 'src/shared/utils/vis'
|
||||
|
||||
// Types
|
||||
import {ThresholdConfig} from 'src/types'
|
||||
import {CheckThreshold, ThresholdType} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
thresholds: ThresholdConfig[]
|
||||
onSetThresholds: (newThresholds: ThresholdConfig[]) => void
|
||||
thresholds: CheckThreshold[]
|
||||
onSetThresholds: (newThresholds: CheckThreshold[]) => void
|
||||
yScale: Scale<number, number>
|
||||
yDomain: number[]
|
||||
}
|
||||
|
@ -31,18 +31,21 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
|
|||
const handleDrag = (index: number, field: string, y: number) => {
|
||||
const yRelative = y - originRef.current.getBoundingClientRect().top
|
||||
const yValue = clamp(yScale.invert(yRelative), yDomain)
|
||||
const nextThreshold = {...thresholds[index], [field]: yValue}
|
||||
const nextThreshold: CheckThreshold = {
|
||||
...thresholds[index],
|
||||
[field]: yValue,
|
||||
}
|
||||
|
||||
if (
|
||||
nextThreshold.type === 'range' &&
|
||||
nextThreshold.minValue > nextThreshold.maxValue
|
||||
nextThreshold.type === ThresholdType.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.minValue
|
||||
const maxValue = nextThreshold.min
|
||||
|
||||
nextThreshold.minValue = nextThreshold.maxValue
|
||||
nextThreshold.maxValue = maxValue
|
||||
nextThreshold.min = nextThreshold.max
|
||||
nextThreshold.max = maxValue
|
||||
}
|
||||
|
||||
const nextThresholds = thresholds.map((t, i) =>
|
||||
|
@ -60,7 +63,7 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
|
|||
const onChangeMinPos = ({y}) => handleDrag(index, 'minValue', y)
|
||||
|
||||
switch (threshold.type) {
|
||||
case 'greater':
|
||||
case ThresholdType.Greater:
|
||||
return (
|
||||
<GreaterThresholdMarker
|
||||
key={index}
|
||||
|
@ -70,7 +73,7 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
|
|||
onChangePos={onChangePos}
|
||||
/>
|
||||
)
|
||||
case 'less':
|
||||
case ThresholdType.Lesser:
|
||||
return (
|
||||
<LessThresholdMarker
|
||||
key={index}
|
||||
|
@ -80,7 +83,7 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
|
|||
onChangePos={onChangePos}
|
||||
/>
|
||||
)
|
||||
case 'range':
|
||||
case ThresholdType.Range:
|
||||
return (
|
||||
<RangeThresholdMarkers
|
||||
key={index}
|
||||
|
|
|
@ -22,13 +22,14 @@ import {
|
|||
XYViewGeom,
|
||||
RemoteDataState,
|
||||
TimeZone,
|
||||
CheckViewProperties,
|
||||
} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
giraffeResult: FromFluxResult
|
||||
files: string[]
|
||||
loading: RemoteDataState
|
||||
properties: QueryViewProperties
|
||||
properties: QueryViewProperties | CheckViewProperties
|
||||
timeZone: TimeZone
|
||||
}
|
||||
|
||||
|
|
|
@ -747,3 +747,39 @@ export const getNotificationRuleFailed = (message: string): Notification => ({
|
|||
...defaultErrorNotification,
|
||||
message: `Failed to get notification rule: ${message}`,
|
||||
})
|
||||
|
||||
export const createCheckFailed = (message: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to create check: ${message}`,
|
||||
})
|
||||
|
||||
export const updateCheckFailed = (message: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to update check: ${message}`,
|
||||
})
|
||||
|
||||
export const deleteCheckFailed = (message: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to delete check: ${message}`,
|
||||
})
|
||||
|
||||
export const createNotificationRuleFailed = (
|
||||
message: string
|
||||
): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to create notification rule: ${message}`,
|
||||
})
|
||||
|
||||
export const updateNotificationRuleFailed = (
|
||||
message: string
|
||||
): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to update notification rule: ${message}`,
|
||||
})
|
||||
|
||||
export const deleteNotificationRuleFailed = (
|
||||
message: string
|
||||
): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to delete notification rule: ${message}`,
|
||||
})
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
QueryView,
|
||||
QueryViewProperties,
|
||||
ExtractWorkingView,
|
||||
AggregateWindow,
|
||||
BuilderConfigAggregateWindow,
|
||||
} from 'src/types/dashboards'
|
||||
import {Action} from 'src/timeMachine/actions'
|
||||
import {TimeMachineTab} from 'src/types/timeMachine'
|
||||
|
@ -39,7 +39,7 @@ interface QueryBuilderState {
|
|||
buckets: string[]
|
||||
bucketsStatus: RemoteDataState
|
||||
functions: Array<[{name: string}]>
|
||||
aggregateWindow: AggregateWindow
|
||||
aggregateWindow: BuilderConfigAggregateWindow
|
||||
tags: Array<{
|
||||
valuesSearchTerm: string
|
||||
keysSearchTerm: string
|
||||
|
|
|
@ -1 +1,40 @@
|
|||
export {Check, NotificationRule} from '@influxdata/influx'
|
||||
export {
|
||||
Check,
|
||||
CheckType,
|
||||
CheckBaseTags,
|
||||
CheckBase,
|
||||
CheckStatusLevel,
|
||||
ThresholdCheck,
|
||||
ThresholdType,
|
||||
GreaterThreshold,
|
||||
LesserThreshold,
|
||||
RangeThreshold,
|
||||
DeadmanCheck,
|
||||
NotificationRuleType,
|
||||
CheckThreshold,
|
||||
NotificationRuleBase,
|
||||
} from '@influxdata/influx'
|
||||
|
||||
import {
|
||||
SlackNotificationRule as SlackNotificationRuleAPI,
|
||||
SMTPNotificationRule as SMTPNotificationRuleAPI,
|
||||
PagerDutyNotificationRule as PagerDutyNotificationRuleAPI,
|
||||
} from '@influxdata/influx'
|
||||
|
||||
export interface SlackNotificationRule extends SlackNotificationRuleAPI {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface SMTPNotificationRule extends SMTPNotificationRuleAPI {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface PagerDutyNotificationRule
|
||||
extends PagerDutyNotificationRuleAPI {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type NotificationRule =
|
||||
| SlackNotificationRule
|
||||
| SMTPNotificationRule
|
||||
| PagerDutyNotificationRule
|
||||
|
|
|
@ -5,6 +5,16 @@ import {
|
|||
IDashboard as DashboardAPI,
|
||||
Cell as CellAPI,
|
||||
ViewLinks,
|
||||
DashboardQuery,
|
||||
CheckViewProperties,
|
||||
} from '@influxdata/influx'
|
||||
|
||||
export {
|
||||
CheckView,
|
||||
CheckViewProperties,
|
||||
DashboardQuery,
|
||||
BuilderConfig,
|
||||
BuilderConfigAggregateWindow,
|
||||
} from '@influxdata/influx'
|
||||
|
||||
export enum Scale {
|
||||
|
@ -57,24 +67,6 @@ export enum QueryEditMode {
|
|||
Advanced = 'advanced',
|
||||
}
|
||||
|
||||
export interface AggregateWindow {
|
||||
period: string
|
||||
}
|
||||
|
||||
export interface BuilderConfig {
|
||||
buckets: string[]
|
||||
tags: Array<{key: string; values: string[]}>
|
||||
functions: Array<{name: string}>
|
||||
aggregateWindow: AggregateWindow
|
||||
}
|
||||
|
||||
export interface DashboardQuery {
|
||||
text: string
|
||||
editMode: QueryEditMode
|
||||
builderConfig: BuilderConfig
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface DashboardDraftQuery extends DashboardQuery {
|
||||
hidden: boolean
|
||||
}
|
||||
|
@ -129,7 +121,7 @@ export type ViewProperties =
|
|||
| HistogramView
|
||||
| HeatmapView
|
||||
| ScatterView
|
||||
| CheckView
|
||||
| CheckViewProperties
|
||||
|
||||
export type QueryViewProperties = Extract<
|
||||
ViewProperties,
|
||||
|
@ -290,47 +282,6 @@ export interface ScatterView {
|
|||
showNoteWhenEmpty: boolean
|
||||
}
|
||||
|
||||
export type CheckStatusLevel = 'OK' | 'INFO' | 'WARN' | 'CRIT' | 'UNKNOWN'
|
||||
|
||||
export interface GreaterThresholdConfig {
|
||||
type: 'greater'
|
||||
level: CheckStatusLevel
|
||||
allValues: boolean
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface LessThresholdConfig {
|
||||
type: 'less'
|
||||
level: CheckStatusLevel
|
||||
allValues: boolean
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface RangeThresholdConfig {
|
||||
type: 'range'
|
||||
level: CheckStatusLevel
|
||||
allValues: boolean
|
||||
minValue: number
|
||||
maxValue: number
|
||||
within: boolean
|
||||
}
|
||||
|
||||
export type ThresholdConfig =
|
||||
| GreaterThresholdConfig
|
||||
| LessThresholdConfig
|
||||
| RangeThresholdConfig
|
||||
|
||||
export interface CheckView {
|
||||
type: ViewType.Check
|
||||
shape: ViewShape.ChronografV2
|
||||
queries: DashboardQuery[]
|
||||
thresholds: ThresholdConfig[]
|
||||
yDomain: [number, number]
|
||||
colors: string[]
|
||||
note: string
|
||||
showNoteWhenEmpty: boolean
|
||||
}
|
||||
|
||||
export interface MarkdownView {
|
||||
type: ViewType.Markdown
|
||||
shape: ViewShape.ChronografV2
|
||||
|
@ -367,12 +318,6 @@ interface DashboardFileMetaSection {
|
|||
|
||||
export type NewCell = Omit<Cell, 'id' | 'links' | 'dashboardID'>
|
||||
|
||||
export enum ThresholdType {
|
||||
Text = 'text',
|
||||
BG = 'background',
|
||||
Base = 'base',
|
||||
}
|
||||
|
||||
export interface DashboardSwitcherLink {
|
||||
key: string
|
||||
text: string
|
||||
|
|
|
@ -1151,10 +1151,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@influxdata/giraffe/-/giraffe-0.16.0.tgz#b04a304460a7c9449fe1a9fa2a3f5db97949e916"
|
||||
integrity sha512-nDVQgx5Lq3fjsMXTzYwzf0HXKjSxzsBJibitObwtE0wefpzU4LW0IEUrcCBNIQyXj1OxyBNL9a67gZ4DyV7M2w==
|
||||
|
||||
"@influxdata/influx@0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@influxdata/influx/-/influx-0.5.0.tgz#dedf688d84a50b526a15362633bd5ee7f1e7a95b"
|
||||
integrity sha512-P24C5j20RRMX2JT43vTdoSozjZp01rcXxOAoIMR+sDMSMG4XuuckIEEOsdTnP1w+N2ivhjC74MjzmswxKeO0fw==
|
||||
"@influxdata/influx@0.5.5":
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@influxdata/influx/-/influx-0.5.5.tgz#ff30862ba3837df8e6e237634e7c844a00c03c08"
|
||||
integrity sha512-mmyymYIT/HpWDrdR1/x9+ENJIrZTFyr46EF42adqRwujwLxVyG1MPOC7H6aoH87DZgVxKV4nt+zmRSgOnetdpg==
|
||||
dependencies:
|
||||
axios "^0.19.0"
|
||||
|
||||
|
|
Loading…
Reference in New Issue