Skip to content

Commit

Permalink
Various renaming and refactoring after review
Browse files Browse the repository at this point in the history
  • Loading branch information
louptheron committed Jan 31, 2024
1 parent 6dcf944 commit b488fa0
Show file tree
Hide file tree
Showing 23 changed files with 97 additions and 143 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ lint-back:
run-back-for-puppeteer: run-stubbed-apis
docker compose up -d --quiet-pull --wait db
docker compose -f ./frontend/puppeteer/docker-compose.dev.yml up -d
cd backend && ./gradlew bootRun --args='--spring.profiles.active=puppeteer --spring.config.additional-location=$(INFRA_FOLDER)'
cd backend && MONITORENV_URL=http://localhost:8882 ./gradlew bootRun --args='--spring.profiles.active=local --spring.config.additional-location=$(INFRA_FOLDER)'
run-front-for-puppeteer:
cd ./frontend && npm run dev-puppeteer

Expand Down
1 change: 0 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
FRONTEND_GEOSERVER_LOCAL_URL=
FRONTEND_GEOSERVER_REMOTE_URL=
FRONTEND_IS_DEV_ENV=
FRONTEND_MAPBOX_KEY=
FRONTEND_MONITORENV_URL=
FRONTEND_OIDC_AUTHORITY=
Expand Down
1 change: 0 additions & 1 deletion frontend/cypress/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ services:
- MONITORFISH_API_PROTECTED_API_KEY=APIKEY
- FRONTEND_GEOSERVER_LOCAL_URL=http://0.0.0.0:8081
- FRONTEND_GEOSERVER_REMOTE_URL=http://0.0.0.0:8081
- FRONTEND_IS_DEV_ENV=true
- FRONTEND_MAPBOX_KEY=pk.eyJ1IjoibW9uaXRvcmZpc2giLCJhIjoiY2tsdHJ6dHhhMGZ0eDJ2bjhtZmJlOHJmZiJ9.bdi1cO-cUcZKXdkEkqAoZQ
- FRONTEND_MONITORENV_URL=http://0.0.0.0:8081
- FRONTEND_OIDC_AUTHORITY=https://authentification.recette.din.developpement-durable.gouv.fr/authSAML/oidc/monitorfish
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"build:local": "ENV_PROFILE=local npm run build",
"cypress:open": "cypress open --browser firefox --config-file ./config/cypress.config.ts --e2e",
"dev": "dotenv -e ../infra/configurations/frontend/.env.local vite --port 3000",
"dev-puppeteer": "dotenv -e ../infra/configurations/frontend/.env.puppeteer vite --port 3000",
"dev-puppeteer": "dotenv -e ../infra/configurations/frontend/.env.local -v FRONTEND_MONITORENV_URL=//localhost:8882 vite --port 3000",
"bundle-sw": "esbuild src/workers/serviceWorker.ts --bundle --outfile=public/service-worker.js",
"prepare": "cd .. && ./frontend/node_modules/.bin/husky install ./frontend/config/husky",
"start": "vite preview --port 3000",
Expand Down
1 change: 0 additions & 1 deletion frontend/puppeteer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ services:
- MONITORFISH_API_PROTECTED_API_KEY=APIKEY
- FRONTEND_GEOSERVER_LOCAL_URL=http://0.0.0.0:8081
- FRONTEND_GEOSERVER_REMOTE_URL=http://0.0.0.0:8081
- FRONTEND_IS_DEV_ENV=true
- FRONTEND_MAPBOX_KEY=pk.eyJ1IjoibW9uaXRvcmZpc2giLCJhIjoiY2tsdHJ6dHhhMGZ0eDJ2bjhtZmJlOHJmZiJ9.bdi1cO-cUcZKXdkEkqAoZQ
- FRONTEND_MONITORENV_URL=http://0.0.0.0:8882
- FRONTEND_OIDC_AUTHORITY=https://authentification.recette.din.developpement-durable.gouv.fr/authSAML/oidc/monitorfish
Expand Down
23 changes: 15 additions & 8 deletions frontend/puppeteer/e2e/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'assert'
import { Page, Browser } from 'puppeteer'

export function listenToConsole(page, index) {
export function listenToConsole(page: Page, index: number) {
page
.on('console', message => {
const messageType = message.type().substr(0, 3).toUpperCase()
Expand All @@ -23,32 +24,38 @@ export function listenToConsole(page, index) {
})
}

export async function assertContains(page, selector, text) {
export async function assertContains(page: Page, selector: string, text: string) {
// TODO Remove ts-ignore when TS version is 4.9.3:
// @ts-ignore: https://github.com/puppeteer/puppeteer/issues/9369
const nodes = await page.$$eval(selector, elements => elements.map(element => element.textContent))
const node = nodes.find(content => content.includes(text))

assert.ok(node, `${selector} of value ${text} not found in array ${nodes}.`)
}

export async function getTextContent(page, selector) {
export async function getTextContent(page: Page, selector: string) {
const element = await page.waitForSelector(selector)

return element.evaluate(el => el.textContent)
return element && element.evaluate(el => el.textContent)
}

export async function getInputContent(page, selector) {
export async function getInputContent(page: Page, selector: string) {
const element = await page.waitForSelector(selector)

return element.evaluate(el => el.value)
// From Puppeteer doc:
// If you are using TypeScript, you may have to provide an explicit type to the first argument of the pageFunction.
// By default it is typed as Element[], but you may need to provide a more specific sub-type
// @ts-ignore
return element && element.evaluate((el: HTMLInputElement) => el.value)
}

export async function getFirstTab(browser) {
export async function getFirstTab(browser: Browser) {
const [firstTab] = await browser.pages()

return firstTab
}

export function wait(ms) {
export function wait(ms: number) {
/* eslint-disable no-promise-executor-return */
return new Promise(resolve => setTimeout(resolve, ms))
}
2 changes: 1 addition & 1 deletion frontend/src/domain/entities/mission/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const getMissionFeatureZone = (mission: Mission.Mission | MissionMainForm
}

export const getMissionActionFeature = (
action: MissionAction.MissionAction | (MissionActionFormValues & { missionId: number })
action: MissionAction.MissionAction | MissionActionFormValues
): Feature | undefined => {
if (!action.longitude || !action.latitude) {
return undefined
Expand Down
46 changes: 24 additions & 22 deletions frontend/src/features/SideWindow/MissionForm/ActionList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,35 @@ import type { Promisable } from 'type-fest'

export type ItemProps = {
isSelected: boolean
missionAction: MissionActionFormValues
onDuplicate: () => Promisable<void>
onRemove: () => Promisable<void>
onSelect: () => Promisable<void>
values: MissionActionFormValues
}
export function Item({ isSelected, onDuplicate, onRemove, onSelect, values }: ItemProps) {
export function Item({ isSelected, missionAction, onDuplicate, onRemove, onSelect }: ItemProps) {
const draft = useMainAppSelector(state => state.mission.draft)

const natinfsAsOptions = useGetNatinfsAsOptions()

const isControlAction =
values.actionType === MissionAction.MissionActionType.AIR_CONTROL ||
values.actionType === MissionAction.MissionActionType.LAND_CONTROL ||
values.actionType === MissionAction.MissionActionType.SEA_CONTROL
missionAction.actionType === MissionAction.MissionActionType.AIR_CONTROL ||
missionAction.actionType === MissionAction.MissionActionType.LAND_CONTROL ||
missionAction.actionType === MissionAction.MissionActionType.SEA_CONTROL

const [actionLabel, ActionIcon] = useMemo(() => {
const vesselName = values.vesselName === UNKNOWN_VESSEL.vesselName ? 'INCONNU' : values.vesselName
const vesselName = missionAction.vesselName === UNKNOWN_VESSEL.vesselName ? 'INCONNU' : missionAction.vesselName

switch (values.actionType) {
switch (missionAction.actionType) {
case MissionAction.MissionActionType.AIR_CONTROL:
return [getActionTitle('Contrôle aérien', vesselName, '- Navire inconnu'), Icon.Plane]

case MissionAction.MissionActionType.AIR_SURVEILLANCE:
return [
getActionTitle(
'Surveillance aérienne',
values.numberOfVesselsFlownOver ? `${values.numberOfVesselsFlownOver} pistes survolées` : undefined,
missionAction.numberOfVesselsFlownOver
? `${missionAction.numberOfVesselsFlownOver} pistes survolées`
: undefined,
'à renseigner'
),
Icon.Observation
Expand All @@ -61,22 +63,22 @@ export function Item({ isSelected, onDuplicate, onRemove, onSelect, values }: It
return [getActionTitle('Contrôle à la débarque', vesselName, '- Navire inconnu'), Icon.Anchor]

case MissionAction.MissionActionType.OBSERVATION:
return [getActionTitle('', values.otherComments, 'Note libre à renseigner'), Icon.Note]
return [getActionTitle('', missionAction.otherComments, 'Note libre à renseigner'), Icon.Note]

case MissionAction.MissionActionType.SEA_CONTROL:
return [getActionTitle('Contrôle en mer', vesselName, '- Navire inconnu'), Icon.FleetSegment]

default:
throw new FrontendError('`initialValues.actionType` does not match the enum')
}
}, [values])
}, [missionAction])

const infractionTags = useMemo(() => {
const allInfractions = getMissionActionInfractionsFromMissionActionFormValues(values, true)
const allInfractions = getMissionActionInfractionsFromMissionActionFormValues(missionAction, true)
if (!allInfractions.length) {
return []
}
const nonPendingInfractions = getMissionActionInfractionsFromMissionActionFormValues(values)
const nonPendingInfractions = getMissionActionInfractionsFromMissionActionFormValues(missionAction)
const pendingInfractions = allInfractions.filter(
({ infractionType }) => infractionType === MissionAction.InfractionType.PENDING
)
Expand Down Expand Up @@ -107,28 +109,28 @@ export function Item({ isSelected, onDuplicate, onRemove, onSelect, values }: It
)

return [...infractionsRecapTags, infractionsTag]
}, [values, natinfsAsOptions])
}, [missionAction, natinfsAsOptions])

const redTags = useMemo(
() =>
[
...(values.hasSomeGearsSeized ? ['Appréhension engin'] : []),
...(values.hasSomeSpeciesSeized ? ['Appréhension espèce'] : []),
...(values.seizureAndDiversion ? ['Appréhension navire'] : [])
...(missionAction.hasSomeGearsSeized ? ['Appréhension engin'] : []),
...(missionAction.hasSomeSpeciesSeized ? ['Appréhension espèce'] : []),
...(missionAction.seizureAndDiversion ? ['Appréhension navire'] : [])
].map(label => (
<Tag key={label} accent={Accent.PRIMARY} bullet={TagBullet.DISK} bulletColor={THEME.color.maximumRed}>
{label}
</Tag>
)),
[values]
[missionAction]
)

const startDateAsDayjs = useMemo(
() => values.actionDatetimeUtc && getLocalizedDayjs(values.actionDatetimeUtc),
[values]
() => missionAction.actionDatetimeUtc && getLocalizedDayjs(missionAction.actionDatetimeUtc),
[missionAction]
)

const isOpen = isControlAction && draft?.mainFormValues && !draft?.mainFormValues.isClosed && !values.closedBy
const isOpen = isControlAction && draft?.mainFormValues && !draft?.mainFormValues.isClosed && !missionAction.closedBy

return (
<>
Expand All @@ -144,7 +146,7 @@ export function Item({ isSelected, onDuplicate, onRemove, onSelect, values }: It
<InnerWrapper
$isOpen={isOpen}
$isSelected={isSelected}
$type={values.actionType}
$type={missionAction.actionType}
data-cy="action-list-item"
onClick={onSelect}
title={isOpen ? 'Contrôle en cours' : undefined}
Expand Down Expand Up @@ -180,7 +182,7 @@ export function Item({ isSelected, onDuplicate, onRemove, onSelect, values }: It
</InnerWrapper>
</Wrapper>

{!values.isValid && (
{!missionAction.isValid && (
<StyledFieldError>Veuillez compléter les champs manquants dans cette action de contrôle.</StyledFieldError>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ export function ActionList({
// eslint-disable-next-line react/no-array-index-key
key={index}
isSelected={index === currentIndex}
missionAction={action}
onDuplicate={() => onDuplicate(index)}
onRemove={() => onRemove(index)}
onSelect={() => onSelect(index)}
values={action}
/>
))}
</FrontendErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useFormikContext } from 'formik'
import { omit } from 'lodash'

import { useDeepCompareEffect } from '../../../../hooks/useDeepCompareEffect'
import { useListenMissionEventUpdatesById } from '../hooks/useListenMissionEventUpdatesById'
import { logInDev } from '../../../../utils/logInDev'
import { useListenToMissionEventUpdatesById } from '../hooks/useListenToMissionEventUpdatesById'
import { MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM } from '../sse'

import type { MissionMainFormValues } from '../types'
Expand All @@ -16,34 +17,33 @@ type FormikSyncMissionFormProps = {
*/
export function FormikSyncMissionFields({ missionId }: FormikSyncMissionFormProps) {
const { setFieldValue, values } = useFormikContext<MissionMainFormValues>()
const receivedMission = useListenMissionEventUpdatesById(missionId)
const missionEvent = useListenToMissionEventUpdatesById(missionId)

useDeepCompareEffect(
() => {
if (!receivedMission) {
if (!missionEvent) {
return
}

;(async () => {
const receivedDiff = diff(
omit(values, MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM),
omit(receivedMission, MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM)
omit(missionEvent, MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM)
)

/**
* We iterate and use `setFieldValue` on each diff key to avoid a global re-render of the <MainForm/> component
*/
Object.keys(receivedDiff).forEach(key => {
// eslint-disable-next-line no-console
console.log(`SSE: setting form key "${key}" to ${JSON.stringify(receivedMission[key])}`)
setFieldValue(key, receivedMission[key])
logInDev(`SSE: setting form key "${key}" to ${JSON.stringify(missionEvent[key])}`)
setFieldValue(key, missionEvent[key])
})
})()
},

// We don't want to trigger infinite re-renders since `setFieldValue` changes after each rendering
// eslint-disable-next-line react-hooks/exhaustive-deps
[receivedMission]
[missionEvent]
)

return <></>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { FormikMultiControlUnitPicker } from './FormikMultiControlUnitPicker'
import { FormikSyncMissionFields } from './FormikSyncMissionFields'
import { MainFormLiveSchema } from './schemas'
import { BOOLEAN_AS_OPTIONS } from '../../../../constants'
import { useListenMissionEventUpdatesById } from '../hooks/useListenMissionEventUpdatesById'
import { useListenToMissionEventUpdatesById } from '../hooks/useListenToMissionEventUpdatesById'
import { FormBody, FormBodyInnerWrapper } from '../shared/FormBody'
import { FormHead } from '../shared/FormHead'

Expand All @@ -32,7 +32,7 @@ type MainFormProps = {
onChange: (nextValues: MissionMainFormValues) => Promisable<void>
}
function UnmemoizedMainForm({ initialValues, missionId, onChange }: MainFormProps) {
const receivedMission = useListenMissionEventUpdatesById(missionId)
const missionEvent = useListenToMissionEventUpdatesById(missionId)

function validateBeforeOnChange(validateForm) {
return async nextValues => {
Expand All @@ -46,7 +46,7 @@ function UnmemoizedMainForm({ initialValues, missionId, onChange }: MainFormProp

// Prevent re-sending the form when receiving an update
const nextValuesWithoutIsValid = omit(nextValues, ['isValid'])
if (isEqual(receivedMission, nextValuesWithoutIsValid)) {
if (isEqual(missionEvent, nextValuesWithoutIsValid)) {
return
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { useEffect, useRef, useState } from 'react'
import { MONITORENV_API_URL } from '../../../../api/api'
import { Mission } from '../../../../domain/entities/mission/types'
import { useMainAppSelector } from '../../../../hooks/useMainAppSelector'
import { logInDev } from '../../../../utils/logInDev'
import { missionEventListener } from '../sse'

const MISSION_UPDATES_URL = `${MONITORENV_API_URL}/api/v1/missions/sse`
const MISSION_UPDATE_EVENT = `MISSION_UPDATE`

export function useListenMissionEventUpdates() {
export function useListenToMissionEventUpdates() {
const isListeningToEvents = useMainAppSelector(state => state.mission.isListeningToEvents)
const eventSourceRef = useRef<EventSource>()
const [missionEvent, setMissionEvent] = useState<Mission.Mission>()
Expand All @@ -18,8 +19,7 @@ export function useListenMissionEventUpdates() {
eventSourceRef.current = new EventSource(MISSION_UPDATES_URL)

eventSourceRef.current?.addEventListener('open', () => {
// eslint-disable-next-line no-console
console.log(`SSE: Connected to missions endpoint.`)
logInDev(`SSE: Connected to missions endpoint.`)
})

return () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useContext, useMemo, useRef } from 'react'

import { MissionEventContext } from '../../../../context/MissionEventContext'
import { Mission } from '../../../../domain/entities/mission/types'

export function useListenToMissionEventUpdatesById(missionId: number | string | undefined) {
const missionEvent = useContext(MissionEventContext)
const previousMission = useRef<Mission.Mission | undefined>()

const mission = useMemo(() => {
if (!Number.isInteger(missionId) || !missionEvent || missionEvent.id !== missionId) {
return previousMission.current
}

previousMission.current = missionEvent

return missionEvent
}, [missionEvent, missionId])

return mission || previousMission.current
}
Loading

0 comments on commit b488fa0

Please sign in to comment.