Skip to content

Commit

Permalink
Refactor DisplayedError to handle class and use case outside Redux
Browse files Browse the repository at this point in the history
  • Loading branch information
ivangabriele committed Feb 15, 2024
1 parent 967927c commit 2898d62
Show file tree
Hide file tree
Showing 18 changed files with 130 additions and 81 deletions.
1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"test:multi-windows:run": "IS_HEADLESS=true jest --config=./config/multi-windows/jest.config.js --detectOpenHandles",
"test:lint": "eslint ./src",
"test:lint:partial": "eslint --config=./.eslintrc.partial.js ./src ./cypress ./puppeteer",
"test:lint:partial:bis": "eslint --config=./.eslintrc.partial.js ./src/features/Mission",
"test:lint:partial:fix": "npm run test:lint:partial -- --fix",
"test:type": "tsc",
"test:unit": "jest --config=./config/jest.config.js --detectOpenHandles",
Expand Down
33 changes: 26 additions & 7 deletions frontend/src/domain/shared_slices/DisplayedError.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { UseCaseStore } from '@libs/UseCaseStore'
import { createSlice } from '@reduxjs/toolkit'
import { ensure } from '@utils/ensure'

import { DisplayedError } from '../../libs/DisplayedError'

import type { DisplayedError } from '@libs/DisplayedError'
import type { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import type { PayloadAction } from '@reduxjs/toolkit'

export type DisplayedErrorState = {
missionFormError: DisplayedError | undefined
vesselSidebarError: DisplayedError | undefined
type DisplayedErrorStateValue = {
hasRetryableUseCase: boolean
message: string
}

export type DisplayedErrorState = Record<DisplayedErrorKey, DisplayedErrorStateValue | undefined>
export const INITIAL_STATE: DisplayedErrorState = {
missionFormError: undefined,
vesselSidebarError: undefined
Expand All @@ -17,16 +21,31 @@ const displayedErrorSlice = createSlice({
initialState: INITIAL_STATE,
name: 'displayedError',
reducers: {
set(state, action: PayloadAction<Partial<DisplayedErrorState>>) {
set(
state,
action: PayloadAction<
Partial<{
[key in DisplayedErrorKey]: DisplayedError
}>
>
) {
Object.keys(action.payload).forEach(key => {
state[key] = action.payload[key]
const typedKey = key as DisplayedErrorKey
const displayedError = ensure(action.payload[typedKey], `action.payload[${typedKey}]`)

state[typedKey] = {
hasRetryableUseCase: displayedError.hasRetryableUseCase,
message: displayedError.message
}
})
},

unset(state, action: PayloadAction<keyof DisplayedErrorState | Array<keyof DisplayedErrorState>>) {
const keys = Array.isArray(action.payload) ? action.payload : [action.payload]

keys.forEach(key => {
UseCaseStore.unset(key)

state[key] = undefined
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'

import openBeaconMalfunction from './openBeaconMalfunction'
import { getVesselBeaconsMalfunctionsFromAPI } from '../../../api/beaconMalfunction'
import { getOnlyVesselIdentityProperties } from '../../entities/vessel/vessel'
Expand All @@ -24,7 +26,7 @@ export const getVesselBeaconMalfunctions = (isFromUserAction: boolean) => async

if (isFromUserAction) {
dispatch(loadVesselBeaconMalfunctions())
dispatch(displayedErrorActions.unset('vesselSidebarError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.VESSEL_SIDEBAR_ERROR))
}

try {
Expand All @@ -50,7 +52,7 @@ export const getVesselBeaconMalfunctions = (isFromUserAction: boolean) => async
error,
() => getVesselBeaconMalfunctions(isFromUserAction),
isFromUserAction,
'vesselSidebarError'
DisplayedErrorKey.VESSEL_SIDEBAR_ERROR
)
)
dispatch(resetVesselBeaconMalfunctionsResumeAndHistory())
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/domain/use_cases/error/displayOrLogError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ export const displayOrLogError =
return
}

const displayedError = new DisplayedError(errorMessage, retryableUseCase)
const displayedError = new DisplayedError(errorBoundaryKey, errorMessage, retryableUseCase)
dispatch(displayedErrorActions.set({ [errorBoundaryKey]: displayedError }))
}
15 changes: 0 additions & 15 deletions frontend/src/domain/use_cases/error/retry.ts

This file was deleted.

11 changes: 9 additions & 2 deletions frontend/src/domain/use_cases/mission/getVesselControls.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'

import { getVesselControlsFromAPI } from '../../../api/missionAction'
import NoControlsFoundError from '../../../errors/NoControlsFoundError'
import {
Expand Down Expand Up @@ -28,7 +30,7 @@ export const getVesselControls = (isFromUserAction: boolean) => async (dispatch,

const isSameVesselAsCurrentlyShowed = getIsSameVesselAsCurrentlyShowed(selectedVessel.vesselId, currentControlSummary)
if (isFromUserAction) {
dispatch(displayedErrorActions.unset('vesselSidebarError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.VESSEL_SIDEBAR_ERROR))
dispatch(loadControls())
}

Expand All @@ -44,7 +46,12 @@ export const getVesselControls = (isFromUserAction: boolean) => async (dispatch,
dispatch(removeError())
} catch (error) {
dispatch(
displayOrLogError(error, () => getVesselControls(isFromUserAction), isFromUserAction, 'vesselSidebarError')
displayOrLogError(
error,
() => getVesselControls(isFromUserAction),
isFromUserAction,
DisplayedErrorKey.VESSEL_SIDEBAR_ERROR
)
)
dispatch(resetLoadControls())
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/domain/use_cases/vessel/getVesselReportings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'

import { getVesselReportingsFromAPI } from '../../../api/vessel'
import {
loadReporting,
Expand All @@ -17,7 +19,7 @@ export const getVesselReportings = (isFromUserAction: boolean) => async (dispatc
}

if (isFromUserAction) {
dispatch(displayedErrorActions.unset('vesselSidebarError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.VESSEL_SIDEBAR_ERROR))
dispatch(loadReporting())
}

Expand All @@ -40,7 +42,7 @@ export const getVesselReportings = (isFromUserAction: boolean) => async (dispatc
error as Error,
() => getVesselReportings(isFromUserAction),
isFromUserAction,
'vesselSidebarError'
DisplayedErrorKey.VESSEL_SIDEBAR_ERROR
)
)
dispatch(resetCurrentAndArchivedReportingsOfSelectedVessel())
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/domain/use_cases/vessel/showVessel.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'

import { getVesselFromAPI } from '../../../api/vessel'
import { logbookActions } from '../../../features/Logbook/slice'
import { addVesselIdentifierToVesselIdentity } from '../../../features/VesselSearch/utils'
Expand Down Expand Up @@ -63,7 +65,7 @@ export const showVessel =
vesselIdentifier: addVesselIdentifierToVesselIdentity(vesselIdentity).vesselIdentifier
}

dispatch(displayedErrorActions.unset('vesselSidebarError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.VESSEL_SIDEBAR_ERROR))
dispatch(
setSelectedVessel({
positions: vesselAndPositions.positions,
Expand All @@ -76,7 +78,7 @@ export const showVessel =
error as Error,
() => showVessel(vesselIdentity, isFromSearch, isFromUserAction),
isFromUserAction,
'vesselSidebarError'
DisplayedErrorKey.VESSEL_SIDEBAR_ERROR
)
)
dispatch(resetLoadingVessel())
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/features/Logbook/useCases/getVesselLogbook.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import { customDayjs } from '@mtes-mct/monitor-ui'

import { vesselsAreEquals } from '../../../domain/entities/vessel/vessel'
Expand Down Expand Up @@ -35,14 +36,14 @@ export const getVesselLogbook =

const updateVesselTrack = navigateTo && isFromUserAction
const isSameVesselAsCurrentlyShowed = vesselsAreEquals(vesselIdentity, currentSelectedVesselIdentity)
const nextNavigateTo = navigateTo || NavigateTo.LAST
const nextNavigateTo = navigateTo ?? NavigateTo.LAST

if (nextNavigateTo === NavigateTo.NEXT && isLastVoyage) {
return
}

if (isFromUserAction) {
dispatch(displayedErrorActions.unset('vesselSidebarError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.VESSEL_SIDEBAR_ERROR))
dispatch(logbookActions.setIsLoading())
}

Expand All @@ -51,7 +52,7 @@ export const getVesselLogbook =
isInLightMode,
vesselIdentity,
nextNavigateTo,
nextTripNumber || tripNumber
nextTripNumber ?? tripNumber
)
if (!voyage) {
dispatch(logbookActions.init(vesselIdentity))
Expand Down Expand Up @@ -94,7 +95,7 @@ export const getVesselLogbook =
error as Error,
() => getVesselLogbook(isInLightMode)(vesselIdentity, navigateTo, isFromUserAction, nextTripNumber),
isFromUserAction,
'vesselSidebarError'
DisplayedErrorKey.VESSEL_SIDEBAR_ERROR
)
)
dispatch(logbookActions.resetIsLoading())
Expand Down
10 changes: 4 additions & 6 deletions frontend/src/features/Mission/components/MissionForm/Loader.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { openSideWindowPath } from '@features/SideWindow/useCases/openSideWindowPath'
import { useMainAppDispatch } from '@hooks/useMainAppDispatch'
import { useMainAppSelector } from '@hooks/useMainAppSelector'
import { DisplayedError } from '@libs/DisplayedError'
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import { Accent, Button, Icon } from '@mtes-mct/monitor-ui'
import { assert } from '@utils/assert'
import { SideWindowMenuKey } from 'domain/entities/sideWindow/constants'
import { retry } from 'domain/use_cases/error/retry'
import { useCallback } from 'react'
import styled from 'styled-components'
import { LoadingSpinnerWall } from 'ui/LoadingSpinnerWall'
Expand All @@ -22,17 +22,15 @@ export function Loader() {
}, [dispatch])

const handleRetry = () => {
assert(missionFormError, 'missionFormError')

dispatch(retry('missionFormError', missionFormError.useCase))
DisplayedError.retryUseCase(dispatch, DisplayedErrorKey.MISSION_FORM_ERROR)
}

if (missionFormError) {
return (
<ErrorFallback data-cy="mission-form-error">
🔌 {missionFormError.message}
<br />
{missionFormError.useCase && (
{missionFormError.hasRetryableUseCase && (
<RetryButton accent={Accent.PRIMARY} onClick={handleRetry}>
Réessayer
</RetryButton>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/features/Mission/useCases/addMission.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import { SideWindowMenuKey, SideWindowStatus } from 'domain/entities/sideWindow/constants'
import { displayedErrorActions } from 'domain/shared_slices/DisplayedError'

Expand Down Expand Up @@ -36,6 +37,6 @@ const addMissionWithoutConfirmation =
dispatch => {
const newDraft = getMissionDraftFromPartialMainFormValues(initialMainFormValues)

dispatch(displayedErrorActions.unset('missionFormError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.MISSION_FORM_ERROR))
dispatch(missionFormActions.initializeDraft(newDraft))
}
7 changes: 5 additions & 2 deletions frontend/src/features/Mission/useCases/editMission.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import { SideWindowMenuKey, SideWindowStatus } from 'domain/entities/sideWindow/constants'
import { displayedErrorActions } from 'domain/shared_slices/DisplayedError'
import { sideWindowActions } from 'domain/shared_slices/SideWindow'
Expand Down Expand Up @@ -36,7 +37,7 @@ export const editMission =
const editMissionWithoutConfirmation =
(id: number): MainAppThunk =>
async dispatch => {
dispatch(displayedErrorActions.unset('missionFormError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.MISSION_FORM_ERROR))

try {
const missionWithActions = await dispatch(getMissionWithActions(id))
Expand All @@ -47,7 +48,9 @@ const editMissionWithoutConfirmation =
dispatch(sideWindowActions.setSelectedPathIsLoading(false))
} catch (err) {
if (err instanceof FrontendApiError) {
dispatch(displayOrLogError(err, () => editMissionWithoutConfirmation(id), true, 'missionFormError'))
dispatch(
displayOrLogError(err, () => editMissionWithoutConfirmation(id), true, DisplayedErrorKey.MISSION_FORM_ERROR)
)

return
}
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/features/VesselSidebar/Body.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DisplayedError } from '@libs/DisplayedError'
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import { Accent, Button } from '@mtes-mct/monitor-ui'
import styled from 'styled-components'

Expand All @@ -8,7 +10,6 @@ import { VesselSummary } from './Summary'
import { AlertWarning } from './warnings/AlertWarning'
import { BeaconMalfunctionWarning } from './warnings/BeaconMalfunctionWarning'
import { VesselSidebarTab } from '../../domain/entities/vessel/vessel'
import { retry } from '../../domain/use_cases/error/retry'
import { useIsSuperUser } from '../../hooks/authorization/useIsSuperUser'
import { useMainAppDispatch } from '../../hooks/useMainAppDispatch'
import { useMainAppSelector } from '../../hooks/useMainAppSelector'
Expand All @@ -23,16 +24,17 @@ export function Body() {
const selectedVessel = useMainAppSelector(state => state.vessel.selectedVessel)
const vesselSidebarTab = useMainAppSelector(state => state.vessel.vesselSidebarTab)

const handleRetry = () => {
DisplayedError.retryUseCase(dispatch, DisplayedErrorKey.VESSEL_SIDEBAR_ERROR)
}

if (vesselSidebarError) {
return (
<ErrorFallback data-cy="vessel-sidebar-error">
🔌 {vesselSidebarError.message}
<br />
{vesselSidebarError.useCase && (
<RetryButton
accent={Accent.PRIMARY}
onClick={() => dispatch(retry('vesselSidebarError', vesselSidebarError.useCase))}
>
{vesselSidebarError.hasRetryableUseCase && (
<RetryButton accent={Accent.PRIMARY} onClick={handleRetry}>
Réessayer
</RetryButton>
)}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/features/VesselSidebar/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DisplayedErrorKey } from '@libs/DisplayedError/constants'
import { useEffect } from 'react'
import styled from 'styled-components'

Expand Down Expand Up @@ -30,7 +31,7 @@ export function Tabs() {
}, [dispatch, isSuperUser, vesselSidebarTab])

function showTab(tab: VesselSidebarTab) {
dispatch(displayedErrorActions.unset('vesselSidebarError'))
dispatch(displayedErrorActions.unset(DisplayedErrorKey.VESSEL_SIDEBAR_ERROR))
dispatch(showVesselSidebarTab(tab))
}

Expand Down
22 changes: 0 additions & 22 deletions frontend/src/libs/DisplayedError.ts

This file was deleted.

4 changes: 4 additions & 0 deletions frontend/src/libs/DisplayedError/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum DisplayedErrorKey {
MISSION_FORM_ERROR = 'missionFormError',
VESSEL_SIDEBAR_ERROR = 'vesselSidebarError'
}
Loading

0 comments on commit 2898d62

Please sign in to comment.