import { Grid, menuClasses as muiMenuClasses } from '@mui/material'; import { AxiosError } from 'axios'; import { FC, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; import ActionGroup from '../ActionGroup'; import api from '../../lib/api'; import FlexBox from '../FlexBox'; import FormSummary from '../FormSummary'; import handleAPIError from '../../lib/handleAPIError'; import mailRecipientListSchema from './schema'; import ManageAlertOverride from './ManageAlertOverride'; import MessageGroup from '../MessageGroup'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; import SelectWithLabel from '../SelectWithLabel'; import { BodyText, SmallText } from '../Text'; import UncontrolledInput from '../UncontrolledInput'; import useFormikUtils from '../../hooks/useFormikUtils'; /** * TODO: add descriptions to each item: * =head4 1 / critical Alerts at this level will go to all recipients, except for those ignoring the source system entirely. This is reserved for alerts that could lead to imminent service interruption or unexpected loss of redundancy. Alerts at this level should trigger alarm systems for all administrators as well as management who may be impacted by service interruptions. =head4 2 / warning This is used for alerts that require attention from administrators. Examples include intentional loss of redundancy caused by load shedding, hardware in pre-failure, loss of input power, temperature anomalies, etc. Alerts at this level should trigger alarm systems for administrative staff. =head4 3 / notice This is used for alerts that are generally safe to ignore, but might provide early warnings of developing issues or insight into system behaviour. Alerts at this level should not trigger alarm systems. Periodic review is sufficient. =head4 4 / info This is used for alerts that are almost always safe to ignore, but may be useful in testing and debugging. * */ const LEVEL_OPTIONS: SelectItem[] = [ { displayValue: ( Critical Alerts that could lead to imminent service interruption or unexpected loss of redundancy. ), value: 1, }, { displayValue: ( Warning Alerts that require attention from administrators, such as redundancy loss due to load shedding, hardware in pre-failure, input power loss, temperature anomalies, etc. ), value: 2, }, { displayValue: ( Notice Alerts that are generally safe to ignore, but might provide early warnings of developing issues or insight into system behaviour. ), value: 3, }, { displayValue: ( Info Alerts that are almost always safe to ignore, but may be useful in testing and debugging. ), value: 4, }, ]; const MAP_TO_LEVEL_LABEL: Record = { 1: 'Critical', 2: 'Warning', 3: 'Notice', 4: 'Info', }; const getAlertOverrideRequestList = ( current: MailRecipientFormikMailRecipient, initial?: MailRecipientFormikMailRecipient, urlPrefix = '/alert-override', ): AlertOverrideRequest[] => { const { uuid: mailRecipientUuid } = current; if (!mailRecipientUuid) return []; return Object.values(current.alertOverrides).reduce( (previous, { remove, level, target, uuids: existingOverrides }) => { /** * There's no update, just delete every record and create the new records. * * This is not optimal, but keep it until there's a better solution. */ if (existingOverrides) { previous.push( ...Object.keys(existingOverrides).map( (overrideUuid) => ({ method: 'delete', url: `${urlPrefix}/${overrideUuid}`, }), ), ); } if (target && !remove) { const newHosts: string[] = target.subnodes ?? [target.uuid]; previous.push( ...newHosts.map((hostUuid) => ({ body: { hostUuid, level, mailRecipientUuid }, method: 'post', url: urlPrefix, })), ); } return previous; }, [], ); }; const AddMailRecipientForm: FC = (props) => { const { alertOverrideTargetOptions, mailRecipientUuid, previousFormikValues, tools, } = props; const mrUuid = useMemo( () => mailRecipientUuid ?? uuidv4(), [mailRecipientUuid], ); const formikUtils = useFormikUtils({ initialValues: previousFormikValues ?? { [mrUuid]: { alertOverrides: {}, email: '', language: 'en_CA', level: 2, name: '', }, }, onSubmit: (values, { setSubmitting }) => { const { [mrUuid]: mailRecipient } = values; let actionProceedText: string = 'Add'; let errorMessage: React.ReactNode = <>Failed to add mail recipient.; let method: 'post' | 'put' = 'post'; let successMessage: React.ReactNode = <>Mail recipient added.; let titleText: string = `Add mail recipient with the following?`; let url: string = '/mail-recipient'; if (previousFormikValues) { actionProceedText = 'Update'; errorMessage = <>Failed to update mail server.; method = 'put'; successMessage = <>Mail recipient updated.; titleText = `Update ${mailRecipient.name} with the following?`; url += `/${mrUuid}`; } const { alertOverrides, uuid: ignore, ...mrBody } = mailRecipient; tools.confirm.prepare({ actionProceedText, content: ( <> >((previous, [valueId, value]) => { if (value.remove || !value.target) return previous; previous[valueId] = { level: value.level, name: value.target.name, }; return previous; }, {}), }} /> ), onCancelAppend: () => setSubmitting(false), onProceedAppend: async () => { tools.confirm.loading(true); const handleError = (error: AxiosError) => { const emsg = handleAPIError(error); emsg.children = ( <> {errorMessage} {emsg.children} ); tools.confirm.finish('Error', emsg); setSubmitting(false); }; // Handle the mail recipient first, wait until it's done to process // the related alert override records. api[method](url, mrBody) .then((response) => { const { data } = response; const shallow = { ...mailRecipient }; if (data) { shallow.uuid = data.uuid; } const initial = previousFormikValues && previousFormikValues[mrUuid]; const promises = getAlertOverrideRequestList( shallow, initial, ).map((request) => api[request.method](request.url, request.body), ); Promise.all(promises) .then(() => { tools.confirm.finish('Success', { children: successMessage }); tools[method === 'post' ? 'add' : 'edit'].open(false); }) .catch(handleError); }) .catch(handleError); }, titleText, }); tools.confirm.open(true); }, validationSchema: mailRecipientListSchema, }); const { disabledSubmit, formik, formikErrors, handleChange } = formikUtils; const emailChain = useMemo(() => `${mrUuid}.email`, [mrUuid]); const levelChain = useMemo(() => `${mrUuid}.level`, [mrUuid]); const nameChain = useMemo(() => `${mrUuid}.name`, [mrUuid]); return ( { event.preventDefault(); formik.submitForm(); }} spacing="1em" > } /> } /> MAP_TO_LEVEL_LABEL[value], }} value={formik.values[mrUuid].level} /> } /> ); }; export default AddMailRecipientForm;