diff --git a/ui/src/alerting/actions/checks.ts b/ui/src/alerting/actions/checks.ts index fab409a8cd..9207d47e90 100644 --- a/ui/src/alerting/actions/checks.ts +++ b/ui/src/alerting/actions/checks.ts @@ -126,53 +126,43 @@ export const saveCheckFromTimeMachine = () => async ( dispatch: Dispatch, 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) => async ( dispatch: Dispatch ) => { - 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 ( diff --git a/ui/src/alerting/components/CheckCard.tsx b/ui/src/alerting/components/CheckCard.tsx index fc4d62c8c5..f805b65424 100644 --- a/ui/src/alerting/components/CheckCard.tsx +++ b/ui/src/alerting/components/CheckCard.tsx @@ -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 = ({ onAddCheckLabel, onCreateLabel, onCloneCheck, + onNotify, check, updateCheck, deleteCheck, @@ -60,12 +64,20 @@ const CheckCard: FunctionComponent = ({ 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 = ({ 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 => { diff --git a/ui/src/alerting/components/CheckEOHeader.tsx b/ui/src/alerting/components/CheckEOHeader.tsx index fc03425482..50694c25e5 100644 --- a/ui/src/alerting/components/CheckEOHeader.tsx +++ b/ui/src/alerting/components/CheckEOHeader.tsx @@ -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 } const saveButtonClass = 'veo-header--save-cell-button' const CheckEOHeader: FC = ({name, onSetName, onCancel, onSave}) => { - const handleClickOutsideTitle = (e: MouseEvent) => { - 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) => { + if ((e.target as Element).classList.contains(saveButtonClass)) { + handleSave() + } + } + + const saveButtonStatus = + saveStatus === RemoteDataState.Loading + ? ComponentStatus.Loading + : ComponentStatus.Default + return (
@@ -59,6 +79,7 @@ const CheckEOHeader: FC = ({name, onSetName, onCancel, onSave}) => { icon={IconFont.Checkmark} color={ComponentColor.Success} size={ComponentSize.Small} + status={saveButtonStatus} onClick={onSave} testID="save-cell--button" /> diff --git a/ui/src/alerting/components/EditCheckEO.tsx b/ui/src/alerting/components/EditCheckEO.tsx index 9bf85deac7..8d4f3c850c 100644 --- a/ui/src/alerting/components/EditCheckEO.tsx +++ b/ui/src/alerting/components/EditCheckEO.tsx @@ -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 = ({ onGetCheckForTimeMachine, onUpdateTimeMachineCheck, onSetTimeMachineCheck, + onNotify, activeTimeMachineID, checkStatus, router, @@ -80,10 +84,15 @@ const EditCheckEditorOverlay: FunctionComponent = ({ 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( diff --git a/ui/src/alerting/components/NewCheckEO.tsx b/ui/src/alerting/components/NewCheckEO.tsx index 0a9a3144e9..76c1d61731 100644 --- a/ui/src/alerting/components/NewCheckEO.tsx +++ b/ui/src/alerting/components/NewCheckEO.tsx @@ -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 = ({ router, checkStatus, check, + notify, }) => { useEffect(() => { const view = createView('check') @@ -72,12 +72,17 @@ const NewCheckOverlay: FunctionComponent = ({ 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(