From 8949eb52ce0969790df84e5856f56477a6e4ed4c Mon Sep 17 00:00:00 2001 From: Milagros Date: Mon, 27 Jul 2026 17:59:54 +0200 Subject: [PATCH 1/3] fix: add alert when call fails for event export --- .../EndpointParamChecks/ExportEvent/index.js | 26 +++++++++++ src/pages/EventExport/EventExport.jsx | 5 +- src/pages/EventExport/form-helper.js | 46 ++++++++++++++++--- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/cypress/integration/EndpointParamChecks/ExportEvent/index.js b/cypress/integration/EndpointParamChecks/ExportEvent/index.js index 3db7fea0a..4b97978a2 100644 --- a/cypress/integration/EndpointParamChecks/ExportEvent/index.js +++ b/cypress/integration/EndpointParamChecks/ExportEvent/index.js @@ -7,6 +7,7 @@ const orgUnitsRootApi = /\/organisationUnits\?filter=level:eq:1&fields=id,path,displayName,children::isNotEmpty&paging=false/ const programsApi = /\/programs\?/ const programStagesApi = /\/programs\/[a-zA-Z0-9]+/ +const eventsApi = /\/api\/tracker\/events/ Before(() => { cy.server() @@ -30,6 +31,11 @@ Before(() => { url: programStagesApi, fixture: 'programStages', }).as('programStagesXHR') + + cy.intercept(eventsApi, { + statusCode: 200, + body: '{}', + }).as('downloadXHR') }) Given('the user is on the event export page', () => { @@ -72,6 +78,26 @@ When('the export form is submitted', () => { win.locationAssign = locationAssignStub cy.get('[data-test="input-export-submit"]').click() }) + cy.wait('@downloadXHR') +}) + +Given('the event export request will fail', () => { + cy.intercept(eventsApi, { + statusCode: 409, + body: { + httpStatus: 'Conflict', + httpStatusCode: 409, + status: 'ERROR', + message: 'Could not find an id for CODE on Data Element.', + }, + }).as('downloadXHR') +}) + +Then('a warning alert is shown with the error message', () => { + cy.get('[data-test="input-form-alerts"]').should( + 'contain', + 'Could not find an id for CODE on Data Element.' + ) }) Then('the download request is sent with the right parameters', () => { diff --git a/src/pages/EventExport/EventExport.jsx b/src/pages/EventExport/EventExport.jsx index 927bce063..2d9899e40 100644 --- a/src/pages/EventExport/EventExport.jsx +++ b/src/pages/EventExport/EventExport.jsx @@ -32,6 +32,7 @@ import { IdScheme, defaultIdSchemeOption, formatNoXmlOptions, + FormAlerts, } from '../../components/Inputs/index.js' import { jsDateToISO8601 } from '../../utils/helper.js' import { onExport, validate } from './form-helper.js' @@ -88,8 +89,9 @@ const EventExport = () => { validate={validate} subscription={{ values: true, + submitError: true, }} - render={({ handleSubmit, form, values }) => ( + render={({ handleSubmit, form, values, submitError }) => (
@@ -121,6 +123,7 @@ const EventExport = () => { label={i18n.t('Export events')} disabled={!exportEnabled} /> + )} /> diff --git a/src/pages/EventExport/form-helper.js b/src/pages/EventExport/form-helper.js index b986714eb..e0541fa29 100644 --- a/src/pages/EventExport/form-helper.js +++ b/src/pages/EventExport/form-helper.js @@ -3,9 +3,24 @@ import { DATE_AFTER_VALIDATOR, } from '../../components/DatePicker/DatePickerField.jsx' import { ALL_VALUE } from '../../hooks/useProgramStages.js' -import { locationAssign, pathToId } from '../../utils/helper.js' +import { FORM_ERROR } from '../../utils/final-form.js' +import { + genericErrorMessage, + locationAssign, + pathToId, +} from '../../utils/helper.js' + +const exportErrorAlert = (message) => ({ + [FORM_ERROR]: [ + { + id: `event-export-error-${new Date().getTime()}`, + warning: true, + message, + }, + ], +}) -const onExport = (baseUrl, setExportEnabled) => (values) => { +const onExport = (baseUrl, setExportEnabled) => async (values) => { setExportEnabled(false) const { @@ -44,11 +59,30 @@ const onExport = (baseUrl, setExportEnabled) => (values) => { .filter((s) => s != '') .join('&') const url = `${apiBaseUrl}${endpoint}.${endpointExtension}?${downloadUrlParams}` - locationAssign(url) - setExportEnabled(true) - // log for debugging purposes - console.log('event-export:', { url, params: downloadUrlParams }) + try { + const response = await fetch(url, { credentials: 'include' }) + + if (!response.ok) { + let message = genericErrorMessage + try { + const body = await response.json() + message = body.message || message + } catch (e) { + // response body wasn't JSON, fall back to the generic message + } + return exportErrorAlert(message) + } + + locationAssign(url) + + // log for debugging purposes + console.log('event-export:', { url, params: downloadUrlParams }) + } catch (e) { + return exportErrorAlert(genericErrorMessage) + } finally { + setExportEnabled(true) + } } const validate = (values) => ({ From 7af48d485818ec971c7e6290637f4ae6ca1f342c Mon Sep 17 00:00:00 2001 From: Milagros Date: Mon, 27 Jul 2026 18:03:04 +0200 Subject: [PATCH 2/3] test: add test when export request fails --- cypress/integration/EndpointParamChecks/ExportEvent.feature | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cypress/integration/EndpointParamChecks/ExportEvent.feature b/cypress/integration/EndpointParamChecks/ExportEvent.feature index 154612891..7a1d062bb 100644 --- a/cypress/integration/EndpointParamChecks/ExportEvent.feature +++ b/cypress/integration/EndpointParamChecks/ExportEvent.feature @@ -84,3 +84,8 @@ Feature: The user should be able to export events Given the "inclusion" input is set to "CHILDREN" When the export form is submitted Then the download request is sent with the right parameters + + Scenario: The export request fails + Given the event export request will fail + When the export form is submitted + Then a warning alert is shown with the error message From b90c138a3a5983750a5b24a32c13f31c5e0c1b4d Mon Sep 17 00:00:00 2001 From: Milagros Date: Mon, 27 Jul 2026 18:56:51 +0200 Subject: [PATCH 3/3] fix: sonar --- src/pages/EventExport/form-helper.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/EventExport/form-helper.js b/src/pages/EventExport/form-helper.js index e0541fa29..8d4fefa0b 100644 --- a/src/pages/EventExport/form-helper.js +++ b/src/pages/EventExport/form-helper.js @@ -13,7 +13,7 @@ import { const exportErrorAlert = (message) => ({ [FORM_ERROR]: [ { - id: `event-export-error-${new Date().getTime()}`, + id: `event-export-error-${Date.now()}`, warning: true, message, }, @@ -70,6 +70,7 @@ const onExport = (baseUrl, setExportEnabled) => async (values) => { message = body.message || message } catch (e) { // response body wasn't JSON, fall back to the generic message + console.error('event-export: failed to parse error response', e) } return exportErrorAlert(message) } @@ -79,6 +80,7 @@ const onExport = (baseUrl, setExportEnabled) => async (values) => { // log for debugging purposes console.log('event-export:', { url, params: downloadUrlParams }) } catch (e) { + console.error('event-export: request failed', e) return exportErrorAlert(genericErrorMessage) } finally { setExportEnabled(true)