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

View File

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

View File

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

View File

@ -19,6 +19,8 @@ import {
updateTimeMachineCheck,
} from 'src/timeMachine/actions'
import {executeQueries} from 'src/timeMachine/actions/queries'
import {notify} from 'src/shared/actions/notifications'
import {updateCheckFailed} from 'src/shared/copy/notifications'
// Types
import {
@ -37,6 +39,7 @@ interface DispatchProps {
onSetActiveTimeMachine: typeof setActiveTimeMachine
onSetTimeMachineCheck: typeof setTimeMachineCheck
onExecuteQueries: typeof executeQueries
onNotify: typeof notify
}
interface StateProps {
@ -55,6 +58,7 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
onGetCheckForTimeMachine,
onUpdateTimeMachineCheck,
onSetTimeMachineCheck,
onNotify,
activeTimeMachineID,
checkStatus,
router,
@ -80,10 +84,15 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
onSetTimeMachineCheck(RemoteDataState.NotStarted, null)
}
const handleSave = () => {
const handleSave = async () => {
// todo: update view when check has own view
onUpdateCheck({...check, query})
handleClose()
try {
await onUpdateCheck({...check, query})
handleClose()
} catch (e) {
console.error(e)
onNotify(updateCheckFailed(e.message))
}
}
let loadingStatus = RemoteDataState.Loading
@ -144,6 +153,7 @@ const mdtp: DispatchProps = {
onSetActiveTimeMachine: setActiveTimeMachine,
onGetCheckForTimeMachine: getCheckForTimeMachine,
onExecuteQueries: executeQueries,
onNotify: notify,
}
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'
// Actions
import {
updateCheck,
saveCheckFromTimeMachine,
} from 'src/alerting/actions/checks'
import {saveCheckFromTimeMachine} from 'src/alerting/actions/checks'
import {
setActiveTimeMachine,
updateTimeMachineCheck,
setTimeMachineCheck,
} from 'src/timeMachine/actions'
import {createCheckFailed} from 'src/shared/copy/notifications'
import {notify} from 'src/shared/actions/notifications'
// Utils
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'
interface DispatchProps {
updateCheck: typeof updateCheck
setTimeMachineCheck: typeof setTimeMachineCheck
updateTimeMachineCheck: typeof updateTimeMachineCheck
onSetActiveTimeMachine: typeof setActiveTimeMachine
saveCheckFromTimeMachine: typeof saveCheckFromTimeMachine
notify: typeof notify
}
interface StateProps {
@ -51,6 +50,7 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
router,
checkStatus,
check,
notify,
}) => {
useEffect(() => {
const view = createView<CheckViewProperties>('check')
@ -72,12 +72,17 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
router.push(`/orgs/${params.orgID}/alerting`)
}
const handleSave = () => {
const handleSave = async () => {
// todo: when check has own view
// save view as view
// put view.id on check.viewID
saveCheckFromTimeMachine()
handleClose()
try {
await saveCheckFromTimeMachine()
handleClose()
} catch (e) {
console.error(e)
notify(createCheckFailed(e.message))
}
}
return (
@ -112,11 +117,11 @@ const mstp = (state: AppState): StateProps => {
}
const mdtp: DispatchProps = {
updateCheck: updateCheck,
setTimeMachineCheck: setTimeMachineCheck,
updateTimeMachineCheck: updateTimeMachineCheck,
onSetActiveTimeMachine: setActiveTimeMachine,
saveCheckFromTimeMachine: saveCheckFromTimeMachine,
notify: notify,
}
export default connect<StateProps, DispatchProps, {}>(