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 spacepull/14567/head
parent
b303d54584
commit
bca1af25a9
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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={
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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, {}>(
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
const CheckMatchingRulesCard: FunctionComponent = () => {
|
||||
return <div>Matching Rules go here</div>
|
||||
}
|
||||
|
||||
export default CheckMatchingRulesCard
|
|
@ -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)
|
|
@ -0,0 +1,8 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
const CheckThresholdsCard: FunctionComponent = () => {
|
||||
return <div>Thresholds go here</div>
|
||||
}
|
||||
|
||||
export default CheckThresholdsCard
|
|
@ -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
|
|
@ -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)
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -29,10 +29,9 @@ const Y_COLUMN = '_value'
|
|||
|
||||
const THRESHOLDS: Threshold[] = [
|
||||
{
|
||||
type: 'greater',
|
||||
allValues: false,
|
||||
level: 'UNKNOWN',
|
||||
value: 20,
|
||||
lowerBound: 20,
|
||||
allValues: false,
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -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])}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -69,7 +69,6 @@ const ViewSwitcher: FunctionComponent<Props> = ({
|
|||
)}
|
||||
</LatestValueTransform>
|
||||
)
|
||||
|
||||
case 'xy':
|
||||
return (
|
||||
<XYPlot
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
export enum TimeMachineEnum {
|
||||
DE = 'de',
|
||||
VEO = 'veo',
|
||||
Alerting = 'alerting',
|
||||
}
|
||||
export type TimeMachineID = 'de' | 'veo' | 'alerting'
|
||||
|
|
|
@ -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},
|
||||
])
|
||||
})
|
||||
})
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
export enum TimeMachineTab {
|
||||
Queries = 'queries',
|
||||
Visualization = 'visualization',
|
||||
}
|
||||
export type TimeMachineTab = 'queries' | 'visualization' | 'alerting'
|
||||
|
|
Loading…
Reference in New Issue