diff --git a/apps/client/src/common/api/apiUtils.ts b/apps/client/src/common/api/apiUtils.ts new file mode 100644 index 000000000..613df45cc --- /dev/null +++ b/apps/client/src/common/api/apiUtils.ts @@ -0,0 +1,20 @@ +import axios, { AxiosError } from 'axios'; +import { LogLevel } from 'ontime-types'; +import { generateId, millisToString } from 'ontime-utils'; + +import { addLog } from '../stores/logger'; +import { nowInMillis } from '../utils/time'; + +export function logAxiosError(prepend: string, error: unknown) { + const message = axios.isAxiosError(error) + ? `${prepend}, ${(error as AxiosError).response?.statusText}: ${(error as AxiosError).response?.data}` + : `${prepend}: ${error}`; + + addLog({ + id: generateId(), + origin: 'SERVER', + time: millisToString(nowInMillis()), + level: LogLevel.Error, + text: message, + }); +} diff --git a/apps/client/src/common/api/eventsApi.ts b/apps/client/src/common/api/eventsApi.ts index fd8c406bd..c6ad73f67 100644 --- a/apps/client/src/common/api/eventsApi.ts +++ b/apps/client/src/common/api/eventsApi.ts @@ -28,14 +28,6 @@ export async function requestPutEvent(data: Partial) { return axios.put(rundownURL, data); } -/** - * @description HTTP request to modify event - * @return {Promise} - */ -export async function requestPatchEvent(data: OntimeRundownEntry) { - return axios.patch(rundownURL, data); -} - export type ReorderEntry = { eventId: string; from: number; diff --git a/apps/client/src/common/hooks-query/useOscSettings.ts b/apps/client/src/common/hooks-query/useOscSettings.ts index 610bc40a3..5b55556e2 100644 --- a/apps/client/src/common/hooks-query/useOscSettings.ts +++ b/apps/client/src/common/hooks-query/useOscSettings.ts @@ -3,6 +3,7 @@ import { OSCSettings } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { OSC_SETTINGS } from '../api/apiConstants'; +import { logAxiosError } from '../api/apiUtils'; import { getOSC, postOSC, postOscSubscriptions } from '../api/ontimeApi'; import { oscPlaceholderSettings } from '../models/OscSettings'; import { ontimeQueryClient } from '../queryClient'; @@ -25,6 +26,7 @@ export default function useOscSettings() { export function useOscSettingsMutation() { const { isLoading, mutateAsync } = useMutation({ mutationFn: postOSC, + onError: (error) => logAxiosError('Error saving OSC settings', error), onSuccess: (res) => ontimeQueryClient.setQueryData(OSC_SETTINGS, res.data), onSettled: () => ontimeQueryClient.invalidateQueries({ queryKey: OSC_SETTINGS }), }); @@ -34,6 +36,7 @@ export function useOscSettingsMutation() { export function usePostOscSubscriptions() { const { isLoading, mutateAsync } = useMutation({ mutationFn: postOscSubscriptions, + onError: (error) => logAxiosError('Error saving OSC settings', error), onSettled: () => ontimeQueryClient.invalidateQueries({ queryKey: OSC_SETTINGS }), }); return { isLoading, mutateAsync }; diff --git a/apps/client/src/common/hooks/useEventAction.ts b/apps/client/src/common/hooks/useEventAction.ts index 802ee0f0c..4d3085774 100644 --- a/apps/client/src/common/hooks/useEventAction.ts +++ b/apps/client/src/common/hooks/useEventAction.ts @@ -1,9 +1,9 @@ import { useCallback } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import axios, { AxiosError } from 'axios'; import { OntimeRundown, OntimeRundownEntry, SupportedEvent } from 'ontime-types'; import { RUNDOWN_TABLE, RUNDOWN_TABLE_KEY } from '../api/apiConstants'; +import { logAxiosError } from '../api/apiUtils'; import { ReorderEntry, requestApplyDelay, @@ -14,14 +14,12 @@ import { requestReorderEvent, } from '../api/eventsApi'; import { useLocalEvent } from '../stores/localEvent'; -import { useEmitLog } from '../stores/logger'; /** * @description Set of utilities for events */ export const useEventAction = () => { const queryClient = useQueryClient(); - const { emitError } = useEmitLog(); const eventSettings = useLocalEvent((state) => state.eventSettings); const defaultPublic = eventSettings.defaultPublic; const startTimeIsLastEnd = eventSettings.startTimeIsLastEnd; @@ -94,14 +92,10 @@ export const useEventAction = () => { // @ts-expect-error -- we know that the object is well formed now await _addEventMutation.mutateAsync(newEvent); } catch (error) { - if (!axios.isAxiosError(error)) { - emitError(`Error fetching data: ${(error as AxiosError).message}`); - } else { - emitError(`Error fetching data: ${error}`); - } + logAxiosError('Error fetching data', error); } }, - [_addEventMutation, defaultPublic, emitError, queryClient, startTimeIsLastEnd], + [_addEventMutation, defaultPublic, queryClient, startTimeIsLastEnd], ); /** @@ -144,14 +138,10 @@ export const useEventAction = () => { try { await _updateEventMutation.mutateAsync(event); } catch (error) { - if (!axios.isAxiosError(error)) { - emitError(`Error updating event: ${(error as AxiosError).message}`); - } else { - emitError(`Error updating event: ${error}`); - } + logAxiosError('Error updating event', error); } }, - [_updateEventMutation, emitError], + [_updateEventMutation], ); /** @@ -196,14 +186,10 @@ export const useEventAction = () => { try { await _deleteEventMutation.mutateAsync(eventId); } catch (error) { - if (!axios.isAxiosError(error)) { - emitError(`Error deleting event: ${(error as AxiosError).message}`); - } else { - emitError(`Error deleting event: ${error}`); - } + logAxiosError('Error deleting event', error); } }, - [_deleteEventMutation, emitError], + [_deleteEventMutation], ); /** @@ -245,13 +231,9 @@ export const useEventAction = () => { try { await _deleteAllEventsMutation.mutateAsync(); } catch (error) { - if (!axios.isAxiosError(error)) { - emitError(`Error deleting events: ${(error as AxiosError).message}`); - } else { - emitError(`Error deleting events: ${error}`); - } + logAxiosError('Error deleting events', error); } - }, [_deleteAllEventsMutation, emitError]); + }, [_deleteAllEventsMutation]); /** * Calls mutation to apply a delay @@ -273,14 +255,10 @@ export const useEventAction = () => { try { await _applyDelayMutation.mutateAsync(delayEventId); } catch (error) { - if (!axios.isAxiosError(error)) { - emitError(`Error applying delay: ${(error as AxiosError).message}`); - } else { - emitError(`Error applying delay: ${error}`); - } + logAxiosError('Error applying delay', error); } }, - [_applyDelayMutation, emitError], + [_applyDelayMutation], ); /** @@ -332,14 +310,10 @@ export const useEventAction = () => { }; await _reorderEventMutation.mutateAsync(reorderObject); } catch (error) { - if (!axios.isAxiosError(error)) { - emitError(`Error re-ordering event: ${(error as AxiosError).message}`); - } else { - emitError(`Error re-ordering event: ${error}`); - } + logAxiosError('Error re-ordering event', error); } }, - [_reorderEventMutation, emitError], + [_reorderEventMutation], ); return { addEvent, updateEvent, deleteEvent, deleteAllEvents, applyDelay, reorderEvent }; diff --git a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx index c542adff1..d25730737 100644 --- a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx +++ b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx @@ -5,6 +5,7 @@ import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline'; import { IoRemove } from '@react-icons/all-files/io5/IoRemove'; import { Alias } from 'ontime-types'; +import { logAxiosError } from '../../../common/api/apiUtils'; import { postAliases } from '../../../common/api/ontimeApi'; import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn'; import useAliases from '../../../common/hooks-query/useAliases'; @@ -53,7 +54,7 @@ export default function AliasesForm() { try { await postAliases(formData.aliases); } catch (error) { - emitError(`Error saving aliases: ${error}`); + logAxiosError('Error saving aliases', error); } finally { await refetch(); } diff --git a/apps/client/src/features/modals/settings-modal/AppSettings.tsx b/apps/client/src/features/modals/settings-modal/AppSettings.tsx index cd9456391..2892faa9f 100644 --- a/apps/client/src/features/modals/settings-modal/AppSettings.tsx +++ b/apps/client/src/features/modals/settings-modal/AppSettings.tsx @@ -3,9 +3,9 @@ import { useForm } from 'react-hook-form'; import { Input, Select } from '@chakra-ui/react'; import type { Settings } from 'ontime-types'; +import { logAxiosError } from '../../../common/api/apiUtils'; import { postSettings } from '../../../common/api/ontimeApi'; import useSettings from '../../../common/hooks-query/useSettings'; -import { useEmitLog } from '../../../common/stores/logger'; import { isOnlyNumbers } from '../../../common/utils/regex'; import ModalLoader from '../modal-loader/ModalLoader'; import ModalSplitInput from '../ModalSplitInput'; @@ -17,7 +17,6 @@ import style from './SettingsModal.module.scss'; export default function AppSettingsModal() { const { data, status, isFetching, refetch } = useSettings(); - const { emitError } = useEmitLog(); const { handleSubmit, register, @@ -41,7 +40,7 @@ export default function AppSettingsModal() { try { await postSettings(formData); } catch (error) { - emitError(`Error saving settings: ${error}`); + logAxiosError('Error saving settings', error); } finally { await refetch(); } diff --git a/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx index c25cebd1e..9d997b0cd 100644 --- a/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx @@ -3,9 +3,9 @@ import { useForm } from 'react-hook-form'; import { Alert, AlertDescription, AlertIcon, AlertTitle, Input } from '@chakra-ui/react'; import { UserFields } from 'ontime-types'; +import { logAxiosError } from '../../../common/api/apiUtils'; import { postUserFields } from '../../../common/api/ontimeApi'; import useUserFields from '../../../common/hooks-query/useUserFields'; -import { useEmitLog } from '../../../common/stores/logger'; import ModalLoader from '../modal-loader/ModalLoader'; import { inputProps } from '../modalHelper'; import ModalLink from '../ModalLink'; @@ -18,7 +18,6 @@ const userFieldsDocsUrl = 'https://ontime.gitbook.io/v2/features/user-fields'; export default function CuesheetSettingsForm() { const { data, status, isFetching, refetch } = useUserFields(); - const { emitError } = useEmitLog(); const { handleSubmit, register, @@ -42,7 +41,7 @@ export default function CuesheetSettingsForm() { try { await postUserFields(formData); } catch (error) { - emitError(`Error saving cuesheet settings: ${error}`); + logAxiosError('Error saving cuesheet settings', error); } finally { await refetch(); } diff --git a/apps/client/src/features/modals/settings-modal/EventDataForm.tsx b/apps/client/src/features/modals/settings-modal/EventDataForm.tsx index 90f70e51d..d39d5d705 100644 --- a/apps/client/src/features/modals/settings-modal/EventDataForm.tsx +++ b/apps/client/src/features/modals/settings-modal/EventDataForm.tsx @@ -3,9 +3,9 @@ import { useForm } from 'react-hook-form'; import { Input, Textarea } from '@chakra-ui/react'; import { EventData } from 'ontime-types'; +import { logAxiosError } from '../../../common/api/apiUtils'; import { postEventData } from '../../../common/api/eventDataApi'; import useEventData from '../../../common/hooks-query/useEventData'; -import { useEmitLog } from '../../../common/stores/logger'; import ModalLoader from '../modal-loader/ModalLoader'; import { inputProps } from '../modalHelper'; import ModalInput from '../ModalInput'; @@ -15,7 +15,6 @@ import style from './SettingsModal.module.scss'; export default function EventDataForm() { const { data, status, isFetching, refetch } = useEventData(); - const { emitError } = useEmitLog(); const { handleSubmit, register, @@ -39,7 +38,7 @@ export default function EventDataForm() { try { await postEventData(formData); } catch (error) { - emitError(`Error saving event settings: ${error}`); + logAxiosError('Error saving event settings', error); } finally { await refetch(); } diff --git a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx index 59b2ecd0c..3ab21dd32 100644 --- a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx @@ -3,15 +3,15 @@ import { useForm } from 'react-hook-form'; import { Input, Switch } from '@chakra-ui/react'; import { ViewSettings } from 'ontime-types'; +import { logAxiosError } from '../../../common/api/apiUtils'; import { postViewSettings } from '../../../common/api/ontimeApi'; import { PopoverPickerRHF } from '../../../common/components/input/popover-picker/PopoverPicker'; import useViewSettings from '../../../common/hooks-query/useViewSettings'; -import { useEmitLog } from '../../../common/stores/logger'; import { mtm } from '../../../common/utils/timeConstants'; +import ModalLoader from '../modal-loader/ModalLoader'; import { inputProps } from '../modalHelper'; import ModalInput from '../ModalInput'; import ModalSplitInput from '../ModalSplitInput'; -import ModalLoader from '../modal-loader/ModalLoader'; import OntimeModalFooter from '../OntimeModalFooter'; import InputMillisWithString from './InputMillisWithString'; @@ -20,7 +20,6 @@ import style from './SettingsModal.module.scss'; export default function ViewSettingsForm() { const { data, status, refetch, isFetching } = useViewSettings(); - const { emitError } = useEmitLog(); const { control, handleSubmit, @@ -60,7 +59,7 @@ export default function ViewSettingsForm() { try { await postViewSettings(newData); } catch (error) { - emitError(`Error saving view settings: ${error}`); + logAxiosError('Error saving view settings', error); } finally { await refetch(); }