diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index c0430f69ad..6d02e534c1 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -259,7 +259,7 @@ jobs: uses: actions/checkout@v4 - name: Download image - uses: ishworkh/container-image-artifact-upload@v2.0.0 + uses: ishworkh/container-image-artifact-download@v2.0.0 with: image: monitorfish-app:${{ env.VERSION }} diff --git a/frontend/cypress/e2e/main_window/vessel_sidebar/logbook.spec.ts b/frontend/cypress/e2e/main_window/vessel_sidebar/logbook.spec.ts index c054c306eb..b5ce681a2e 100644 --- a/frontend/cypress/e2e/main_window/vessel_sidebar/logbook.spec.ts +++ b/frontend/cypress/e2e/main_window/vessel_sidebar/logbook.spec.ts @@ -152,9 +152,9 @@ context('Vessel sidebar logbook tab', () => { .its('response.url') .should( 'have.string', - encodeURIComponent('/bff/v1/vessels/positions?afterDateTime=2019-02-16T21:05:00.000Z' + - '&beforeDateTime=2019-10-15T13:01:00.000Z&externalReferenceNumber=DONTSINK&internalReferenceNumber=FAK000999999' + - '&IRCS=CALLME&trackDepth=CUSTOM&vesselIdentifier=INTERNAL_REFERENCE_NUMBER') + `/bff/v1/vessels/positions?afterDateTime=${encodeURIComponent('2019-02-16T21:05:00.000Z')}` + + `&beforeDateTime=${encodeURIComponent('2019-10-15T13:01:00.000Z')}&externalReferenceNumber=DONTSINK&internalReferenceNumber=FAK000999999` + + '&IRCS=CALLME&trackDepth=CUSTOM&vesselIdentifier=INTERNAL_REFERENCE_NUMBER' ) cy.get('*[data-cy^="fishing-activity-name"]').should('exist').should('have.length', 4) @@ -171,9 +171,9 @@ context('Vessel sidebar logbook tab', () => { .its('response.url') .should( 'have.string', - encodeURIComponent('/bff/v1/vessels/positions?&afterDateTime=&beforeDateTime=' + + '/bff/v1/vessels/positions?&afterDateTime=&beforeDateTime=' + '&externalReferenceNumber=DONTSINK&internalReferenceNumber=FAK000999999' + - '&IRCS=CALLME&trackDepth=TWELVE_HOURS&vesselIdentifier=INTERNAL_REFERENCE_NUMBER') + '&IRCS=CALLME&trackDepth=TWELVE_HOURS&vesselIdentifier=INTERNAL_REFERENCE_NUMBER' ) cy.get('*[data-cy^="fishing-activity-name"]').should('not.exist') // Go back to the default track depth diff --git a/frontend/cypress/e2e/main_window/vessel_sidebar/offline_management.spec.ts b/frontend/cypress/e2e/main_window/vessel_sidebar/offline_management.spec.ts index 2af0fc5d8c..b49ec92c38 100644 --- a/frontend/cypress/e2e/main_window/vessel_sidebar/offline_management.spec.ts +++ b/frontend/cypress/e2e/main_window/vessel_sidebar/offline_management.spec.ts @@ -83,7 +83,7 @@ context('Offline management', () => { { method: 'GET', path: - 'bff/v1/vessels/find?afterDateTime=&beforeDateTime=&externalReferenceNumber=DONTSINK' + + '/bff/v1/vessels/find?afterDateTime=&beforeDateTime=&externalReferenceNumber=DONTSINK' + '&internalReferenceNumber=FAK000999999&IRCS=CALLME&trackDepth=TWELVE_HOURS' + '&vesselId=1&vesselIdentifier=INTERNAL_REFERENCE_NUMBER', times: 2 diff --git a/frontend/cypress/e2e/side_window/beacon_malfunction/board.spec.ts b/frontend/cypress/e2e/side_window/beacon_malfunction/board.spec.ts index 640431ea7f..bc4011c566 100644 --- a/frontend/cypress/e2e/side_window/beacon_malfunction/board.spec.ts +++ b/frontend/cypress/e2e/side_window/beacon_malfunction/board.spec.ts @@ -239,7 +239,7 @@ context('Side Window > Beacon Malfunction Board', () => { 'GET', new RegExp( `bff\\/v1\\/vessels\\/find\\?` + - `&afterDateTime=${oneWeeksBeforeDate.format('YYYY-MM-DD')}T\\d{2}%3A\\d{2}%3A\\d{2}\\.\\d{3}Z` + + `afterDateTime=${oneWeeksBeforeDate.format('YYYY-MM-DD')}T\\d{2}%3A\\d{2}%3A\\d{2}\\.\\d{3}Z` + `&beforeDateTime=${oneWeeksBeforePlusOneDayDate.format('YYYY-MM-DD')}T\\d{2}%3A\\d{2}%3A\\d{2}\\.\\d{3}Z` + `&externalReferenceNumber=DONTSINK&internalReferenceNumber=FAK000999999` + `&IRCS=CALLME&trackDepth=CUSTOM&vesselId=1&vesselIdentifier=INTERNAL_REFERENCE_NUMBER` diff --git a/frontend/src/api/alert.ts b/frontend/src/api/alert.ts index 839bbba069..11166bbc64 100644 --- a/frontend/src/api/alert.ts +++ b/frontend/src/api/alert.ts @@ -50,7 +50,7 @@ export const alertApi = monitorfishApi.injectEndpoints({ transformErrorResponse: response => new FrontendApiError(DELETE_SILENCED_ALERT_ERROR_MESSAGE, response) }), getOperationalAlerts: builder.query({ - query: () => '/bff/v1/operational_alerts', + query: () => '/operational_alerts', transformErrorResponse: response => new FrontendApiError(ALERTS_ERROR_MESSAGE, response), transformResponse: (response: PendingAlert[]) => response.map(normalizePendingAlert) }), diff --git a/frontend/src/features/FleetSegment/apis.ts b/frontend/src/features/FleetSegment/apis.ts index 2e1e891e63..cb1330eff6 100644 --- a/frontend/src/features/FleetSegment/apis.ts +++ b/frontend/src/features/FleetSegment/apis.ts @@ -3,6 +3,7 @@ import { FleetSegmentSchema } from '@features/FleetSegment/types' import { MissionAction } from '@features/Mission/missionAction.types' import { FrontendApiError } from '@libs/FrontendApiError' import { customDayjs } from '@mtes-mct/monitor-ui' +import { parseResponseOrReturn } from '@utils/parseResponseOrReturn' import type { FleetSegment } from '@features/FleetSegment/types' @@ -43,9 +44,9 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ url: `/fleet_segments/compute` }), transformResponse: (baseQueryReturnValue: FleetSegment[]) => - baseQueryReturnValue - .map(segment => FleetSegmentSchema.parse(segment)) - .sort((a, b) => a.segment.localeCompare(b.segment)) + parseResponseOrReturn(baseQueryReturnValue, FleetSegmentSchema, true).sort((a, b) => + a.segment.localeCompare(b.segment) + ) }), createFleetSegment: builder.mutation({ query: segmentFields => ({ @@ -54,7 +55,8 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ url: '/admin/fleet_segments' }), transformErrorResponse: response => new FrontendApiError(CREATE_FLEET_SEGMENT_ERROR_MESSAGE, response), - transformResponse: (baseQueryReturnValue: FleetSegment) => FleetSegmentSchema.parse(baseQueryReturnValue) + transformResponse: (baseQueryReturnValue: FleetSegment) => + parseResponseOrReturn(baseQueryReturnValue, FleetSegmentSchema, false) }), deleteFleetSegment: builder.mutation({ query: ({ segment, year }) => ({ @@ -63,9 +65,9 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ }), transformErrorResponse: response => new FrontendApiError(DELETE_FLEET_SEGMENT_ERROR_MESSAGE, response), transformResponse: (baseQueryReturnValue: FleetSegment[]) => - baseQueryReturnValue - .map(segment => FleetSegmentSchema.parse(segment)) - .sort((a, b) => a.segment.localeCompare(b.segment)) + parseResponseOrReturn(baseQueryReturnValue, FleetSegmentSchema, true).sort((a, b) => + a.segment.localeCompare(b.segment) + ) }), getFleetSegments: builder.query({ providesTags: () => [{ type: 'FleetSegments' }], @@ -75,9 +77,9 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ return `fleet_segments/${controlledYear}` }, transformResponse: (baseQueryReturnValue: FleetSegment[]) => - baseQueryReturnValue - .map(segment => FleetSegmentSchema.parse(segment)) - .sort((a, b) => a.segment.localeCompare(b.segment)) + parseResponseOrReturn(baseQueryReturnValue, FleetSegmentSchema, true).sort((a, b) => + a.segment.localeCompare(b.segment) + ) }), getFleetSegmentYearEntries: builder.query({ query: () => '/admin/fleet_segments/years', @@ -94,7 +96,8 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ } }, transformErrorResponse: response => new FrontendApiError(UPDATE_FLEET_SEGMENT_ERROR_MESSAGE, response), - transformResponse: (baseQueryReturnValue: FleetSegment) => FleetSegmentSchema.parse(baseQueryReturnValue) + transformResponse: (baseQueryReturnValue: FleetSegment) => + parseResponseOrReturn(baseQueryReturnValue, FleetSegmentSchema, false) }) }) }) diff --git a/frontend/src/features/Vessel/vesselApi.ts b/frontend/src/features/Vessel/vesselApi.ts index 9b1d15968e..e82840be43 100644 --- a/frontend/src/features/Vessel/vesselApi.ts +++ b/frontend/src/features/Vessel/vesselApi.ts @@ -4,6 +4,7 @@ import { VesselLastPositionSchema } from '@features/Vessel/schemas/VesselLastPos import { DisplayedErrorKey } from '@libs/DisplayedError/constants' import { FrontendApiError } from '@libs/FrontendApiError' import { getUrlOrPathWithQueryParams } from '@utils/getUrlOrPathWithQueryParams' +import { parseResponseOrReturn } from '@utils/parseResponseOrReturn' import { displayedErrorActions } from 'domain/shared_slices/DisplayedError' import { displayOrLogError } from 'domain/use_cases/error/displayOrLogError' @@ -140,7 +141,7 @@ export const vesselApi = monitorfishApi.injectEndpoints({ getVesselsLastPositions: builder.query({ query: () => `/vessels`, transformResponse: (baseQueryReturnValue: Vessel.VesselLastPosition[]) => - baseQueryReturnValue.map(LastPosition => VesselLastPositionSchema.parse(LastPosition)) + parseResponseOrReturn(baseQueryReturnValue, VesselLastPositionSchema, true) }), searchVessels: builder.query({ diff --git a/frontend/src/features/Vessel/vesselNavApi.ts b/frontend/src/features/Vessel/vesselNavApi.ts index aa6bad5944..d4c62c5ba4 100644 --- a/frontend/src/features/Vessel/vesselNavApi.ts +++ b/frontend/src/features/Vessel/vesselNavApi.ts @@ -1,13 +1,14 @@ import { monitorfishLightApi } from '@api/api' import { VesselLastPositionLightSchema } from '@features/Vessel/schemas/VesselLastPositionLightSchema' import { Vessel } from '@features/Vessel/Vessel.types' +import { parseResponseOrReturn } from '@utils/parseResponseOrReturn' export const vesselNavApi = monitorfishLightApi.injectEndpoints({ endpoints: builder => ({ getVesselsLastPositions: builder.query({ query: () => `/v1/vessels`, transformResponse: (baseQueryReturnValue: Vessel.VesselLightLastPosition[]) => - baseQueryReturnValue.map(LastPosition => VesselLastPositionLightSchema.parse(LastPosition)) + parseResponseOrReturn(baseQueryReturnValue, VesselLastPositionLightSchema, true) }) }) }) diff --git a/frontend/src/utils/__tests__/parseResponseOrReturn.test.ts b/frontend/src/utils/__tests__/parseResponseOrReturn.test.ts new file mode 100644 index 0000000000..40cf6bdc7e --- /dev/null +++ b/frontend/src/utils/__tests__/parseResponseOrReturn.test.ts @@ -0,0 +1,44 @@ +import { FleetSegmentSchema } from '@features/FleetSegment/types' +import { VesselLastPositionSchema } from '@features/Vessel/schemas/VesselLastPositionSchema' +import { expect } from '@jest/globals' +import { parseResponseOrReturn } from '@utils/parseResponseOrReturn' + +describe('utils/parseResponseOrReturn()', () => { + it('should return the original response and print an error', () => { + const object = { dummy: true } + + const result = parseResponseOrReturn(object, VesselLastPositionSchema, false) + + expect(result).toStrictEqual(object) + }) + + it('should return the original response and print an error with an array', () => { + const object = { dummy: true } + + const result = parseResponseOrReturn([object], VesselLastPositionSchema, true) + + expect(result).toStrictEqual([object]) + }) + + it('should validate the type successfully', () => { + const segment = { + faoAreas: [], + gears: [], + impactRiskFactor: 2.0, + mainScipSpeciesType: undefined, + maxMesh: undefined, + minMesh: undefined, + minShareOfTargetSpecies: undefined, + priority: 0, + segment: '', + segmentName: '', + targetSpecies: [], + vesselTypes: [], + year: 2021 + } + + const result = parseResponseOrReturn(segment, FleetSegmentSchema, false) + + expect(result).toStrictEqual(segment) + }) +}) diff --git a/frontend/src/utils/parseResponseOrReturn.ts b/frontend/src/utils/parseResponseOrReturn.ts new file mode 100644 index 0000000000..a768d8eece --- /dev/null +++ b/frontend/src/utils/parseResponseOrReturn.ts @@ -0,0 +1,23 @@ +import { FrontendError } from '@libs/FrontendError' +import { ZodSchema } from 'zod' + +export function parseResponseOrReturn(body: unknown, schema: ZodSchema, isArray: false): T +export function parseResponseOrReturn(body: unknown, schema: ZodSchema, isArray: true): T[] +export function parseResponseOrReturn(body: unknown, schema: ZodSchema, isArray: boolean): T | T[] { + try { + if (!isArray) { + return schema.parse(body) + } + + if (!Array.isArray(body)) { + throw new Error('Expected an array for parsing.') + } + + return body.map(bodyElement => schema.parse(bodyElement)) + } catch (e) { + // eslint-disable-next-line no-new + new FrontendError('Failing validating type', e) + + return body as T | T[] + } +}