fix(ui): don't close check editor overlay when saving check fails

pull/14852/head
Christopher Henn 2019-08-28 10:55:56 -07:00
parent e925b0ad72
commit 3dadb2b4ce
5 changed files with 101 additions and 58 deletions

View File

@ -126,53 +126,43 @@ export const saveCheckFromTimeMachine = () => async (
dispatch: Dispatch<any>, dispatch: Dispatch<any>,
getState: GetState getState: GetState
) => { ) => {
try { const state = getState()
const state = getState() const {
const { orgs: {
orgs: { org: {id: orgID},
org: {id: orgID}, },
}, } = state
} = state
const { const {
draftQueries, draftQueries,
alerting: {check}, alerting: {check},
} = getActiveTimeMachine(state) } = getActiveTimeMachine(state)
const checkWithOrg = {...check, query: draftQueries[0], orgID} as Check const checkWithOrg = {...check, query: draftQueries[0], orgID} as Check
const resp = check.id const resp = check.id
? await api.patchCheck({checkID: check.id, data: checkWithOrg}) ? await api.patchCheck({checkID: check.id, data: checkWithOrg})
: await api.postCheck({data: checkWithOrg}) : await api.postCheck({data: checkWithOrg})
if (resp.status === 201 || resp.status === 200) { if (resp.status === 201 || resp.status === 200) {
dispatch(setCheck(resp.data)) dispatch(setCheck(resp.data))
} else { } else {
throw new Error(resp.data.message) throw new Error(resp.data.message)
}
} catch (e) {
console.error(e)
dispatch(notify(copy.createCheckFailed(e.message)))
} }
} }
export const updateCheck = (check: Partial<Check>) => async ( export const updateCheck = (check: Partial<Check>) => async (
dispatch: Dispatch<Action | NotificationAction> dispatch: Dispatch<Action | NotificationAction>
) => { ) => {
try { const resp = await api.patchCheck({checkID: check.id, data: check as Check})
const resp = await api.patchCheck({checkID: check.id, data: check as Check})
if (resp.status === 200) {
dispatch(setCheck(resp.data))
} else {
throw new Error(resp.data.message)
}
if (resp.status === 200) {
dispatch(setCheck(resp.data)) dispatch(setCheck(resp.data))
} catch (e) { } else {
console.error(e) throw new Error(resp.data.message)
dispatch(notify(copy.updateCheckFailed(e.message)))
} }
dispatch(setCheck(resp.data))
} }
export const deleteCheck = (checkID: string) => async ( export const deleteCheck = (checkID: string) => async (

View File

@ -25,6 +25,8 @@ import {
} from 'src/alerting/actions/checks' } from 'src/alerting/actions/checks'
import {createLabel as createLabelAsync} from 'src/labels/actions' import {createLabel as createLabelAsync} from 'src/labels/actions'
import {viewableLabels} from 'src/labels/selectors' import {viewableLabels} from 'src/labels/selectors'
import {notify} from 'src/shared/actions/notifications'
import {updateCheckFailed} from 'src/shared/copy/notifications'
// Types // Types
import {Check, Label, AppState, AlertHistoryType} from 'src/types' import {Check, Label, AppState, AlertHistoryType} from 'src/types'
@ -36,6 +38,7 @@ interface DispatchProps {
onRemoveCheckLabel: typeof deleteCheckLabel onRemoveCheckLabel: typeof deleteCheckLabel
onCreateLabel: typeof createLabelAsync onCreateLabel: typeof createLabelAsync
onCloneCheck: typeof cloneCheck onCloneCheck: typeof cloneCheck
onNotify: typeof notify
} }
interface StateProps { interface StateProps {
@ -53,6 +56,7 @@ const CheckCard: FunctionComponent<Props> = ({
onAddCheckLabel, onAddCheckLabel,
onCreateLabel, onCreateLabel,
onCloneCheck, onCloneCheck,
onNotify,
check, check,
updateCheck, updateCheck,
deleteCheck, deleteCheck,
@ -60,12 +64,20 @@ const CheckCard: FunctionComponent<Props> = ({
labels, labels,
router, router,
}) => { }) => {
const onUpdateName = (name: string) => { const onUpdateName = async (name: string) => {
updateCheck({id: check.id, name}) try {
await updateCheck({id: check.id, name})
} catch (e) {
onNotify(updateCheckFailed(e.message))
}
} }
const onUpdateDescription = (description: string) => { const onUpdateDescription = async (description: string) => {
updateCheck({id: check.id, description}) try {
await updateCheck({id: check.id, description})
} catch (e) {
onNotify(updateCheckFailed(e.message))
}
} }
const onDelete = () => { const onDelete = () => {
@ -76,10 +88,14 @@ const CheckCard: FunctionComponent<Props> = ({
onCloneCheck(check) onCloneCheck(check)
} }
const onToggle = () => { const onToggle = async () => {
const status = check.status === 'active' ? 'inactive' : 'active' const status = check.status === 'active' ? 'inactive' : 'active'
updateCheck({id: check.id, status}) try {
await updateCheck({id: check.id, status})
} catch (e) {
onNotify(updateCheckFailed(e.message))
}
} }
const onEdit = () => { const onEdit = () => {
@ -168,6 +184,7 @@ const mdtp: DispatchProps = {
onCreateLabel: createLabelAsync, onCreateLabel: createLabelAsync,
onRemoveCheckLabel: deleteCheckLabel, onRemoveCheckLabel: deleteCheckLabel,
onCloneCheck: cloneCheck, onCloneCheck: cloneCheck,
onNotify: notify,
} }
const mstp = ({labels}: AppState): StateProps => { const mstp = ({labels}: AppState): StateProps => {

View File

@ -1,5 +1,5 @@
// Libraries // Libraries
import React, {FC, MouseEvent} from 'react' import React, {useState, FC, MouseEvent} from 'react'
// Components // Components
import RenamablePageTitle from 'src/pageLayout/components/RenamablePageTitle' import RenamablePageTitle from 'src/pageLayout/components/RenamablePageTitle'
@ -7,6 +7,7 @@ import {
SquareButton, SquareButton,
ComponentColor, ComponentColor,
ComponentSize, ComponentSize,
ComponentStatus,
IconFont, IconFont,
Page, Page,
} from '@influxdata/clockface' } from '@influxdata/clockface'
@ -15,26 +16,45 @@ import CheckAlertingButton from 'src/alerting/components/CheckAlertingButton'
// Constants // Constants
import {DEFAULT_CHECK_NAME, CHECK_NAME_MAX_LENGTH} from 'src/alerting/constants' import {DEFAULT_CHECK_NAME, CHECK_NAME_MAX_LENGTH} from 'src/alerting/constants'
// Types
import {RemoteDataState} from 'src/types'
interface Props { interface Props {
name: string name: string
onSetName: (name: string) => void onSetName: (name: string) => void
onCancel: () => void onCancel: () => void
onSave: () => void onSave: () => Promise<void>
} }
const saveButtonClass = 'veo-header--save-cell-button' const saveButtonClass = 'veo-header--save-cell-button'
const CheckEOHeader: FC<Props> = ({name, onSetName, onCancel, onSave}) => { const CheckEOHeader: FC<Props> = ({name, onSetName, onCancel, onSave}) => {
const handleClickOutsideTitle = (e: MouseEvent<HTMLElement>) => { const [saveStatus, setSaveStatus] = useState(RemoteDataState.NotStarted)
const target = e.target as HTMLButtonElement
if (!target.className.includes(saveButtonClass)) { const handleSave = async () => {
if (saveStatus === RemoteDataState.Loading) {
return return
} }
onSave() try {
setSaveStatus(RemoteDataState.Loading)
await onSave()
} catch {
setSaveStatus(RemoteDataState.NotStarted)
}
} }
const handleClickOutsideTitle = (e: MouseEvent<HTMLElement>) => {
if ((e.target as Element).classList.contains(saveButtonClass)) {
handleSave()
}
}
const saveButtonStatus =
saveStatus === RemoteDataState.Loading
? ComponentStatus.Loading
: ComponentStatus.Default
return ( return (
<div className="veo-header"> <div className="veo-header">
<Page.Header fullWidth={true}> <Page.Header fullWidth={true}>
@ -59,6 +79,7 @@ const CheckEOHeader: FC<Props> = ({name, onSetName, onCancel, onSave}) => {
icon={IconFont.Checkmark} icon={IconFont.Checkmark}
color={ComponentColor.Success} color={ComponentColor.Success}
size={ComponentSize.Small} size={ComponentSize.Small}
status={saveButtonStatus}
onClick={onSave} onClick={onSave}
testID="save-cell--button" testID="save-cell--button"
/> />

View File

@ -19,6 +19,8 @@ import {
updateTimeMachineCheck, updateTimeMachineCheck,
} from 'src/timeMachine/actions' } from 'src/timeMachine/actions'
import {executeQueries} from 'src/timeMachine/actions/queries' import {executeQueries} from 'src/timeMachine/actions/queries'
import {notify} from 'src/shared/actions/notifications'
import {updateCheckFailed} from 'src/shared/copy/notifications'
// Types // Types
import { import {
@ -37,6 +39,7 @@ interface DispatchProps {
onSetActiveTimeMachine: typeof setActiveTimeMachine onSetActiveTimeMachine: typeof setActiveTimeMachine
onSetTimeMachineCheck: typeof setTimeMachineCheck onSetTimeMachineCheck: typeof setTimeMachineCheck
onExecuteQueries: typeof executeQueries onExecuteQueries: typeof executeQueries
onNotify: typeof notify
} }
interface StateProps { interface StateProps {
@ -55,6 +58,7 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
onGetCheckForTimeMachine, onGetCheckForTimeMachine,
onUpdateTimeMachineCheck, onUpdateTimeMachineCheck,
onSetTimeMachineCheck, onSetTimeMachineCheck,
onNotify,
activeTimeMachineID, activeTimeMachineID,
checkStatus, checkStatus,
router, router,
@ -80,10 +84,15 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
onSetTimeMachineCheck(RemoteDataState.NotStarted, null) onSetTimeMachineCheck(RemoteDataState.NotStarted, null)
} }
const handleSave = () => { const handleSave = async () => {
// todo: update view when check has own view // todo: update view when check has own view
onUpdateCheck({...check, query}) try {
handleClose() await onUpdateCheck({...check, query})
handleClose()
} catch (e) {
console.error(e)
onNotify(updateCheckFailed(e.message))
}
} }
let loadingStatus = RemoteDataState.Loading let loadingStatus = RemoteDataState.Loading
@ -144,6 +153,7 @@ const mdtp: DispatchProps = {
onSetActiveTimeMachine: setActiveTimeMachine, onSetActiveTimeMachine: setActiveTimeMachine,
onGetCheckForTimeMachine: getCheckForTimeMachine, onGetCheckForTimeMachine: getCheckForTimeMachine,
onExecuteQueries: executeQueries, onExecuteQueries: executeQueries,
onNotify: notify,
} }
export default connect<StateProps, DispatchProps, {}>( export default connect<StateProps, DispatchProps, {}>(

View File

@ -9,15 +9,14 @@ import CheckEOHeader from 'src/alerting/components/CheckEOHeader'
import TimeMachine from 'src/timeMachine/components/TimeMachine' import TimeMachine from 'src/timeMachine/components/TimeMachine'
// Actions // Actions
import { import {saveCheckFromTimeMachine} from 'src/alerting/actions/checks'
updateCheck,
saveCheckFromTimeMachine,
} from 'src/alerting/actions/checks'
import { import {
setActiveTimeMachine, setActiveTimeMachine,
updateTimeMachineCheck, updateTimeMachineCheck,
setTimeMachineCheck, setTimeMachineCheck,
} from 'src/timeMachine/actions' } from 'src/timeMachine/actions'
import {createCheckFailed} from 'src/shared/copy/notifications'
import {notify} from 'src/shared/actions/notifications'
// Utils // Utils
import {createView} from 'src/shared/utils/view' import {createView} from 'src/shared/utils/view'
@ -28,11 +27,11 @@ import {Check, AppState, RemoteDataState, CheckViewProperties} from 'src/types'
import {DEFAULT_THRESHOLD_CHECK} from 'src/alerting/constants' import {DEFAULT_THRESHOLD_CHECK} from 'src/alerting/constants'
interface DispatchProps { interface DispatchProps {
updateCheck: typeof updateCheck
setTimeMachineCheck: typeof setTimeMachineCheck setTimeMachineCheck: typeof setTimeMachineCheck
updateTimeMachineCheck: typeof updateTimeMachineCheck updateTimeMachineCheck: typeof updateTimeMachineCheck
onSetActiveTimeMachine: typeof setActiveTimeMachine onSetActiveTimeMachine: typeof setActiveTimeMachine
saveCheckFromTimeMachine: typeof saveCheckFromTimeMachine saveCheckFromTimeMachine: typeof saveCheckFromTimeMachine
notify: typeof notify
} }
interface StateProps { interface StateProps {
@ -51,6 +50,7 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
router, router,
checkStatus, checkStatus,
check, check,
notify,
}) => { }) => {
useEffect(() => { useEffect(() => {
const view = createView<CheckViewProperties>('check') const view = createView<CheckViewProperties>('check')
@ -72,12 +72,17 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
router.push(`/orgs/${params.orgID}/alerting`) router.push(`/orgs/${params.orgID}/alerting`)
} }
const handleSave = () => { const handleSave = async () => {
// todo: when check has own view // todo: when check has own view
// save view as view // save view as view
// put view.id on check.viewID // put view.id on check.viewID
saveCheckFromTimeMachine() try {
handleClose() await saveCheckFromTimeMachine()
handleClose()
} catch (e) {
console.error(e)
notify(createCheckFailed(e.message))
}
} }
return ( return (
@ -112,11 +117,11 @@ const mstp = (state: AppState): StateProps => {
} }
const mdtp: DispatchProps = { const mdtp: DispatchProps = {
updateCheck: updateCheck,
setTimeMachineCheck: setTimeMachineCheck, setTimeMachineCheck: setTimeMachineCheck,
updateTimeMachineCheck: updateTimeMachineCheck, updateTimeMachineCheck: updateTimeMachineCheck,
onSetActiveTimeMachine: setActiveTimeMachine, onSetActiveTimeMachine: setActiveTimeMachine,
saveCheckFromTimeMachine: saveCheckFromTimeMachine, saveCheckFromTimeMachine: saveCheckFromTimeMachine,
notify: notify,
} }
export default connect<StateProps, DispatchProps, {}>( export default connect<StateProps, DispatchProps, {}>(