feat: alerting - Add alert builder (#14550)

* Add check saving flows and change activeTab type

* Add first pass at alertBuilder

* Change TimeMachineEnum type

* Add xy view properties to checks, and convert to and from check View type

* Add Column Header to checks column

* Add change current check type action

* Access view type through properties

* Load xy view options for check view

* Add orgID to check in createCheck

* Clear current check on close CheckEO

* Create Check Alerting Button

* Create check view on edit and new check EO

* Fix edit check eo action bug

* Update threshold types

* When switch schedule from every to cron change the inputs that are visible

* save Current Check from VEO if view type is check

* Add description component to checks

* TimeMachineIDs to TimeMachineID

* Remove bracketed object decleration

* Remove as Threshold type

* Use ViewType instead of typeof

* Create CheckType type

* Remove time machine reducer tests

* Remove check view properties that come from xyView

* Fix EditCheck hooks

* Move status calculations to body of function

* Update redux store when performing check CRUD

* Create add and remove check actions in timeMachine

* Remove trailing space
pull/14567/head
Deniz Kusefoglu 2019-08-05 17:38:49 -07:00 committed by GitHub
parent b303d54584
commit bca1af25a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1052 additions and 892 deletions

View File

@ -7506,7 +7506,8 @@ components:
- type
- shape
- checkID
- check
- queries
- colors
properties:
type:
type: string
@ -7518,6 +7519,15 @@ components:
type: string
check:
$ref: '#/components/schemas/Check'
queries:
type: array
items:
$ref: "#/components/schemas/DashboardQuery"
colors:
description: Colors define color encoding of data into a visualization
type: array
items:
type: string
Axes:
description: The viewport for a View's visualizations
type: object

View File

@ -7,6 +7,9 @@ import * as copy from 'src/shared/copy/notifications'
// APIs
import * as api from 'src/client'
// Utils
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
//Actions
import {
notify,
@ -15,14 +18,16 @@ import {
// Types
import {RemoteDataState} from '@influxdata/clockface'
import {Check, GetState} from 'src/types'
import {Check, GetState, CheckType} from 'src/types'
export type Action =
| ReturnType<typeof setAllChecks>
| ReturnType<typeof setCheck>
| ReturnType<typeof removeCheck>
| ReturnType<typeof setCurrentCheck>
| ReturnType<typeof setCurrentCheckStatus>
| ReturnType<typeof updateCurrentCheck>
| ReturnType<typeof changeCurrentCheckType>
export const setAllChecks = (status: RemoteDataState, checks?: Check[]) => ({
type: 'SET_ALL_CHECKS' as 'SET_ALL_CHECKS',
@ -41,17 +46,27 @@ export const removeCheck = (checkID: string) => ({
export const setCurrentCheck = (
status: RemoteDataState,
check?: Partial<Check>
check: Partial<Check>
) => ({
type: 'SET_CURRENT_CHECK' as 'SET_CURRENT_CHECK',
payload: {status, check},
})
export const setCurrentCheckStatus = (status: RemoteDataState) => ({
type: 'SET_CURRENT_CHECK_STATUS' as 'SET_CURRENT_CHECK_STATUS',
payload: {status},
})
export const updateCurrentCheck = (checkUpdate: Partial<Check>) => ({
type: 'UPDATE_CURRENT_CHECK' as 'UPDATE_CURRENT_CHECK',
payload: {status, checkUpdate},
})
export const changeCurrentCheckType = (type: CheckType) => ({
type: 'CHANGE_CURRENT_CHECK_TYPE' as 'CHANGE_CURRENT_CHECK_TYPE',
payload: {status, type},
})
export const getChecks = () => async (
dispatch: Dispatch<Action | NotificationAction>,
getState: GetState
@ -82,7 +97,7 @@ export const getCurrentCheck = (checkID: string) => async (
dispatch: Dispatch<Action | NotificationAction>
) => {
try {
dispatch(setCurrentCheck(RemoteDataState.Loading))
dispatch(setCurrentCheckStatus(RemoteDataState.Loading))
const resp = await api.getCheck({checkID})
@ -93,18 +108,37 @@ export const getCurrentCheck = (checkID: string) => async (
dispatch(setCurrentCheck(RemoteDataState.Done, resp.data))
} catch (e) {
console.error(e)
dispatch(setCurrentCheck(RemoteDataState.Error))
dispatch(setCurrentCheckStatus(RemoteDataState.Error))
dispatch(notify(copy.getCheckFailed(e.message)))
}
}
export const createCheck = (check: Partial<Check>) => async (
dispatch: Dispatch<Action | NotificationAction>
export const saveCurrentCheck = () => async (
dispatch: Dispatch<Action | NotificationAction>,
getState: GetState
) => {
try {
const resp = await api.postCheck({data: check as Check})
const state = getState()
const {
checks: {
current: {check},
},
orgs: {
org: {id: orgID},
},
} = state
if (resp.status !== 201) {
const {draftQueries} = getActiveTimeMachine(state)
const checkWithOrg = {...check, query: draftQueries[0], orgID} as Check
const resp = check.id
? await api.patchCheck({checkID: check.id, data: checkWithOrg})
: await api.postCheck({data: checkWithOrg})
if (resp.status === 201 || resp.status === 200) {
dispatch(setCheck(resp.data))
} else {
throw new Error(resp.data.message)
}
} catch (e) {
@ -119,7 +153,9 @@ export const updateCheck = (check: Partial<Check>) => async (
try {
const resp = await api.patchCheck({checkID: check.id, data: check as Check})
if (resp.status !== 200) {
if (resp.status === 200) {
dispatch(setCheck(resp.data))
} else {
throw new Error(resp.data.message)
}
@ -136,7 +172,9 @@ export const deleteCheck = (checkID: string) => async (
try {
const resp = await api.deleteCheck({checkID})
if (resp.status !== 204) {
if (resp.status === 204) {
dispatch(removeCheck(checkID))
} else {
throw new Error(resp.data.message)
}

View File

@ -0,0 +1,63 @@
// Libraries
import React, {FunctionComponent} from 'react'
import {connect} from 'react-redux'
// Components
import {Button, ComponentStatus} from '@influxdata/clockface'
// Utils
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
// Actions
import {setActiveTab} from 'src/timeMachine/actions'
// Types
import {AppState, TimeMachineTab} from 'src/types'
interface DispatchProps {
setActiveTab: typeof setActiveTab
}
interface StateProps {
activeTab: TimeMachineTab
}
type Props = DispatchProps & StateProps
const CheckAlertingButton: FunctionComponent<Props> = ({
setActiveTab,
activeTab,
}) => {
let buttonStatus = ComponentStatus.Default
if (activeTab === 'alerting') {
buttonStatus = ComponentStatus.Disabled
}
const handleClick = () => {
setActiveTab('alerting')
}
return (
<Button
titleText="Add alerting to this query"
text="Alerting"
onClick={handleClick}
status={buttonStatus}
/>
)
}
const mstp = (state: AppState): StateProps => {
const {activeTab} = getActiveTimeMachine(state)
return {activeTab}
}
const mdtp: DispatchProps = {
setActiveTab: setActiveTab,
}
export default connect<StateProps, DispatchProps, {}>(
mstp,
mdtp
)(CheckAlertingButton)

View File

@ -38,6 +38,10 @@ const CheckCard: FunctionComponent<Props> = ({
updateCheck({id: check.id, name})
}
const onUpdateDescription = (description: string) => {
updateCheck({id: check.id, description})
}
const onDelete = () => {
deleteCheck(check.id)
}
@ -53,7 +57,7 @@ const CheckCard: FunctionComponent<Props> = ({
}
const onCheckClick = () => {
router.push(`/orgs/${orgID}/checks/${check.id}`)
router.push(`/orgs/${orgID}/alerting/checks/${check.id}`)
}
return (
@ -79,7 +83,13 @@ const CheckCard: FunctionComponent<Props> = ({
testID="check-card--slide-toggle"
/>
}
// description
description={
<ResourceCard.Description
onUpdate={onUpdateDescription}
description={check.description}
placeholder={`Describe ${check.name}`}
/>
}
// labels
disabled={check.status === 'inactive'}
contextMenu={

View File

@ -0,0 +1,75 @@
// Libraries
import React, {FC, MouseEvent} from 'react'
// Components
import RenamablePageTitle from 'src/pageLayout/components/RenamablePageTitle'
import {
SquareButton,
ComponentColor,
ComponentSize,
IconFont,
Page,
} from '@influxdata/clockface'
import VisOptionsButton from 'src/timeMachine/components/VisOptionsButton'
import ViewTypeDropdown from 'src/timeMachine/components/view_options/ViewTypeDropdown'
import CheckAlertingButton from 'src/alerting/components/CheckAlertingButton'
// Constants
import {DEFAULT_CHECK_NAME, CHECK_NAME_MAX_LENGTH} from 'src/alerting/constants'
interface Props {
name: string
onSetName: (name: string) => void
onCancel: () => void
onSave: () => void
}
const saveButtonClass = 'veo-header--save-cell-button'
const CheckEOHeader: FC<Props> = ({name, onSetName, onCancel, onSave}) => {
const handleClickOutsideTitle = (e: MouseEvent<HTMLElement>) => {
const target = e.target as HTMLButtonElement
if (!target.className.includes(saveButtonClass)) {
return
}
onSave()
}
return (
<div className="veo-header">
<Page.Header fullWidth={true}>
<Page.Header.Left>
<RenamablePageTitle
name={name}
onRename={onSetName}
placeholder={DEFAULT_CHECK_NAME}
maxLength={CHECK_NAME_MAX_LENGTH}
onClickOutside={handleClickOutsideTitle}
/>
</Page.Header.Left>
<Page.Header.Right>
<CheckAlertingButton />
<ViewTypeDropdown />
<VisOptionsButton />
<SquareButton
icon={IconFont.Remove}
onClick={onCancel}
size={ComponentSize.Small}
/>
<SquareButton
className={saveButtonClass}
icon={IconFont.Checkmark}
color={ComponentColor.Success}
size={ComponentSize.Small}
onClick={onSave}
testID="save-cell--button"
/>
</Page.Header.Right>
</Page.Header>
</div>
)
}
export default CheckEOHeader

View File

@ -1,21 +1,32 @@
// Libraries
import React, {FunctionComponent} from 'react'
import {withRouter, WithRouterProps} from 'react-router'
import {connect} from 'react-redux'
// Components
import CheckCards from 'src/alerting/components/CheckCards'
import AlertsColumnHeader from 'src/alerting/components/AlertsColumnHeader'
// Types
import {Check, AppState} from 'src/types'
import CheckCards from 'src/alerting/components/CheckCards'
interface StateProps {
checks: Check[]
}
type Props = StateProps
type Props = StateProps & WithRouterProps
const ChecksColumn: FunctionComponent<Props> = ({checks}) => {
const ChecksColumn: FunctionComponent<Props> = ({
checks,
router,
params: {orgID},
}) => {
const handleClick = () => {
router.push(`/orgs/${orgID}/alerting/checks/new`)
}
return (
<>
Checks
<AlertsColumnHeader title="Checks" onCreate={handleClick} />
<CheckCards checks={checks} />
</>
)
@ -32,4 +43,4 @@ const mstp = (state: AppState) => {
export default connect<StateProps, {}, {}>(
mstp,
null
)(ChecksColumn)
)(withRouter(ChecksColumn))

View File

@ -5,11 +5,12 @@ import {connect} from 'react-redux'
// Components
import {Overlay, SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
import VEOHeader from 'src/dashboards/components/VEOHeader'
import CheckEOHeader from 'src/alerting/components/CheckEOHeader'
import TimeMachine from 'src/timeMachine/components/TimeMachine'
// Utils
import {createView} from 'src/shared/utils/view'
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
// Actions
import {
@ -21,61 +22,97 @@ import {
import {setActiveTimeMachine} from 'src/timeMachine/actions'
// Types
import {Check, AppState, RemoteDataState, XYViewProperties} from 'src/types'
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {
Check,
AppState,
RemoteDataState,
DashboardDraftQuery,
CheckViewProperties,
} from 'src/types'
import {TimeMachineID} from 'src/timeMachine/constants'
interface DispatchProps {
updateCheck: typeof updateCheck
setCurrentCheck: typeof setCurrentCheck
getCurrentCheck: typeof getCurrentCheck
updateCurrentCheck: typeof updateCurrentCheck
onSetActiveTimeMachine: typeof setActiveTimeMachine
}
interface StateProps {
check: Partial<Check>
status: RemoteDataState
query: DashboardDraftQuery
checkStatus: RemoteDataState
activeTimeMachineID: TimeMachineID
}
type Props = WithRouterProps & DispatchProps & StateProps
const EditCheckEditorOverlay: FunctionComponent<Props> = ({
onSetActiveTimeMachine,
params,
activeTimeMachineID,
getCurrentCheck,
checkStatus,
updateCheck,
router,
params: {checkID, orgID},
query,
check,
status,
}) => {
useEffect(() => {
getCurrentCheck(params.checkID)
onSetActiveTimeMachine(TimeMachineEnum.Alerting)
}, [params.checkID])
useEffect(() => {
// create view properties from check
const view = createView<XYViewProperties>('xy')
onSetActiveTimeMachine(TimeMachineEnum.Alerting, {view})
}, [check.id])
if (check) {
const view = createView<CheckViewProperties>('check')
// todo: when check has own view get view here
onSetActiveTimeMachine('alerting', {
view,
activeTab: 'alerting',
isViewingRawData: false,
})
} else {
getCurrentCheck(checkID)
}
}, [check, checkID])
const handleUpdateName = (name: string) => {
updateCurrentCheck({name})
}
const handleCancel = () => {}
const handleClose = () => {
setCurrentCheck(RemoteDataState.NotStarted, null)
router.push(`/orgs/${orgID}/alerting`)
}
const handleSave = () => {}
// dont render time machine until active time machine is what we expect
const handleSave = () => {
// todo: update view when check has own view
updateCheck({...check, query})
handleClose()
}
let loadingStatus = RemoteDataState.Loading
if (checkStatus === RemoteDataState.Error) {
loadingStatus = RemoteDataState.Error
}
if (
checkStatus === RemoteDataState.Done &&
activeTimeMachineID === 'alerting' &&
check.id === checkID
) {
loadingStatus = RemoteDataState.Done
}
return (
<Overlay visible={true} className="veo-overlay">
<div className="veo">
<SpinnerContainer
spinnerComponent={<TechnoSpinner />}
loading={status || RemoteDataState.Loading}
loading={loadingStatus}
>
<VEOHeader
<CheckEOHeader
key={check && check.name}
name={check && check.name}
onSetName={handleUpdateName}
onCancel={handleCancel}
onCancel={handleClose}
onSave={handleSave}
/>
<div className="veo-contents">
@ -90,11 +127,14 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
const mstp = (state: AppState): StateProps => {
const {
checks: {
current: {check, status},
current: {check, status: checkStatus},
},
timeMachines: {activeTimeMachineID},
} = state
return {check, status}
const {draftQueries} = getActiveTimeMachine(state)
return {check, checkStatus, activeTimeMachineID, query: draftQueries[0]}
}
const mdtp: DispatchProps = {
@ -102,6 +142,7 @@ const mdtp: DispatchProps = {
setCurrentCheck: setCurrentCheck,
updateCurrentCheck: updateCurrentCheck,
onSetActiveTimeMachine: setActiveTimeMachine,
getCurrentCheck: getCurrentCheck,
}
export default connect<StateProps, DispatchProps, {}>(

View File

@ -1,10 +1,11 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {connect} from 'react-redux'
import {withRouter, WithRouterProps} from 'react-router'
// Components
import {Overlay, SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
import VEOHeader from 'src/dashboards/components/VEOHeader'
import CheckEOHeader from 'src/alerting/components/CheckEOHeader'
import TimeMachine from 'src/timeMachine/components/TimeMachine'
// Actions
@ -12,6 +13,7 @@ import {
updateCheck,
setCurrentCheck,
updateCurrentCheck,
saveCurrentCheck,
} from 'src/alerting/actions/checks'
import {setActiveTimeMachine} from 'src/timeMachine/actions'
@ -19,15 +21,15 @@ import {setActiveTimeMachine} from 'src/timeMachine/actions'
import {createView} from 'src/shared/utils/view'
// Types
import {Check, AppState, RemoteDataState, XYViewProperties} from 'src/types'
import {DEFAULT_CHECK} from 'src/alerting/constants'
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {Check, AppState, RemoteDataState, CheckViewProperties} from 'src/types'
import {DEFAULT_THRESHOLD_CHECK} from 'src/alerting/constants'
interface DispatchProps {
updateCheck: typeof updateCheck
setCurrentCheck: typeof setCurrentCheck
updateCurrentCheck: typeof updateCurrentCheck
onSetActiveTimeMachine: typeof setActiveTimeMachine
saveCurrentCheck: typeof saveCurrentCheck
}
interface StateProps {
@ -35,28 +37,43 @@ interface StateProps {
status: RemoteDataState
}
type Props = DispatchProps & StateProps
type Props = DispatchProps & StateProps & WithRouterProps
const NewCheckOverlay: FunctionComponent<Props> = ({
onSetActiveTimeMachine,
updateCurrentCheck,
setCurrentCheck,
saveCurrentCheck,
params,
router,
status,
check,
}) => {
useEffect(() => {
setCurrentCheck(RemoteDataState.Done, DEFAULT_CHECK)
const view = createView<XYViewProperties>('xy')
onSetActiveTimeMachine(TimeMachineEnum.Alerting, {view})
setCurrentCheck(RemoteDataState.Done, DEFAULT_THRESHOLD_CHECK)
const view = createView<CheckViewProperties>('check')
onSetActiveTimeMachine('alerting', {
view,
activeTab: 'queries',
})
}, [])
const handleUpdateName = (name: string) => {
updateCurrentCheck({name})
}
const handleCancel = () => {}
const handleClose = () => {
setCurrentCheck(RemoteDataState.NotStarted, null)
router.push(`/orgs/${params.orgID}/alerting`)
}
const handleSave = () => {}
const handleSave = () => {
// todo: when check has own view
// save view as view
// put view.id on check.viewID
saveCurrentCheck()
handleClose()
}
return (
<Overlay visible={true} className="veo-overlay">
@ -65,11 +82,11 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
spinnerComponent={<TechnoSpinner />}
loading={status || RemoteDataState.Loading}
>
<VEOHeader
<CheckEOHeader
key={check && check.name}
name={check && check.name}
onSetName={handleUpdateName}
onCancel={handleCancel}
onCancel={handleClose}
onSave={handleSave}
/>
<div className="veo-contents">
@ -96,9 +113,10 @@ const mdtp: DispatchProps = {
setCurrentCheck: setCurrentCheck,
updateCurrentCheck: updateCurrentCheck,
onSetActiveTimeMachine: setActiveTimeMachine,
saveCurrentCheck: saveCurrentCheck,
}
export default connect<StateProps, DispatchProps, {}>(
mstp,
mdtp
)(NewCheckOverlay)
)(withRouter(NewCheckOverlay))

View File

@ -0,0 +1,42 @@
// Libraries
import React, {FC} from 'react'
// Components
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
import BuilderCard from 'src/timeMachine/components/builderCard/BuilderCard'
import CheckMetaCard from 'src/alerting/components/builder/CheckMetaCard'
import CheckThresholdsCard from 'src/alerting/components/builder/CheckThresholdsCard'
import CheckMatchingRulesCard from 'src/alerting/components/builder/CheckMatchingRulesCard'
const AlertBuilder: FC = () => {
return (
<div className="query-builder" data-testid="query-builder">
<div className="query-builder--cards">
<FancyScrollbar>
<div className="builder-card--list">
<BuilderCard testID="builder-meta">
<BuilderCard.Header title="Meta" />
<BuilderCard.Body addPadding={true}>
<CheckMetaCard />
</BuilderCard.Body>
</BuilderCard>
<BuilderCard testID="builder-meta">
<BuilderCard.Header title="Thresholds" />
<BuilderCard.Body addPadding={true}>
<CheckThresholdsCard />
</BuilderCard.Body>
</BuilderCard>
<BuilderCard testID="builder-meta">
<BuilderCard.Header title="Matching Notification Rules" />
<BuilderCard.Body addPadding={true}>
<CheckMatchingRulesCard />
</BuilderCard.Body>
</BuilderCard>
</div>
</FancyScrollbar>
</div>
</div>
)
}
export default AlertBuilder

View File

@ -0,0 +1,8 @@
// Libraries
import React, {FunctionComponent} from 'react'
const CheckMatchingRulesCard: FunctionComponent = () => {
return <div>Matching Rules go here</div>
}
export default CheckMatchingRulesCard

View File

@ -0,0 +1,225 @@
// Libraries
import React, {FC} from 'react'
import {connect} from 'react-redux'
// Components
import {
Radio,
ButtonShape,
Form,
InputType,
ComponentSize,
TextArea,
AutoComplete,
Wrap,
} from '@influxdata/clockface'
import {Input} from '@influxdata/clockface'
// Actions
import {
updateCurrentCheck,
changeCurrentCheckType,
} from 'src/alerting/actions/checks'
// Types
import {Check, AppState, CheckType} from 'src/types'
import {
DEFAULT_CHECK_EVERY,
DEFAULT_CHECK_OFFSET,
DEFAULT_CHECK_CRON,
} from 'src/alerting/constants'
interface DispatchProps {
updateCurrentCheck: typeof updateCurrentCheck
}
interface StateProps {
check: Partial<Check>
}
type Props = DispatchProps & StateProps
const CheckMetaCard: FC<Props> = ({updateCurrentCheck, check}) => {
const handleChangeType = (type: CheckType) => {
changeCurrentCheckType(type)
}
const handleChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
updateCurrentCheck({name: e.target.value})
}
const handleChangeMessage = (statusMessageTemplate: string) => {
updateCurrentCheck({statusMessageTemplate})
}
const handleChangeSchedule = (scheduleType: 'cron' | 'every') => {
if (scheduleType == 'cron' && !check.cron) {
updateCurrentCheck({cron: DEFAULT_CHECK_CRON, every: null, offset: null})
return
}
if (scheduleType == 'every' && !check.every) {
updateCurrentCheck({
every: DEFAULT_CHECK_EVERY,
offset: DEFAULT_CHECK_OFFSET,
cron: null,
})
return
}
}
return (
<>
<Form.Element label="Check Type">
<Radio shape={ButtonShape.StretchToFit}>
<Radio.Button
key="threshold"
id="threshold"
titleText="threshold"
value="threshold"
active={check.type === 'threshold'}
onClick={handleChangeType}
>
Threshold
</Radio.Button>
<Radio.Button
key="deadman"
id="deadman"
titleText="deadman"
value="deadman"
active={check.type === 'deadman'}
onClick={handleChangeType}
>
Deadman
</Radio.Button>
</Radio>
</Form.Element>
<Form.Element label="Name">
<Input
autoFocus={true}
maxLength={24}
name="Name"
onChange={handleChangeName}
placeholder="Name this check"
size={ComponentSize.Small}
spellCheck={false}
testID="input-field"
titleText="Title Text"
type={InputType.Text}
value={check.name}
/>
</Form.Element>
<Form.Element label="Status Message Template">
<TextArea
autoFocus={false}
autocomplete={AutoComplete.Off}
form=""
maxLength={50}
minLength={5}
name=""
onChange={handleChangeMessage}
placeholder="Placeholder Text"
readOnly={false}
required={false}
size={ComponentSize.Medium}
spellCheck={false}
testID="textarea"
value={check.statusMessageTemplate}
wrap={Wrap.Soft}
/>
</Form.Element>
<Form.Element label="Schedule">
<Radio shape={ButtonShape.StretchToFit}>
<Radio.Button
key="every"
id="every"
titleText="every"
value="every"
active={!!check.every}
onClick={handleChangeSchedule}
>
Every
</Radio.Button>
<Radio.Button
key="cron"
id="cron"
titleText="cron"
value="cron"
active={!check.every}
onClick={handleChangeSchedule}
>
Cron
</Radio.Button>
</Radio>
</Form.Element>
{check.every != null && (
<Form.Element label="Every">
<Input
autoFocus={false}
maxLength={24}
name="Name"
onChange={handleChangeName}
placeholder="Name this check"
size={ComponentSize.Small}
spellCheck={false}
testID="input-field"
titleText="Title Text"
type={InputType.Text}
value={check.every}
/>
</Form.Element>
)}
{check.offset != null && (
<Form.Element label="Offset">
<Input
autoFocus={false}
maxLength={24}
name="Offset"
onChange={handleChangeName}
placeholder="offset"
size={ComponentSize.Small}
spellCheck={false}
testID="input-field"
titleText="Title Text"
type={InputType.Text}
value={check.offset}
/>
</Form.Element>
)}
{check.cron != null && (
<Form.Element label="Cron">
<Input
autoFocus={false}
maxLength={24}
name="Cron"
onChange={handleChangeName}
placeholder="cron"
size={ComponentSize.Small}
spellCheck={false}
testID="input-field"
titleText="Title Text"
type={InputType.Text}
value={check.cron}
/>
</Form.Element>
)}
</>
)
}
const mstp = (state: AppState): StateProps => {
const {
checks: {
current: {check},
},
} = state
return {check}
}
const mdtp: DispatchProps = {
updateCurrentCheck: updateCurrentCheck,
}
export default connect<StateProps, DispatchProps, {}>(
mstp,
mdtp
)(CheckMetaCard)

View File

@ -0,0 +1,8 @@
// Libraries
import React, {FunctionComponent} from 'react'
const CheckThresholdsCard: FunctionComponent = () => {
return <div>Thresholds go here</div>
}
export default CheckThresholdsCard

View File

@ -0,0 +1,19 @@
// Libraries
import React, {FunctionComponent} from 'react'
// Components
import {Button} from '@influxdata/clockface'
const HelpButton: FunctionComponent = () => {
const handleClick = () => {}
return (
<Button
titleText="Learn more about alerting"
text="Help"
onClick={handleClick}
/>
)
}
export default HelpButton

View File

@ -0,0 +1,48 @@
// Libraries
import React, {FunctionComponent} from 'react'
import {connect} from 'react-redux'
// Components
import {Button} from '@influxdata/clockface'
// Actions
import {removeCheck} from 'src/timeMachine/actions'
import {setCurrentCheck} from 'src/alerting/actions/checks'
//Types
import {RemoteDataState} from 'src/types'
interface DispatchProps {
removeCheck: typeof removeCheck
setCurrentCheck: typeof setCurrentCheck
}
type Props = DispatchProps
const RemoveButton: FunctionComponent<Props> = ({
setCurrentCheck,
removeCheck,
}) => {
const handleClick = () => {
setCurrentCheck(RemoteDataState.NotStarted, null)
removeCheck()
}
return (
<Button
titleText="Remove Check from Cell"
text="Remove Check from Cell"
onClick={handleClick}
/>
)
}
const mdtp: DispatchProps = {
removeCheck: removeCheck,
setCurrentCheck: setCurrentCheck,
}
export default connect<{}, DispatchProps, {}>(
null,
mdtp
)(RemoveButton)

View File

@ -2,18 +2,35 @@ import {
Check,
DashboardQuery,
NotificationRule,
GreaterThreshold,
ThresholdCheck,
DeadmanCheck,
} from 'src/types'
export const DEFAULT_CHECK_NAME = 'Name this check'
export const DEFAULT_NOTIFICATION_RULE_NAME = 'Name this notification rule'
export const DEFAULT_CHECK: Partial<ThresholdCheck> = {
export const CHECK_NAME_MAX_LENGTH = 68
export const DEFAULT_CHECK_CRON = '1h'
export const DEFAULT_CHECK_EVERY = '1h'
export const DEFAULT_CHECK_OFFSET = '0s'
export const DEFAULT_CHECK_REPORT_ZERO = false
export const DEFAULT_THRESHOLD_CHECK: Partial<ThresholdCheck> = {
name: DEFAULT_CHECK_NAME,
type: 'threshold',
status: 'active',
thresholds: [],
every: DEFAULT_CHECK_EVERY,
offset: DEFAULT_CHECK_OFFSET,
}
export const DEFAULT_DEADMAN_CHECK: Partial<DeadmanCheck> = {
name: DEFAULT_CHECK_NAME,
type: 'deadman',
status: 'active',
every: DEFAULT_CHECK_EVERY,
offset: DEFAULT_CHECK_OFFSET,
reportZero: DEFAULT_CHECK_REPORT_ZERO,
}
export const query: DashboardQuery = {
@ -38,10 +55,10 @@ export const check1: Check = {
statusMessageTemplate: 'this is a great message template',
thresholds: [
{
level: 'WARN',
level: 'UNKNOWN',
lowerBound: 20,
allValues: false,
type: 'greater',
} as GreaterThreshold,
},
],
}
@ -60,10 +77,10 @@ export const check2: Check = {
statusMessageTemplate: 'this is a great message template',
thresholds: [
{
level: 'WARN',
level: 'UNKNOWN',
lowerBound: 20,
allValues: false,
type: 'greater',
} as GreaterThreshold,
},
],
}

View File

@ -1,9 +1,11 @@
// Libraries
import {produce} from 'immer'
import {omit} from 'lodash'
// Types
import {RemoteDataState, Check} from 'src/types'
import {RemoteDataState, Check, ThresholdCheck, DeadmanCheck} from 'src/types'
import {Action} from 'src/alerting/actions/checks'
import {DEFAULT_THRESHOLD_CHECK, DEFAULT_DEADMAN_CHECK} from '../constants'
export interface ChecksState {
status: RemoteDataState
@ -49,16 +51,34 @@ export default (
case 'SET_CURRENT_CHECK':
draftState.current.status = action.payload.status
if (action.payload.check) {
draftState.current.check = action.payload.check
}
draftState.current.check = action.payload.check
return
case 'SET_CURRENT_CHECK_STATUS':
draftState.current.status = action.payload.status
return
case 'UPDATE_CURRENT_CHECK':
draftState.current.check = {
...draftState.current.check,
...action.payload.checkUpdate,
} as Check
return
case 'CHANGE_CURRENT_CHECK_TYPE':
const exCheck = draftState.current.check
if (action.payload.type == 'deadman') {
draftState.current.check = {
...DEFAULT_DEADMAN_CHECK,
...omit(exCheck, 'thresholds'),
} as DeadmanCheck
}
if (action.payload.type == 'threshold') {
draftState.current.check = {
...DEFAULT_THRESHOLD_CHECK,
...omit(exCheck, ['timeSince', 'reportZero', 'level']),
} as ThresholdCheck
}
return
}
})

View File

@ -846,7 +846,9 @@ export interface CheckViewProperties {
type: 'check'
shape: 'chronograf-v2'
checkID: string
check: Check
check?: Check
queries: DashboardQuery[]
colors: string[]
}
export type Check = DeadmanCheck | ThresholdCheck
@ -888,28 +890,11 @@ export type ThresholdCheck = CheckBase & {
thresholds?: Threshold[]
}
export type Threshold = GreaterThreshold | LesserThreshold | RangeThreshold
export type GreaterThreshold = ThresholdBase & {
type: 'greater'
value: number
}
export interface ThresholdBase {
export interface Threshold {
level?: CheckStatusLevel
allValues?: boolean
}
export type LesserThreshold = ThresholdBase & {
type: 'lesser'
value: number
}
export type RangeThreshold = ThresholdBase & {
type: 'range'
min: number
max: number
within: boolean
lowerBound?: number
upperBound?: number
}
export interface ScatterViewProperties {

View File

@ -9,10 +9,12 @@ import {RemoteDataState, QueryView} from 'src/types'
import {Dispatch} from 'redux'
import {View} from 'src/types'
import {Action as TimeMachineAction} from 'src/timeMachine/actions'
import {Action as CheckAction} from 'src/alerting/actions/checks'
//Actions
import {setActiveTimeMachine} from 'src/timeMachine/actions'
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {TimeMachineID} from 'src/timeMachine/constants'
import {setCurrentCheck} from 'src/alerting/actions/checks'
export type Action = SetViewAction | SetViewsAction | ResetViewsAction
@ -92,12 +94,16 @@ export const updateView = (dashboardID: string, view: View) => async (
export const getViewForTimeMachine = (
dashboardID: string,
cellID: string,
timeMachineID: TimeMachineEnum
) => async (dispatch: Dispatch<Action | TimeMachineAction>): Promise<void> => {
timeMachineID: TimeMachineID
) => async (
dispatch: Dispatch<Action | TimeMachineAction | CheckAction>
): Promise<void> => {
dispatch(setView(cellID, null, RemoteDataState.Loading))
try {
const view = (await getViewAJAX(dashboardID, cellID)) as QueryView
if (view.properties.type === 'check') {
dispatch(setCurrentCheck(RemoteDataState.Done, view.properties.check))
}
dispatch(setView(cellID, view, RemoteDataState.Done))
dispatch(setActiveTimeMachine(timeMachineID, {view}))
} catch {

View File

@ -11,20 +11,22 @@ import VEOHeader from 'src/dashboards/components/VEOHeader'
// Actions
import {setActiveTimeMachine} from 'src/timeMachine/actions'
import {saveCurrentCheck} from 'src/alerting/actions/checks'
import {setName} from 'src/timeMachine/actions'
import {saveVEOView} from 'src/dashboards/actions'
import {setView, getViewForTimeMachine} from 'src/dashboards/actions/views'
// Utils
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {getView} from 'src/dashboards/selectors'
// Types
import {AppState, RemoteDataState, QueryView} from 'src/types'
import {executeQueries} from 'src/timeMachine/actions/queries'
import {TimeMachineID} from 'src/timeMachine/constants'
interface DispatchProps {
onSetActiveTimeMachine: typeof setActiveTimeMachine
saveCurrentCheck: typeof saveCurrentCheck
onSetName: typeof setName
onSaveView: typeof saveVEOView
setView: typeof setView
@ -34,7 +36,7 @@ interface DispatchProps {
interface StateProps {
view: QueryView | null
loadingState: RemoteDataState
activeTimeMachineID: TimeMachineID
}
type Props = DispatchProps & StateProps & WithRouterProps
@ -42,8 +44,9 @@ type Props = DispatchProps & StateProps & WithRouterProps
const EditViewVEO: FunctionComponent<Props> = ({
onSetActiveTimeMachine,
getViewForTimeMachine,
activeTimeMachineID,
saveCurrentCheck,
executeQueries,
loadingState,
onSaveView,
onSetName,
params: {orgID, cellID, dashboardID},
@ -52,9 +55,9 @@ const EditViewVEO: FunctionComponent<Props> = ({
}) => {
useEffect(() => {
if (view) {
onSetActiveTimeMachine(TimeMachineEnum.VEO, {view})
onSetActiveTimeMachine('veo', {view})
} else {
getViewForTimeMachine(dashboardID, cellID, TimeMachineEnum.VEO)
getViewForTimeMachine(dashboardID, cellID, 'veo')
}
}, [view, orgID, cellID, dashboardID])
@ -68,11 +71,21 @@ const EditViewVEO: FunctionComponent<Props> = ({
const handleSave = () => {
try {
if (view.properties.type === 'check') {
saveCurrentCheck()
}
onSaveView(dashboardID)
handleClose()
} catch (e) {}
}
const viewMatchesRoute = get(view, 'id', null) === cellID
let loadingState = RemoteDataState.Loading
if (activeTimeMachineID === 'veo' && viewMatchesRoute) {
loadingState = RemoteDataState.Done
}
return (
<Overlay visible={true} className="veo-overlay">
<div className="veo">
@ -101,15 +114,7 @@ const mstp = (state: AppState, {params: {cellID}}): StateProps => {
const view = getView(state, cellID) as QueryView
const viewMatchesRoute = get(view, 'id', null) === cellID
let loadingState = RemoteDataState.Loading
if (activeTimeMachineID === TimeMachineEnum.VEO && viewMatchesRoute) {
loadingState = RemoteDataState.Done
}
return {view, loadingState}
return {view, activeTimeMachineID}
}
const mdtp: DispatchProps = {
@ -117,6 +122,7 @@ const mdtp: DispatchProps = {
setView: setView,
onSaveView: saveVEOView,
onSetActiveTimeMachine: setActiveTimeMachine,
saveCurrentCheck: saveCurrentCheck,
executeQueries: executeQueries,
getViewForTimeMachine: getViewForTimeMachine,
}

View File

@ -10,6 +10,7 @@ import TimeMachine from 'src/timeMachine/components/TimeMachine'
import VEOHeader from 'src/dashboards/components/VEOHeader'
// Actions
import {saveCurrentCheck} from 'src/alerting/actions/checks'
import {setActiveTimeMachine} from 'src/timeMachine/actions'
import {setName} from 'src/timeMachine/actions'
import {saveVEOView} from 'src/dashboards/actions'
@ -17,50 +18,60 @@ import {saveVEOView} from 'src/dashboards/actions'
// Utils
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
import {createView} from 'src/shared/utils/view'
import {TimeMachineEnum} from 'src/timeMachine/constants'
// Types
import {AppState, XYViewProperties, RemoteDataState, View} from 'src/types'
import {TimeMachineID} from 'src/timeMachine/constants'
interface DispatchProps {
onSetActiveTimeMachine: typeof setActiveTimeMachine
saveCurrentCheck: typeof saveCurrentCheck
onSetName: typeof setName
onSaveView: typeof saveVEOView
}
interface StateProps {
activeTimeMachineID: TimeMachineID
view: View
loadingState: RemoteDataState
}
type Props = DispatchProps & StateProps & WithRouterProps
const NewViewVEO: FunctionComponent<Props> = ({
onSetActiveTimeMachine,
loadingState,
activeTimeMachineID,
saveCurrentCheck,
onSaveView,
onSetName,
params,
params: {orgID, dashboardID},
router,
view,
}) => {
useEffect(() => {
const view = createView<XYViewProperties>('xy')
onSetActiveTimeMachine(TimeMachineEnum.VEO, {view})
onSetActiveTimeMachine('veo', {view})
}, [])
const handleClose = () => {
const {orgID, dashboardID} = params
router.push(`/orgs/${orgID}/dashboards/${dashboardID}`)
}
const handleSave = () => {
try {
onSaveView(params.dashboardID)
if (view.properties.type === 'check') {
saveCurrentCheck()
}
onSaveView(dashboardID)
handleClose()
} catch (e) {}
}
let loadingState = RemoteDataState.Loading
const viewIsNew = !get(view, 'id', null)
if (activeTimeMachineID === 'veo' && viewIsNew) {
loadingState = RemoteDataState.Done
}
return (
<Overlay visible={true} className="veo-overlay">
<div className="veo">
@ -88,20 +99,13 @@ const mstp = (state: AppState): StateProps => {
const {activeTimeMachineID} = state.timeMachines
const {view} = getActiveTimeMachine(state)
const viewIsNew = !get(view, 'id', null)
let loadingState = RemoteDataState.Loading
if (activeTimeMachineID === TimeMachineEnum.VEO && viewIsNew) {
loadingState = RemoteDataState.Done
}
return {view, loadingState}
return {view, activeTimeMachineID}
}
const mdtp: DispatchProps = {
onSetName: setName,
onSaveView: saveVEOView,
saveCurrentCheck: saveCurrentCheck,
onSetActiveTimeMachine: setActiveTimeMachine,
}

View File

@ -12,6 +12,7 @@ import {
import {Page} from 'src/pageLayout'
import VisOptionsButton from 'src/timeMachine/components/VisOptionsButton'
import ViewTypeDropdown from 'src/timeMachine/components/view_options/ViewTypeDropdown'
import AlertingButton from 'src/timeMachine/components/AlertingButton'
// Constants
import {
@ -44,6 +45,7 @@ class VEOHeader extends PureComponent<Props> {
/>
</Page.Header.Left>
<Page.Header.Right>
<AlertingButton />
<ViewTypeDropdown />
<VisOptionsButton />
<SquareButton

View File

@ -11,7 +11,6 @@ import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
import {setActiveTimeMachine} from 'src/timeMachine/actions'
// Utils
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {HoverTimeProvider} from 'src/dashboards/utils/hoverTime'
import {queryBuilderFetcher} from 'src/timeMachine/apis/QueryBuilderFetcher'
import {
@ -37,7 +36,7 @@ class DataExplorer extends PureComponent<Props, {}> {
constructor(props: Props) {
super(props)
props.onSetActiveTimeMachine(TimeMachineEnum.DE)
props.onSetActiveTimeMachine('de')
queryBuilderFetcher.clearCache()
}

View File

@ -29,10 +29,9 @@ const Y_COLUMN = '_value'
const THRESHOLDS: Threshold[] = [
{
type: 'greater',
allValues: false,
level: 'UNKNOWN',
value: 20,
lowerBound: 20,
allValues: false,
},
]

View File

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

View File

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

View File

@ -36,18 +36,6 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
[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) =>
i === index ? nextThreshold : t
)
@ -62,40 +50,41 @@ const ThresholdMarkers: FunctionComponent<Props> = ({
const onChangeMaxPos = ({y}) => handleDrag(index, 'maxValue', y)
const onChangeMinPos = ({y}) => handleDrag(index, 'minValue', y)
switch (threshold.type) {
case 'greater':
return (
<GreaterThresholdMarker
key={index}
yScale={yScale}
yDomain={yDomain}
threshold={threshold}
onChangePos={onChangePos}
/>
)
case 'lesser':
return (
<LessThresholdMarker
key={index}
yScale={yScale}
yDomain={yDomain}
threshold={threshold}
onChangePos={onChangePos}
/>
)
case 'range':
return (
<RangeThresholdMarkers
key={index}
yScale={yScale}
yDomain={yDomain}
threshold={threshold}
onChangeMinPos={onChangeMinPos}
onChangeMaxPos={onChangeMaxPos}
/>
)
default:
return null
if (threshold.lowerBound && threshold.upperBound) {
return (
<RangeThresholdMarkers
key={index}
yScale={yScale}
yDomain={yDomain}
threshold={threshold}
onChangeMinPos={onChangeMinPos}
onChangeMaxPos={onChangeMaxPos}
/>
)
}
if (threshold.lowerBound) {
return (
<GreaterThresholdMarker
key={index}
yScale={yScale}
yDomain={yDomain}
threshold={threshold}
onChangePos={onChangePos}
/>
)
}
if (threshold.upperBound) {
return (
<LessThresholdMarker
key={index}
yScale={yScale}
yDomain={yDomain}
threshold={threshold}
onChangePos={onChangePos}
/>
)
}
})}
</div>

View File

@ -69,7 +69,6 @@ const ViewSwitcher: FunctionComponent<Props> = ({
)}
</LatestValueTransform>
)
case 'xy':
return (
<XYPlot

View File

@ -26,6 +26,7 @@ import {
BuilderConfig,
Axis,
Color,
CheckViewProperties,
} from 'src/types'
function defaultView() {
@ -244,6 +245,16 @@ const NEW_VIEW_CREATORS = {
ySuffix: '',
},
}),
check: (): NewView<CheckViewProperties> => ({
name: 'check',
properties: {
type: 'check',
shape: 'chronograf-v2',
checkID: '',
queries: [defaultViewQuery()],
colors: NINETEEN_EIGHTY_FOUR,
},
}),
}
export function createView<T extends ViewProperties = ViewProperties>(
@ -255,5 +266,5 @@ export function createView<T extends ViewProperties = ViewProperties>(
throw new Error(`no view creator implemented for view of type ${viewType}`)
}
return creator()
return creator() as NewView<T>
}

View File

@ -23,7 +23,7 @@ import {
} from 'src/types'
import {Color} from 'src/types/colors'
import {HistogramPosition} from '@influxdata/giraffe'
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {TimeMachineID} from 'src/timeMachine/constants'
export type Action =
| QueryBuilderAction
@ -73,17 +73,19 @@ export type Action =
| SetYDomainAction
| SetXAxisLabelAction
| SetShadeBelowAction
| ReturnType<typeof removeCheck>
| ReturnType<typeof addCheck>
interface SetActiveTimeMachineAction {
type: 'SET_ACTIVE_TIME_MACHINE'
payload: {
activeTimeMachineID: TimeMachineEnum
activeTimeMachineID: TimeMachineID
initialState: Partial<TimeMachineState>
}
}
export const setActiveTimeMachine = (
activeTimeMachineID: TimeMachineEnum,
activeTimeMachineID: TimeMachineID,
initialState: Partial<TimeMachineState> = {}
): SetActiveTimeMachineAction => ({
type: 'SET_ACTIVE_TIME_MACHINE',
@ -593,3 +595,11 @@ export const setXAxisLabel = (xAxisLabel: string): SetXAxisLabelAction => ({
type: 'SET_X_AXIS_LABEL',
payload: {xAxisLabel},
})
export const removeCheck = () => ({
type: 'REMOVE_CHECK' as 'REMOVE_CHECK',
})
export const addCheck = () => ({
type: 'ADD_CHECK' as 'ADD_CHECK',
})

View File

@ -0,0 +1,86 @@
// Libraries
import React, {FunctionComponent} from 'react'
import {connect} from 'react-redux'
// Components
import {Button, ComponentStatus} from '@influxdata/clockface'
// Utils
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
// Actions
import {setType as setViewType, addCheck} from 'src/timeMachine/actions'
import {setCurrentCheck} from 'src/alerting/actions/checks'
import {setActiveTab} from 'src/timeMachine/actions'
// Types
import {AppState, RemoteDataState, ViewType, TimeMachineTab} from 'src/types'
import {DEFAULT_THRESHOLD_CHECK} from 'src/alerting/constants'
interface DispatchProps {
setActiveTab: typeof setActiveTab
setViewType: typeof setViewType
setCurrentCheck: typeof setCurrentCheck
addCheck: typeof addCheck
}
interface StateProps {
activeTab: TimeMachineTab
viewType: ViewType
}
type Props = DispatchProps & StateProps
const AlertingButton: FunctionComponent<Props> = ({
setActiveTab,
addCheck,
activeTab,
setCurrentCheck,
viewType,
}) => {
const handleClick = () => {
if (viewType !== 'check') {
setCurrentCheck(RemoteDataState.Done, DEFAULT_THRESHOLD_CHECK)
addCheck()
} else {
setActiveTab('alerting')
}
}
let status = ComponentStatus.Default
if (activeTab === 'alerting') {
status = ComponentStatus.Disabled
}
return (
<Button
titleText="Add alerting to this query"
text="Alerting"
onClick={handleClick}
status={status}
/>
)
}
const mstp = (state: AppState): StateProps => {
const {
activeTab,
view: {
properties: {type: viewType},
},
} = getActiveTimeMachine(state)
return {activeTab, viewType}
}
const mdtp: DispatchProps = {
setActiveTab: setActiveTab,
setViewType: setViewType,
setCurrentCheck: setCurrentCheck,
addCheck: addCheck,
}
export default connect<StateProps, DispatchProps, {}>(
mstp,
mdtp
)(AlertingButton)

View File

@ -6,6 +6,7 @@ import classnames from 'classnames'
// Components
import {DraggableResizer, Orientation} from '@influxdata/clockface'
import TimeMachineQueries from 'src/timeMachine/components/Queries'
import TimeMachineAlerting from 'src/timeMachine/components/TimeMachineAlerting'
import TimeMachineVis from 'src/timeMachine/components/Vis'
import ViewOptions from 'src/timeMachine/components/view_options/ViewOptions'
@ -14,18 +15,23 @@ import {getActiveTimeMachine} from 'src/timeMachine/selectors'
// Types
import {AppState, TimeMachineTab} from 'src/types'
import {TimeMachineID} from 'src/timeMachine/constants'
const INITIAL_RESIZER_HANDLE = 0.5
interface StateProps {
activeTab: TimeMachineTab
activeTimeMachineID: TimeMachineID
}
const TimeMachine: FunctionComponent<StateProps> = ({activeTab}) => {
const TimeMachine: FunctionComponent<StateProps> = ({
activeTimeMachineID,
activeTab,
}) => {
const [dragPosition, setDragPosition] = useState([INITIAL_RESIZER_HANDLE])
const containerClassName = classnames('time-machine', {
'time-machine--split': activeTab === TimeMachineTab.Visualization,
'time-machine--split': activeTab === 'visualization',
})
return (
@ -47,21 +53,28 @@ const TimeMachine: FunctionComponent<StateProps> = ({activeTab}) => {
data-testid="time-machine--bottom"
>
<div className="time-machine--bottom-contents">
<TimeMachineQueries />
{activeTab === 'alerting' ? (
<TimeMachineAlerting
activeTimeMachineID={activeTimeMachineID}
/>
) : (
<TimeMachineQueries />
)}
</div>
</div>
</DraggableResizer.Panel>
</DraggableResizer>
</div>
{activeTab === TimeMachineTab.Visualization && <ViewOptions />}
{activeTab === 'visualization' && <ViewOptions />}
</>
)
}
const mstp = (state: AppState) => {
const {activeTab} = getActiveTimeMachine(state)
const {activeTimeMachineID} = state.timeMachines
return {activeTab}
return {activeTab, activeTimeMachineID}
}
export default connect<StateProps>(mstp)(TimeMachine)

View File

@ -0,0 +1,45 @@
// Libraries
import React, {FunctionComponent} from 'react'
// Components
import TimeMachineAlertBuilder from 'src/alerting/components/builder/AlertBuilder'
import {
ComponentSize,
ComponentSpacer,
FlexDirection,
JustifyContent,
} from '@influxdata/clockface'
import RemoveButton from 'src/alerting/components/builder/RemoveButton'
import HelpButton from 'src/alerting/components/builder/HelpButton'
import {TimeMachineID} from 'src/timeMachine/constants'
interface Props {
activeTimeMachineID: TimeMachineID
}
const TimeMachineAlerting: FunctionComponent<Props> = ({
activeTimeMachineID,
}) => {
return (
<div className="time-machine-queries">
<div className="time-machine-queries--controls">
Check Builder
<div className="time-machine-queries--buttons">
<ComponentSpacer
direction={FlexDirection.Row}
justifyContent={JustifyContent.FlexEnd}
margin={ComponentSize.Small}
>
<HelpButton />
{activeTimeMachineID == 'veo' && <RemoveButton />}
</ComponentSpacer>
</div>
</div>
<div className="time-machine-queries--body">
<TimeMachineAlertBuilder />
</div>
</div>
)
}
export default TimeMachineAlerting

View File

@ -30,7 +30,7 @@ class VisOptionsButton extends Component<Props> {
const {activeTab} = this.props
const color =
activeTab === TimeMachineTab.Visualization
activeTab === 'visualization'
? ComponentColor.Primary
: ComponentColor.Default
@ -46,10 +46,10 @@ class VisOptionsButton extends Component<Props> {
private handleClick = (): void => {
const {onSetActiveTab, activeTab} = this.props
if (activeTab === TimeMachineTab.Queries) {
onSetActiveTab(TimeMachineTab.Visualization)
if (activeTab !== 'visualization') {
onSetActiveTab('visualization')
} else {
onSetActiveTab(TimeMachineTab.Queries)
onSetActiveTab('queries')
}
}
}

View File

@ -29,14 +29,13 @@ type Props = DispatchProps & StateProps
class ViewTypeDropdown extends PureComponent<Props> {
public render() {
const {view} = this.props
return (
<Dropdown
widthPixels={215}
className="view-type-dropdown"
button={(active, onClick) => (
<Dropdown.Button active={active} onClick={onClick}>
{this.getVewTypeGraphic(view.properties.type as ViewType)}
{this.getVewTypeGraphic(this.selectedView)}
</Dropdown.Button>
)}
menu={onCollapse => (
@ -68,10 +67,14 @@ class ViewTypeDropdown extends PureComponent<Props> {
))
}
private get selectedView(): string {
private get selectedView(): ViewType {
const {view} = this.props
return `${view.properties.type}`
if (view.properties.type === 'check') {
return 'xy'
}
return view.properties.type
}
private getVewTypeGraphic = (viewType: ViewType): JSX.Element => {

View File

@ -1,5 +1 @@
export enum TimeMachineEnum {
DE = 'de',
VEO = 'veo',
Alerting = 'alerting',
}
export type TimeMachineID = 'de' | 'veo' | 'alerting'

View File

@ -1,657 +0,0 @@
import _ from 'lodash'
// Reducers
import {
initialState,
initialStateHelper,
timeMachineReducer,
timeMachinesReducer,
} from 'src/timeMachine/reducers'
// Actions
import {
setActiveTab,
setActiveTimeMachine,
setActiveQueryIndexSync,
editActiveQueryWithBuilderSync,
editActiveQueryAsFlux,
addQuerySync,
removeQuerySync,
updateActiveQueryName,
setBackgroundThresholdColoring,
setTextThresholdColoring,
} from 'src/timeMachine/actions'
// Utils
import {createView} from 'src/shared/utils/view'
// Constants
import {TimeMachineEnum} from 'src/timeMachine/constants'
// Types
import {TimeMachineTab} from 'src/types/timeMachine'
import {DashboardDraftQuery, QueryViewProperties} from 'src/types/dashboards'
import {selectAggregateWindow} from '../actions/queryBuilder'
describe('timeMachinesReducer', () => {
test('it directs actions to the currently active timeMachine', () => {
const state = initialState()
const de = state.timeMachines[TimeMachineEnum.DE]
const veo = state.timeMachines[TimeMachineEnum.VEO]
expect(state.activeTimeMachineID).toEqual(TimeMachineEnum.DE)
expect(de.activeTab).toEqual(TimeMachineTab.Queries)
expect(veo.activeTab).toEqual(TimeMachineTab.Queries)
const nextState = timeMachinesReducer(
state,
setActiveTab(TimeMachineTab.Visualization)
)
const nextDE = nextState.timeMachines[TimeMachineEnum.DE]
const nextVEO = nextState.timeMachines[TimeMachineEnum.VEO]
expect(nextDE.activeTab).toEqual(TimeMachineTab.Visualization)
expect(nextVEO.activeTab).toEqual(TimeMachineTab.Queries)
})
test('it resets tab and draftScript state on a timeMachine when activated', () => {
const state = initialState()
expect(state.activeTimeMachineID).toEqual(TimeMachineEnum.DE)
const activeTimeMachine = state.timeMachines[state.activeTimeMachineID]
activeTimeMachine.activeQueryIndex = 2
const view = createView<QueryViewProperties>()
view.properties.queries = [
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
{
name: '',
text: 'bar',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
]
const nextState = timeMachinesReducer(
state,
setActiveTimeMachine(TimeMachineEnum.VEO, {view})
)
expect(nextState.activeTimeMachineID).toEqual(TimeMachineEnum.VEO)
const nextTimeMachine =
nextState.timeMachines[nextState.activeTimeMachineID]
expect(nextTimeMachine.activeTab).toEqual(TimeMachineTab.Queries)
expect(nextTimeMachine.activeQueryIndex).toEqual(0)
expect(
_.map(nextTimeMachine.draftQueries, q => _.omit(q, ['hidden']))
).toEqual(view.properties.queries)
})
})
describe('timeMachineReducer', () => {
describe('EDIT_ACTIVE_QUERY_WITH_BUILDER', () => {
test('changes the activeQueryEditor and editMode for the currently active query', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
state.draftQueries = [
{
name: '',
text: 'foo',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: 'bar',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
]
const nextState = timeMachineReducer(
state,
editActiveQueryWithBuilderSync()
)
expect(nextState.activeQueryIndex).toEqual(1)
expect(nextState.draftQueries).toEqual([
{
name: '',
text: '',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: '',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
])
})
})
describe('EDIT_ACTIVE_QUERY_AS_FLUX', () => {
test('changes the activeQueryEditor and editMode for the currently active query', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
state.draftQueries = [
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: 'bar',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
]
const nextState = timeMachineReducer(state, editActiveQueryAsFlux())
expect(nextState.activeQueryIndex).toEqual(1)
expect(nextState.draftQueries).toEqual([
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: 'bar',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
])
})
})
describe('SET_ACTIVE_QUERY_INDEX', () => {
describe('sets the activeQueryIndex and activeQueryEditor', () => {
test('shows the builder when active query is in builder mode', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
state.view.properties.queries = [
{
name: '',
text: 'foo',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
{
name: '',
text: 'bar',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
]
const nextState = timeMachineReducer(state, setActiveQueryIndexSync(0))
expect(nextState.activeQueryIndex).toEqual(0)
})
test('shows the influxql editor when the active query is influxql and in advanced mode', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
state.view.properties.queries = [
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
{
name: '',
text: 'bar',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
]
const nextState = timeMachineReducer(state, setActiveQueryIndexSync(0))
expect(nextState.activeQueryIndex).toEqual(0)
})
test('shows the flux editor when the active query is flux and in advanced mode', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
state.view.properties.queries = [
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
{
name: '',
text: 'bar',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
},
]
const nextState = timeMachineReducer(state, setActiveQueryIndexSync(0))
expect(nextState.activeQueryIndex).toEqual(0)
})
})
})
describe('ADD_QUERY', () => {
test('adds a query, sets the activeQueryIndex and activeQueryEditor', () => {
const state = initialStateHelper()
state.activeQueryIndex = 0
state.draftQueries = [
{
name: '',
text: 'a',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
]
const nextState = timeMachineReducer(state, addQuerySync())
expect(nextState.activeQueryIndex).toEqual(1)
expect(nextState.draftQueries).toEqual([
{
name: '',
text: 'a',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: '',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [{key: '_measurement', values: []}],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
])
})
})
describe('REMOVE_QUERY', () => {
let queries: DashboardDraftQuery[]
beforeEach(() => {
queries = [
{
name: '',
text: 'a',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: 'b',
editMode: 'builder',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
{
name: '',
text: 'c',
editMode: 'advanced',
builderConfig: {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
},
hidden: false,
},
]
})
test('removes the query and draftScript', () => {
const state = initialStateHelper()
state.view.properties.queries = queries
state.draftQueries = queries
state.activeQueryIndex = 1
const nextState = timeMachineReducer(state, removeQuerySync(1))
expect(nextState.draftQueries).toEqual([queries[0], queries[2]])
expect(nextState.activeQueryIndex).toEqual(1)
})
test('sets the activeQueryIndex to the left if was right-most tab', () => {
const state = initialStateHelper()
state.view.properties.queries = queries
state.draftQueries = queries
state.activeQueryIndex = 2
const nextState = timeMachineReducer(state, removeQuerySync(2))
expect(nextState.draftQueries).toEqual([queries[0], queries[1]])
expect(nextState.activeQueryIndex).toEqual(1)
})
})
describe('UPDATE_ACTIVE_QUERY_NAME', () => {
test('sets the name for the activeQueryIndex', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
const builderConfig = {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
}
state.draftQueries = [
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig,
hidden: false,
},
{
name: '',
text: 'bar',
editMode: 'builder',
builderConfig,
hidden: false,
},
]
const nextState = timeMachineReducer(
state,
updateActiveQueryName('test query')
)
expect(nextState.draftQueries).toEqual([
{
name: '',
text: 'foo',
editMode: 'advanced',
builderConfig,
hidden: false,
},
{
text: 'bar',
editMode: 'builder',
name: 'test query',
builderConfig,
hidden: false,
},
])
})
})
describe('SET_TEXT_THRESHOLD_COLORING', () => {
test('sets all color types to text', () => {
const state = initialStateHelper()
state.view.properties.colors = [
{
hex: '#BF3D5E',
id: 'base',
name: 'ruby',
type: 'background',
value: 0,
},
{
hex: '#F48D38',
id: '72bad47c-cec3-4523-8f13-1fabd192ef92',
name: 'tiger',
type: 'background',
value: 22.72,
},
]
const actual = timeMachineReducer(state, setTextThresholdColoring()).view
.properties.colors
const expected = [
{
hex: '#BF3D5E',
id: 'base',
name: 'ruby',
type: 'text',
value: 0,
},
{
hex: '#F48D38',
id: '72bad47c-cec3-4523-8f13-1fabd192ef92',
name: 'tiger',
type: 'text',
value: 22.72,
},
]
expect(actual).toEqual(expected)
})
})
describe('SET_BACKGROUND_THRESHOLD_COLORING', () => {
test('sets all color types to background', () => {
const state = initialStateHelper()
state.view.properties.colors = [
{
hex: '#BF3D5E',
id: 'base',
name: 'ruby',
type: 'text',
value: 0,
},
{
hex: '#F48D38',
id: '72bad47c-cec3-4523-8f13-1fabd192ef92',
name: 'tiger',
type: 'text',
value: 22.72,
},
]
const actual = timeMachineReducer(state, setBackgroundThresholdColoring())
.view.properties.colors
const expected = [
{
hex: '#BF3D5E',
id: 'base',
name: 'ruby',
type: 'background',
value: 0,
},
{
hex: '#F48D38',
id: '72bad47c-cec3-4523-8f13-1fabd192ef92',
name: 'tiger',
type: 'background',
value: 22.72,
},
]
expect(actual).toEqual(expected)
})
})
describe('SET_AGGREGATE_WINDOW', () => {
const state = initialStateHelper()
state.activeQueryIndex = 1
const builderConfig = {
buckets: [],
tags: [],
functions: [],
aggregateWindow: {period: 'auto'},
}
const dq0 = {
name: '',
text: '',
editMode: 'advanced' as 'advanced',
builderConfig,
hidden: false,
}
const dq1 = {
name: '',
text: '',
editMode: 'builder' as 'builder',
builderConfig,
hidden: false,
}
state.draftQueries = [dq0, dq1]
const period = '15m'
const nextState = timeMachineReducer(state, selectAggregateWindow(period))
const updatedConfig = {
...builderConfig,
aggregateWindow: {period},
}
expect(nextState.draftQueries).toEqual([
dq0,
{...dq1, builderConfig: updatedConfig},
])
})
})

View File

@ -8,7 +8,7 @@ import {createView, defaultViewQuery} from 'src/shared/utils/view'
import {isConfigValid, buildQuery} from 'src/timeMachine/utils/queryBuilder'
// Constants
import {TimeMachineEnum} from 'src/timeMachine/constants'
import {TimeMachineID} from 'src/timeMachine/constants'
import {AUTOREFRESH_DEFAULT} from 'src/shared/constants'
import {
THRESHOLD_TYPE_TEXT,
@ -67,11 +67,11 @@ export interface TimeMachineState {
}
export interface TimeMachinesState {
activeTimeMachineID: TimeMachineEnum
activeTimeMachineID: TimeMachineID
timeMachines: {
[TimeMachineEnum.DE]: TimeMachineState
[TimeMachineEnum.VEO]: TimeMachineState
[TimeMachineEnum.Alerting]: TimeMachineState
['de']: TimeMachineState
['veo']: TimeMachineState
['alerting']: TimeMachineState
}
}
@ -81,7 +81,7 @@ export const initialStateHelper = (): TimeMachineState => ({
view: createView(),
draftQueries: [{...defaultViewQuery(), hidden: false}],
isViewingRawData: false,
activeTab: TimeMachineTab.Queries,
activeTab: 'queries',
activeQueryIndex: 0,
queryResults: initialQueryResultsState(),
queryBuilder: {
@ -103,11 +103,11 @@ export const initialStateHelper = (): TimeMachineState => ({
})
export const initialState = (): TimeMachinesState => ({
activeTimeMachineID: TimeMachineEnum.DE,
activeTimeMachineID: 'de',
timeMachines: {
[TimeMachineEnum.VEO]: initialStateHelper(),
[TimeMachineEnum.DE]: initialStateHelper(),
[TimeMachineEnum.Alerting]: initialStateHelper(),
veo: initialStateHelper(),
de: initialStateHelper(),
alerting: initialStateHelper(),
},
})
@ -134,7 +134,7 @@ export const timeMachinesReducer = (
[activeTimeMachineID]: {
...activeTimeMachine,
...initialState,
activeTab: TimeMachineTab.Queries,
activeTab: 'queries',
isViewingRawData: false,
activeQueryIndex: 0,
draftQueries,
@ -394,6 +394,7 @@ export const timeMachineReducer = (
case 'single-stat':
case 'line-plus-single-stat':
return setViewProperties(state, {prefix})
case 'check':
case 'xy':
return setYAxis(state, {prefix})
default:
@ -409,6 +410,7 @@ export const timeMachineReducer = (
case 'single-stat':
case 'line-plus-single-stat':
return setViewProperties(state, {suffix})
case 'check':
case 'xy':
return setYAxis(state, {suffix})
default:
@ -423,6 +425,7 @@ export const timeMachineReducer = (
case 'gauge':
case 'single-stat':
case 'scatter':
case 'check':
case 'xy':
case 'histogram':
return setViewProperties(state, {colors})
@ -804,6 +807,16 @@ export const timeMachineReducer = (
)
})
}
case 'REMOVE_CHECK': {
const view = convertView(state.view, 'xy')
return {...state, view, activeTab: 'queries'}
}
case 'ADD_CHECK': {
const view = convertView(state.view, 'check')
return {...state, view, activeTab: 'alerting'}
}
}
return state

View File

@ -3,9 +3,6 @@ export {
CheckBase,
CheckStatusLevel,
ThresholdCheck,
GreaterThreshold,
LesserThreshold,
RangeThreshold,
DeadmanCheck,
NotificationRuleBase,
NotificationRule,
@ -13,3 +10,7 @@ export {
SlackNotificationRule,
PagerDutyNotificationRule,
} from '../client'
import {Check} from '../client'
export type CheckType = Check['type']

View File

@ -1,4 +1 @@
export enum TimeMachineTab {
Queries = 'queries',
Visualization = 'visualization',
}
export type TimeMachineTab = 'queries' | 'visualization' | 'alerting'