diff --git a/Makefile b/Makefile
index 7dd4df5c7c..aedadecb40 100644
--- a/Makefile
+++ b/Makefile
@@ -309,13 +309,25 @@ docker-down:
docker-compose -f ./app/docker-compose.yml down
cypress-open:
+ifeq ($(CONTAINER), true)
xvfb-run -- npm --prefix ./app run cypress
+else
+ npm --prefix ./app run cypress
+endif
cypress-run:
+ifeq ($(CONTAINER), true)
xvfb-run -- npm --prefix ./app run cypress-run
+else
+ npm --prefix ./app run cypress-run
+endif
cypress-report:
+ifeq ($(CONTAINER), true)
xvfb-run -- npm --prefix ./app run cypress-report
+else
+ npm --prefix ./app run cypress-report
+endif
install-cypress:
npm install --save-dev cypress
diff --git a/app/.eslintrc b/app/.eslintrc
index ce1616713e..0853cd6b61 100644
--- a/app/.eslintrc
+++ b/app/.eslintrc
@@ -84,7 +84,8 @@
"import/no-extraneous-dependencies": [
"error",
{ "devDependencies": true }
- ]
+ ],
+ "@typescript-eslint/explicit-function-return-type": "off"
}
},
{
diff --git a/app/cypress/e2e/0-ndr-core-tests/gp_user_workflows/download_lloyd_george_workflow.cy.js b/app/cypress/e2e/0-ndr-core-tests/gp_user_workflows/download_lloyd_george_workflow.cy.js
index f36f814113..87ad495249 100644
--- a/app/cypress/e2e/0-ndr-core-tests/gp_user_workflows/download_lloyd_george_workflow.cy.js
+++ b/app/cypress/e2e/0-ndr-core-tests/gp_user_workflows/download_lloyd_george_workflow.cy.js
@@ -267,7 +267,7 @@ describe('GP Workflow: View Lloyd George record', () => {
statusCode: 200,
body: {
references: singleTestFile,
- nextPageToken: 'abc'
+ nextPageToken: 'abc',
},
}).as('searchDocumentReferences');
@@ -360,7 +360,7 @@ describe('GP Workflow: View Lloyd George record', () => {
statusCode: 200,
body: { jobStatus: 'Pending' },
});
- if (pendingCounts >= 3) {
+ if (pendingCounts >= 10) {
req.alias = 'documentManifestThirdTimePending';
}
});
@@ -372,7 +372,7 @@ describe('GP Workflow: View Lloyd George record', () => {
cy.getByTestId('toggle-selection-btn').click();
cy.getByTestId('download-selected-files-btn').click();
- cy.wait('@documentManifestThirdTimePending');
+ cy.wait('@documentManifestThirdTimePending', { timeout: 20000 });
cy.title().should('have.string', 'Service error');
cy.url().should('have.string', '/server-error?encodedError=');
diff --git a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.scss b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.scss
index e414d2f769..f425f340ec 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.scss
+++ b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.scss
@@ -30,4 +30,10 @@
transform: rotate(90deg);
}
}
+
+ #failed-files-list {
+ p:not(:last-child) {
+ margin: 0;
+ }
+ }
}
diff --git a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx
index 994d6a7afe..bbf1275988 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx
@@ -62,57 +62,76 @@ const DocumentUploadCompleteStage = ({ documents, documentConfig }: Props): Reac
{failedDocuments.length > 0 ? (
-
-
Some of your files failed to upload
-
setShowFiles(!showFiles)}
- data-testid="accordion-toggle-button"
- aria-expanded={showFiles}
- aria-controls="failed-files-list"
- >
-
-
- {showFiles ? (
- <>
-
- Hide files
- >
- ) : (
- <>
-
- View files
- >
- )}
-
-
-
- {showFiles && (
-
- {failedDocuments.map((doc) => (
-
- {doc.file.name}
-
-
- ))}
-
- )}
-
-
What you need to do
+ <>
- You must note which files uploaded successfully, then return to the
- patient's record to upload any files that failed.
+ We uploaded {documents.length - failedDocuments.length} out of{' '}
+ {documents.length} files.
+
+ {failedDocuments.length} files could not be uploaded.
-
{
- navigate(routes.PATIENT_DOCUMENTS, { replace: true });
- }}
- >
- Go to Lloyd George records
-
-
+ There may be a problem with your files.
+
+
+
Files that could not be uploaded
+
setShowFiles(!showFiles)}
+ data-testid="accordion-toggle-button"
+ aria-expanded={showFiles}
+ aria-controls="failed-files-list"
+ >
+
+
+ {showFiles ? (
+ <>
+
+ Hide files
+ >
+ ) : (
+ <>
+
+ View files
+ >
+ )}
+
+
+
+
+ {showFiles && (
+
+ {failedDocuments.map((doc) => (
+
+ ))}
+
+ )}
+
+
What you need to do
+
You must note which files did not upload.
+
+
+ Remove any passwords from files and check that all files open correctly.
+ Then return to the patient's record to upload them again.
+
+
+
Get help
+
+ Contact your local IT support desk to resolve the problems with these
+ files.
+
+
+
{
+ navigate(routes.PATIENT_DOCUMENTS, { replace: true });
+ }}
+ >
+ Go to Lloyd George records
+
+
+ >
) : (
<>
What happens next
diff --git a/app/src/helpers/utils/documentUpload.test.ts b/app/src/helpers/utils/documentUpload.test.ts
index fb4e11fdc5..47b76db592 100644
--- a/app/src/helpers/utils/documentUpload.test.ts
+++ b/app/src/helpers/utils/documentUpload.test.ts
@@ -5,6 +5,7 @@ import {
goToPreviousDocType,
handleDocReviewStatusResult,
handleDocStatusResult,
+ handleDocumentStatusUpdates,
reduceDocumentsForUpload,
startIntervalTimer,
} from './documentUpload';
@@ -22,6 +23,12 @@ import { uploadDocumentForReview } from '../requests/documentReview';
import * as isLocal from './isLocal';
import { DocumentReviewStatus } from '../../types/blocks/documentReview';
import { buildDocumentConfig } from '../test/testBuilders';
+import { routes, routeChildren } from '../../types/generic/routes';
+import {
+ MAX_POLLING_TIME,
+ UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS,
+} from '../constants/network';
+import * as urlManipulations from './urlManipulations';
vi.mock('../requests/uploadDocuments');
vi.mock('../requests/documentReview');
@@ -413,6 +420,33 @@ describe('documentUpload', () => {
expect(mockSetDocuments).toHaveBeenCalledTimes(1);
});
+ it('should update document state to ERROR when status is INVALID', () => {
+ const documentStatusResult = {
+ 'doc-ref-1': {
+ status: DOCUMENT_STATUS.INVALID as const,
+ error_code: 'ERR_INVALID',
+ },
+ };
+
+ const mockDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ ref: 'doc-ref-1',
+ state: DOCUMENT_UPLOAD_STATE.UPLOADING,
+ },
+ ];
+
+ mockSetDocuments.mockImplementation((updateFn) => {
+ const result = updateFn(mockDocs);
+ expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.ERROR);
+ expect(result[0].errorCode).toBe('ERR_INVALID');
+ });
+
+ handleDocStatusResult(documentStatusResult, mockSetDocuments);
+
+ expect(mockSetDocuments).toHaveBeenCalledTimes(1);
+ });
+
it('should handle multiple documents with different statuses', () => {
const documentStatusResult = {
'doc-ref-1': { status: DOCUMENT_STATUS.FINAL as const },
@@ -1031,4 +1065,319 @@ describe('documentUpload', () => {
expect(mockSetShowSkipLink).not.toHaveBeenCalled();
});
});
+
+ describe('handleDocumentStatusUpdates', () => {
+ const mockNavigate = Object.assign(vi.fn(), {
+ withParams: vi.fn(),
+ });
+
+ let mockInterval: { current: number };
+ let mockVirusRef: { current: boolean };
+ let mockCompleteRef: { current: boolean };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.spyOn(globalThis, 'clearInterval');
+ vi.spyOn(window, 'clearInterval');
+ vi.spyOn(urlManipulations, 'getJourney').mockReturnValue('new');
+ mockInterval = { current: 1 };
+ mockVirusRef = { current: false };
+ mockCompleteRef = { current: false };
+ });
+
+ it('should navigate to SERVER_ERROR when journey param is update but journey type does not match', () => {
+ vi.spyOn(urlManipulations, 'getJourney').mockReturnValue('update');
+
+ const intervalTimer = 123;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ intervalTimer,
+ mockInterval,
+ mockDocuments,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(globalThis.clearInterval).toHaveBeenCalledWith(intervalTimer);
+ expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR);
+ });
+
+ it('should navigate to SERVER_ERROR when polling time exceeds MAX_POLLING_TIME', () => {
+ mockInterval.current =
+ Math.ceil(MAX_POLLING_TIME / UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS) + 1;
+
+ const intervalTimer = 456;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ intervalTimer,
+ mockInterval,
+ mockDocuments,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(window.clearInterval).toHaveBeenCalledWith(intervalTimer);
+ expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR);
+ });
+
+ it('should return early when documents array is empty', () => {
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ 123,
+ mockInterval,
+ [],
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate).not.toHaveBeenCalled();
+ expect(mockNavigate.withParams).not.toHaveBeenCalled();
+ });
+
+ it('should navigate to DOCUMENT_UPLOAD_INFECTED when a document has virus', () => {
+ const documentsWithVirus: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.INFECTED,
+ },
+ ];
+
+ const intervalTimer = 789;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ intervalTimer,
+ mockInterval,
+ documentsWithVirus,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockVirusRef.current).toBe(true);
+ expect(window.clearInterval).toHaveBeenCalledWith(intervalTimer);
+ expect(mockNavigate).toHaveBeenCalledWith(routeChildren.DOCUMENT_UPLOAD_INFECTED);
+ });
+
+ it('should not navigate to infected page again if virusReference is already true', () => {
+ const documentsWithVirus: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.INFECTED,
+ },
+ ];
+
+ mockVirusRef.current = true;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ 123,
+ mockInterval,
+ documentsWithVirus,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate).not.toHaveBeenCalledWith(routeChildren.DOCUMENT_UPLOAD_INFECTED);
+ });
+
+ it('should navigate to SERVER_ERROR with error params when all documents have failed', () => {
+ const allFailedDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.ERROR,
+ errorCode: 'UC_4006',
+ },
+ {
+ ...mockDocuments[1],
+ state: DOCUMENT_UPLOAD_STATE.ERROR,
+ errorCode: 'UC_4007',
+ },
+ ];
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ 123,
+ mockInterval,
+ allFailedDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate).toHaveBeenCalledWith(expect.stringContaining(routes.SERVER_ERROR));
+ expect(mockNavigate).toHaveBeenCalledWith(expect.stringContaining('?encodedError='));
+ });
+
+ it('should navigate to DOCUMENT_UPLOAD_COMPLETED when all documents finished successfully', () => {
+ const allSucceededDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.SUCCEEDED,
+ },
+ {
+ ...mockDocuments[1],
+ state: DOCUMENT_UPLOAD_STATE.SUCCEEDED,
+ },
+ ];
+
+ const intervalTimer = 101;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ intervalTimer,
+ mockInterval,
+ allSucceededDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockCompleteRef.current).toBe(true);
+ expect(window.clearInterval).toHaveBeenCalledWith(intervalTimer);
+ expect(mockNavigate.withParams).toHaveBeenCalledWith(
+ routeChildren.DOCUMENT_UPLOAD_COMPLETED,
+ );
+ });
+
+ it('should not navigate to completed page again if completeRef is already true', () => {
+ const allSucceededDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.SUCCEEDED,
+ },
+ ];
+
+ mockCompleteRef.current = true;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ 123,
+ mockInterval,
+ allSucceededDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate.withParams).not.toHaveBeenCalled();
+ });
+
+ it('should navigate to completed page when mix of succeeded and error documents', () => {
+ const mixedDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.SUCCEEDED,
+ },
+ {
+ ...mockDocuments[1],
+ state: DOCUMENT_UPLOAD_STATE.ERROR,
+ errorCode: 'UC_4006',
+ },
+ ];
+
+ const intervalTimer = 202;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ intervalTimer,
+ mockInterval,
+ mixedDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockCompleteRef.current).toBe(true);
+ expect(window.clearInterval).toHaveBeenCalledWith(intervalTimer);
+ expect(mockNavigate.withParams).toHaveBeenCalledWith(
+ routeChildren.DOCUMENT_UPLOAD_COMPLETED,
+ );
+ });
+
+ it('should not navigate when documents are still uploading', () => {
+ const uploadingDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.UPLOADING,
+ },
+ {
+ ...mockDocuments[1],
+ state: DOCUMENT_UPLOAD_STATE.SUCCEEDED,
+ },
+ ];
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ 123,
+ mockInterval,
+ uploadingDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate).not.toHaveBeenCalled();
+ expect(mockNavigate.withParams).not.toHaveBeenCalled();
+ });
+
+ it('should not navigate when documents are still scanning', () => {
+ const scanningDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.SCANNING,
+ },
+ ];
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ 123,
+ mockInterval,
+ scanningDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate).not.toHaveBeenCalled();
+ expect(mockNavigate.withParams).not.toHaveBeenCalled();
+ });
+
+ it('should prioritize virus detection over all documents failed', () => {
+ const virusAndFailedDocs: UploadDocument[] = [
+ {
+ ...mockDocuments[0],
+ state: DOCUMENT_UPLOAD_STATE.INFECTED,
+ },
+ {
+ ...mockDocuments[1],
+ state: DOCUMENT_UPLOAD_STATE.ERROR,
+ errorCode: 'UC_4006',
+ },
+ ];
+
+ const intervalTimer = 303;
+
+ handleDocumentStatusUpdates(
+ 'new',
+ mockNavigate,
+ intervalTimer,
+ mockInterval,
+ virusAndFailedDocs,
+ mockVirusRef,
+ mockCompleteRef,
+ );
+
+ expect(mockNavigate).toHaveBeenCalledWith(routeChildren.DOCUMENT_UPLOAD_INFECTED);
+ expect(mockNavigate).not.toHaveBeenCalledWith(
+ expect.stringContaining(routes.SERVER_ERROR + '?'),
+ );
+ });
+ });
});
diff --git a/app/src/helpers/utils/documentUpload.ts b/app/src/helpers/utils/documentUpload.ts
index a61ce070e4..cd5f181fb3 100644
--- a/app/src/helpers/utils/documentUpload.ts
+++ b/app/src/helpers/utils/documentUpload.ts
@@ -22,6 +22,13 @@ import {
} from '../../types/blocks/documentReview';
import { getDocumentReviewStatus, uploadDocumentForReview } from '../requests/documentReview';
import { Dispatch, RefObject, SetStateAction } from 'react';
+import { EnhancedNavigate, getJourney, JourneyType } from './urlManipulations';
+import { routeChildren, routes } from '../../types/generic/routes';
+import {
+ MAX_POLLING_TIME,
+ UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS,
+} from '../constants/network';
+import { errorCodeToParams } from './errorToParams';
export const reduceDocumentsForUpload = async (
documents: UploadDocument[],
@@ -164,6 +171,7 @@ export const handleDocStatusResult = (
case DOCUMENT_STATUS.NOT_FOUND:
case DOCUMENT_STATUS.CANCELLED:
+ case DOCUMENT_STATUS.INVALID:
updatedDoc.state = DOCUMENT_UPLOAD_STATE.ERROR;
updatedDoc.errorCode = docStatus.error_code;
break;
@@ -243,6 +251,7 @@ export const startIntervalTimer = (
doc.state = DOCUMENT_UPLOAD_STATE.INFECTED;
} else if (doc.file.name.toLocaleLowerCase() === 'virus-failed.pdf') {
doc.state = DOCUMENT_UPLOAD_STATE.ERROR;
+ doc.errorCode = 'UC_4006';
} else {
doc.state = DOCUMENT_UPLOAD_STATE.SUCCEEDED;
}
@@ -307,3 +316,54 @@ export const goToPreviousDocType = (
setShowSkipLink(true);
setDocumentType(documentTypeList[previousDocTypeIndex]);
};
+
+export const handleDocumentStatusUpdates = (
+ journey: JourneyType,
+ navigate: EnhancedNavigate,
+ intervalTimer: number,
+ interval: RefObject,
+ documents: UploadDocument[],
+ virusReference: RefObject,
+ completeRef: RefObject,
+): void => {
+ const journeyParam = getJourney();
+
+ if (journeyParam === 'update' && journey !== journeyParam) {
+ globalThis.clearInterval(intervalTimer);
+ navigate(routes.SERVER_ERROR);
+ return;
+ }
+
+ if (interval.current * UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS > MAX_POLLING_TIME) {
+ globalThis.clearInterval(intervalTimer);
+ navigate(routes.SERVER_ERROR);
+ return;
+ }
+
+ if (documents.length === 0) {
+ return;
+ }
+
+ const hasVirus = documents.some((d) => d.state === DOCUMENT_UPLOAD_STATE.INFECTED);
+ const failedDocs = documents.filter((d) => d.state === DOCUMENT_UPLOAD_STATE.ERROR);
+ const allFinished =
+ documents.length > 0 &&
+ documents.every(
+ (d) =>
+ d.state === DOCUMENT_UPLOAD_STATE.SUCCEEDED ||
+ d.state === DOCUMENT_UPLOAD_STATE.ERROR,
+ );
+
+ if (hasVirus && !virusReference.current) {
+ virusReference.current = true;
+ globalThis.clearInterval(intervalTimer);
+ navigate(routeChildren.DOCUMENT_UPLOAD_INFECTED);
+ } else if (failedDocs.length === documents.length) {
+ const errorParams = errorCodeToParams(failedDocs[0].errorCode!);
+ navigate(routes.SERVER_ERROR + errorParams);
+ } else if (allFinished && !completeRef.current) {
+ completeRef.current = true;
+ globalThis.clearInterval(intervalTimer);
+ navigate.withParams(routeChildren.DOCUMENT_UPLOAD_COMPLETED);
+ }
+};
diff --git a/app/src/helpers/utils/errorCodes.ts b/app/src/helpers/utils/errorCodes.ts
index 93664201c6..0f05528d40 100644
--- a/app/src/helpers/utils/errorCodes.ts
+++ b/app/src/helpers/utils/errorCodes.ts
@@ -42,6 +42,8 @@ const errorCodes: { [key: string]: string } = {
"You cannot access this patient's record because they are not registered at your practice. The patient's current practice can access this record if it's stored in this service.",
UC_4002: 'There was an issue when attempting to virus scan your uploaded files',
UC_4004: technicalIssueMsg,
+ UC_4006:
+ "1 or more files failed to upload. Remove any passwords from files and check that all files open correctly. Then return to the patient's record to upload them again.",
};
export default errorCodes;
diff --git a/app/src/pages/documentUploadPage/DocumentUploadPage.tsx b/app/src/pages/documentUploadPage/DocumentUploadPage.tsx
index 6bee84c0c1..fd61ada2f6 100644
--- a/app/src/pages/documentUploadPage/DocumentUploadPage.tsx
+++ b/app/src/pages/documentUploadPage/DocumentUploadPage.tsx
@@ -14,7 +14,7 @@ import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl';
import useConfig from '../../helpers/hooks/useConfig';
import usePatient from '../../helpers/hooks/usePatient';
import { uploadDocumentToS3 } from '../../helpers/requests/uploadDocuments';
-import { errorCodeToParams, errorToParams } from '../../helpers/utils/errorToParams';
+import { errorToParams } from '../../helpers/utils/errorToParams';
import { isLocal, isMock } from '../../helpers/utils/isLocal';
import {
markDocumentsAsUploading,
@@ -44,10 +44,12 @@ import {
getUploadSession,
goToNextDocType,
goToPreviousDocType,
+ handleDocumentStatusUpdates,
reduceDocumentsForUpload,
startIntervalTimer,
} from '../../helpers/utils/documentUpload';
import DocumentUploadIndex from '../../components/blocks/_documentUpload/documentUploadIndex/DocumentUploadIndex';
+import { UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS } from '../../helpers/constants/network';
const DocumentUploadPage = (): React.JSX.Element => {
const patientDetails = usePatient();
@@ -74,9 +76,6 @@ const DocumentUploadPage = (): React.JSX.Element => {
const [showSkipLink, setShowSkipLink] = useState(undefined);
const [documentTypeList, setDocumentTypeList] = useState([]);
- const UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS = 5000;
- const MAX_POLLING_TIME = 600000;
-
useEffect(() => {
const journeyParam = getJourney();
if (journeyParam === 'update') {
@@ -91,44 +90,15 @@ const DocumentUploadPage = (): React.JSX.Element => {
}, []);
useEffect(() => {
- const journeyParam = getJourney();
-
- if (journeyParam === 'update' && journey !== journeyParam) {
- globalThis.clearInterval(intervalTimer);
- navigate(routes.SERVER_ERROR);
- return;
- }
-
- if (interval.current * UPDATE_DOCUMENT_STATE_FREQUENCY_MILLISECONDS > MAX_POLLING_TIME) {
- window.clearInterval(intervalTimer);
- navigate(routes.SERVER_ERROR);
- return;
- }
-
- const hasVirus = documents.some((d) => d.state === DOCUMENT_UPLOAD_STATE.INFECTED);
- const docWithError =
- documents.length === 1 &&
- documents.find((d) => d.state === DOCUMENT_UPLOAD_STATE.ERROR);
- const allFinished =
- documents.length > 0 &&
- documents.every(
- (d) =>
- d.state === DOCUMENT_UPLOAD_STATE.SUCCEEDED ||
- d.state === DOCUMENT_UPLOAD_STATE.ERROR,
- );
-
- if (hasVirus && !virusReference.current) {
- virusReference.current = true;
- window.clearInterval(intervalTimer);
- navigate(routeChildren.DOCUMENT_UPLOAD_INFECTED);
- } else if (docWithError) {
- const errorParams = docWithError.error ? errorCodeToParams(docWithError.error) : '';
- navigate(routes.SERVER_ERROR + errorParams);
- } else if (allFinished && !completeRef.current) {
- completeRef.current = true;
- window.clearInterval(intervalTimer);
- navigate.withParams(routeChildren.DOCUMENT_UPLOAD_COMPLETED);
- }
+ handleDocumentStatusUpdates(
+ journey,
+ navigate,
+ intervalTimer,
+ interval,
+ documents,
+ virusReference,
+ completeRef,
+ );
}, [
baseHeaders,
baseUrl,
diff --git a/app/src/pages/serverErrorPage/ServerErrorPage.test.tsx b/app/src/pages/serverErrorPage/ServerErrorPage.test.tsx
index 17445e22ee..ddd7da93af 100644
--- a/app/src/pages/serverErrorPage/ServerErrorPage.test.tsx
+++ b/app/src/pages/serverErrorPage/ServerErrorPage.test.tsx
@@ -1,10 +1,10 @@
import { render, screen, waitFor } from '@testing-library/react';
-import { act } from 'react';
import ServerErrorPage from './ServerErrorPage';
import userEvent from '@testing-library/user-event';
import { unixTimestamp } from '../../helpers/utils/createTimestamp';
import { runAxeTest } from '../../helpers/test/axeTestHelper';
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
+import { routes } from '../../types/generic/routes';
const mockedUseNavigate = vi.fn();
const mockSearchParamsGet = vi.fn();
@@ -38,12 +38,12 @@ describe('ServerErrorPage', () => {
expect(screen.getByText('There was an unexplained error')).toBeInTheDocument();
expect(
screen.getByText(
- "Try again by returning to the previous page. You'll need to enter any information you submitted again.",
+ "Try again by returning to the home page. You'll need to enter any information you submitted again.",
),
).toBeInTheDocument();
expect(
screen.getByRole('button', {
- name: 'Return to previous page',
+ name: 'Go to home',
}),
).toBeInTheDocument();
expect(
@@ -128,16 +128,14 @@ describe('ServerErrorPage', () => {
mockSearchParamsGet.mockReturnValue(mockEncoded);
render( );
- const returnButtonLink = screen.getByRole('button', {
- name: 'Return to previous page',
- });
- expect(returnButtonLink).toBeInTheDocument();
- act(() => {
- userEvent.click(returnButtonLink);
+ const homeButtonLink = screen.getByRole('button', {
+ name: 'Go to home',
});
+ expect(homeButtonLink).toBeInTheDocument();
+ await userEvent.click(homeButtonLink);
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(-2);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
});
});
});
diff --git a/app/src/pages/serverErrorPage/ServerErrorPage.tsx b/app/src/pages/serverErrorPage/ServerErrorPage.tsx
index 1d1d0c7caf..0436db7976 100644
--- a/app/src/pages/serverErrorPage/ServerErrorPage.tsx
+++ b/app/src/pages/serverErrorPage/ServerErrorPage.tsx
@@ -3,6 +3,7 @@ import { ButtonLink } from 'nhsuk-react-components';
import errorCodes from '../../helpers/utils/errorCodes';
import { unixTimestamp } from '../../helpers/utils/createTimestamp';
import useTitle from '../../helpers/hooks/useTitle';
+import { routes } from '../../types/generic/routes';
type ServerError = [errorCode: string | null, interactionId: string | null];
@@ -26,27 +27,17 @@ const ServerErrorPage = (): React.JSX.Element => {
Sorry, there is a problem with the service
{errorMessage}
- Try again by returning to the previous page. You'll need to enter any information
- you submitted again.
+ Try again by returning to the home page. You'll need to enter any information you
+ submitted again.
{
e.preventDefault();
- const errorUrl = window.location.href;
- // Navigate back two paces incase the previous page has an error in the prefetch
- navigate(-2);
-
- // If this code is reached, we can assume that the component
- // has not destroyed and navigate(-2) has no where to go
- const urlAfterMinusTwoNavigate = window.location.href;
- const urlHasNotChanged = errorUrl === urlAfterMinusTwoNavigate;
- if (urlHasNotChanged) {
- navigate(-1);
- }
+ navigate(routes.HOME);
}}
>
- Return to previous page
+ Go to home
If this error keeps appearing
diff --git a/app/src/types/pages/UploadDocumentsPage/types.ts b/app/src/types/pages/UploadDocumentsPage/types.ts
index 121c6a8b00..db18794322 100644
--- a/app/src/types/pages/UploadDocumentsPage/types.ts
+++ b/app/src/types/pages/UploadDocumentsPage/types.ts
@@ -28,6 +28,7 @@ export enum DOCUMENT_STATUS {
CANCELLED = 'cancelled',
INFECTED = 'infected',
NOT_FOUND = 'not-found',
+ INVALID = 'invalid',
}
export enum UploadDocumentType {