diff --git a/.github/workflows/base-cypress-build.yml b/.github/workflows/base-cypress-build.yml index 5b2ef3ff7..82d8bca16 100644 --- a/.github/workflows/base-cypress-build.yml +++ b/.github/workflows/base-cypress-build.yml @@ -22,6 +22,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: + repository: 'nhsconnect/national-document-repository' ref: ${{ github.event.inputs.build_branch }} - name: Cypress install @@ -49,7 +50,7 @@ jobs: working-directory: ./app - name: Save build folder - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build if-no-files-found: error diff --git a/.github/workflows/base-cypress-test.yml b/.github/workflows/base-cypress-test.yml index 7c85bd5b8..133585b90 100644 --- a/.github/workflows/base-cypress-test.yml +++ b/.github/workflows/base-cypress-test.yml @@ -38,10 +38,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: + repository: 'nhsconnect/national-document-repository' ref: ${{ inputs.build_branch }} - name: Download the build folder - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build path: ./app/build diff --git a/.github/workflows/base-lambdas-reusable-deploy-all.yml b/.github/workflows/base-lambdas-reusable-deploy-all.yml index f1185c986..50a864c68 100644 --- a/.github/workflows/base-lambdas-reusable-deploy-all.yml +++ b/.github/workflows/base-lambdas-reusable-deploy-all.yml @@ -14,9 +14,14 @@ on: required: true type: 'string' sandbox: - description: 'Which Sandbox to push to' + description: 'Which Sandbox to push to?' required: true type: 'string' + is_sandbox: + description: 'Is this deploying to a sandbox?' + required: false + default: false + type: boolean secrets: AWS_ASSUME_ROLE: required: true @@ -94,6 +99,20 @@ jobs: secrets: AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} + deploy_delete_document_object_handler: + name: Deploy delete_document_object_handler + uses: ./.github/workflows/base-lambdas-reusable-deploy.yml + with: + environment: ${{ inputs.environment}} + python_version: ${{ inputs.python_version }} + build_branch: ${{ inputs.build_branch}} + sandbox: ${{ inputs.sandbox }} + lambda_handler_name: delete_document_object_handler + lambda_aws_name: DeleteDocumentObjectS3 + lambda_layer_names: 'core_lambda_layer' + secrets: + AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} + deploy_document_manifest_job_lambda: name: Deploy document_manifest_job_lambda uses: ./.github/workflows/base-lambdas-reusable-deploy.yml @@ -389,6 +408,21 @@ jobs: secrets: AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} + deploy_get_document_reference_lambda: + name: Deploy get nrl document reference lambda + uses: ./.github/workflows/base-lambdas-reusable-deploy.yml + with: + environment: ${{ inputs.environment}} + python_version: ${{ inputs.python_version }} + build_branch: ${{ inputs.build_branch}} + sandbox: ${{ inputs.sandbox }} + lambda_handler_name: nrl_get_document_reference_handler + lambda_aws_name: GetDocumentReference + lambda_layer_names: 'core_lambda_layer' + secrets: + AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} + + deploy_edge_presign_lambda: name: Deploy edge presign cloudfront lambda uses: ./.github/workflows/base-lambdas-edge-deploy.yml @@ -403,6 +437,7 @@ jobs: AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} deploy_mns_notification_lambda: + if: ${{ ! inputs.is_sandbox }} name: Deploy mns notification lambda uses: ./.github/workflows/base-lambdas-reusable-deploy.yml with: diff --git a/.github/workflows/full-deploy-to-sandbox.yml b/.github/workflows/full-deploy-to-sandbox.yml index d0b764d08..9012298dc 100644 --- a/.github/workflows/full-deploy-to-sandbox.yml +++ b/.github/workflows/full-deploy-to-sandbox.yml @@ -85,6 +85,7 @@ jobs: sandbox: ${{ inputs.sandbox }} environment: ${{ inputs.environment }} python_version: "3.11" + is_sandbox: true secrets: AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} diff --git a/.github/workflows/lambdas-deploy-feature-to-sandbox.yml b/.github/workflows/lambdas-deploy-feature-to-sandbox.yml index 8d3a6478a..82d158bab 100644 --- a/.github/workflows/lambdas-deploy-feature-to-sandbox.yml +++ b/.github/workflows/lambdas-deploy-feature-to-sandbox.yml @@ -72,6 +72,7 @@ jobs: python_version: "3.11" build_branch: ${{ inputs.build_branch }} sandbox: ${{ inputs.sandbox }} + is_sandbox: true secrets: AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} diff --git a/.github/workflows/lambdas-dev-to-main-ci.yml b/.github/workflows/lambdas-dev-to-main-ci.yml index e18a2e3ce..b1655e923 100644 --- a/.github/workflows/lambdas-dev-to-main-ci.yml +++ b/.github/workflows/lambdas-dev-to-main-ci.yml @@ -11,6 +11,10 @@ on: - main paths: - 'lambdas/**' + workflow_call: + secrets: + AWS_ASSUME_ROLE: + required: true permissions: pull-requests: write diff --git a/.github/workflows/ui-dev-to-main-ci.yml b/.github/workflows/ui-dev-to-main-ci.yml index 6ce6b70fe..29571e861 100644 --- a/.github/workflows/ui-dev-to-main-ci.yml +++ b/.github/workflows/ui-dev-to-main-ci.yml @@ -12,6 +12,10 @@ on: - main paths: - 'app/**' + workflow_call: + secrets: + AWS_ASSUME_ROLE: + required: true permissions: pull-requests: write diff --git a/app/cypress/e2e/0-ndr-core-tests/auth_routes/auth_general_browser_states.cy.js b/app/cypress/e2e/0-ndr-core-tests/auth_routes/auth_general_browser_states.cy.js index f31c91792..643531bfa 100644 --- a/app/cypress/e2e/0-ndr-core-tests/auth_routes/auth_general_browser_states.cy.js +++ b/app/cypress/e2e/0-ndr-core-tests/auth_routes/auth_general_browser_states.cy.js @@ -1,5 +1,7 @@ import authPayload from '../../../fixtures/requests/auth/GET_TokenRequest_GP_ADMIN.json'; import { Roles } from '../../../support/roles'; +import dbItem from '../../../fixtures/dynamo-db-items/active-patient.json'; +import searchPatientPayload from '../../../fixtures/requests/GET_SearchPatientLGUpload.json'; describe('Authentication & Authorisation', () => { const baseUrl = Cypress.config('baseUrl'); @@ -86,4 +88,42 @@ describe('Authentication & Authorisation', () => { }, ); }); + + context('Page refresh redirection ', () => { + const workspace = Cypress.env('WORKSPACE'); + dbItem.FileLocation = dbItem.FileLocation.replace('{env}', workspace); + + const lloydGeorgeRecordUrl = '/patient/lloyd-george-record'; + const verifyUrl = '/patient/verify'; + const patientSearchUrl = '/patient/search'; + + it( + 'Refreshing the browser after searching for a patient will return the user to the patient search page', + { tags: 'regression ', defaultCommandTimeout: 20000 }, + () => { + cy.login(Roles.GP_ADMIN); + cy.visit(patientSearchUrl); + + cy.intercept('GET', '/SearchPatient*', { + statusCode: 200, + body: searchPatientPayload, + }).as('search'); + cy.intercept('POST', '/LloydGeorgeStitch*', { + statusCode: 404, + }).as('stitch'); + + cy.getByTestId('nhs-number-input').type(searchPatientPayload.nhsNumber); + cy.getByTestId('search-submit-btn').click(); + + cy.url().should('contain', verifyUrl); + cy.get('#verify-submit').click(); + + cy.url().should('contain', lloydGeorgeRecordUrl); + + cy.reload(); + + cy.url().should('contain', patientSearchUrl); + }, + ); + }); }); 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 8aa60367b..e4b36585c 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 @@ -10,8 +10,7 @@ const downloadPageTitle = 'Download the Lloyd George record for this patient - Access and store digital patient documents'; const downloadingPageTitle = 'Downloading documents - Access and store digital patient documents'; const downloadCompletePageTitle = 'Download complete - Access and store digital patient documents'; -const verifyPatientPageTitle = - 'Patient details - Access and store digital patient documents'; +const verifyPatientPageTitle = 'Patient details - Access and store digital patient documents'; const lloydGeorgeRecordPageTitle = 'Available records - Access and store digital patient documents'; const testFiles = [ { @@ -19,18 +18,21 @@ const testFiles = [ created: '2024-05-07T14:52:00.827602Z', virusScannerResult: 'Clean', id: 'test-id', + fileSize: 200, }, { fileName: '2of2_testy_test.pdf', created: '2024-05-07T14:52:00.827602Z', virusScannerResult: 'Clean', id: 'test-id-2', + fileSize: 200, }, { fileName: '1of1_lone_test_file.pdf', created: '2024-01-01T14:52:00.827602Z', virusScannerResult: 'Clean', id: 'test-id-3', + fileSize: 200, }, ]; @@ -40,6 +42,7 @@ const singleTestFile = [ created: '2024-01-01T14:52:00.827602Z', virusScannerResult: 'Clean', id: 'test-id-3', + fileSize: 200, }, ]; diff --git a/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js b/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js index fb0b721c5..d6bd44e4b 100644 --- a/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js +++ b/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js @@ -73,6 +73,7 @@ describe('GP Workflow: Upload Lloyd George record', () => { 'You can upload full or part of a patient record', ); cy.getByTestId('upload-patient-record-button').should('exist'); + cy.contains('Control and F').should('not exist'); cy.getByTestId('upload-patient-record-button').click(); uploadedFilePathNames.forEach((file) => { cy.getByTestId('button-input').selectFile(file, { force: true }); diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.test.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.test.tsx index 92712f6d7..382a1e399 100644 --- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.test.tsx +++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.test.tsx @@ -79,6 +79,9 @@ describe('LloydGeorgeDownloadStage', () => { const expectedTestId = 'download-file-header-' + mockPdf.numberOfFiles + '-files'; expect(screen.getByTestId(expectedTestId)).toBeInTheDocument(); + expect(screen.getByTestId('cancel-download-link')).toHaveTextContent( + 'Cancel and return to patient record', + ); }); it('renders a progress bar', () => { diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx index d43a23340..910acb237 100644 --- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx +++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx @@ -95,6 +95,10 @@ function LloydGeorgeDownloadStage({ const onPageLoad = async () => { progressTimer.stop(); window.clearInterval(intervalTimer); + if (!nhsNumber) { + navigate(routes.SEARCH_PATIENT); + return; + } try { const preSignedUrl = await getPresignedUrlForZip({ baseUrl, @@ -189,7 +193,7 @@ function LloydGeorgeDownloadStage({ navigate(routes.LLOYD_GEORGE); }} > - Cancel + Cancel and return to patient record diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx index b47fbd178..c9f43495d 100644 --- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx +++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx @@ -85,6 +85,10 @@ function LloydGeorgeSelectDownloadStage({ useEffect(() => { const onPageLoad = async () => { setSubmissionSearchState(SEARCH_AND_DOWNLOAD_STATE.SEARCH_PENDING); + if (!nhsNumber) { + navigate(routes.SEARCH_PATIENT); + return; + } try { // This check is in place for when we navigate directly to a full download, // in that instance we do not need to get a list of selectable files as we will download all files diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.test.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.test.tsx index 877a70902..048ae2d04 100644 --- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.test.tsx +++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.test.tsx @@ -5,7 +5,6 @@ import usePatient from '../../../../helpers/hooks/usePatient'; import { LinkProps } from 'react-router-dom'; import LloydGeorgeSelectSearchResults, { Props } from './LloydGeorgeSelectSearchResults'; import userEvent from '@testing-library/user-event'; -import { routes } from '../../../../types/generic/routes'; import { SEARCH_AND_DOWNLOAD_STATE } from '../../../../types/pages/documentSearchResultsPage/types'; import { runAxeTest } from '../../../../helpers/test/axeTestHelper'; @@ -57,6 +56,20 @@ describe('LloydGeorgeSelectSearchResults', () => { expect(screen.getByTestId('toggle-selection-btn')).toBeInTheDocument(); }); + it('renders the correct table headers', () => { + renderComponent({ selectedDocuments: mockSelectedDocuments }); + + const headers = screen.getAllByRole('columnheader'); + const expectedHeaders = ['Selected', 'Filename', 'Upload date', 'File size']; + + expectedHeaders.forEach((headerText, index) => { + expect(headers[index]).toHaveTextContent(headerText); + }); + + const filesTable = screen.getByTestId('available-files-table-title'); + expect(filesTable).toHaveTextContent(/bytes|KB|MB|GB/); + }); + it('shows error box when download selected files button is clicked but no files selected', async () => { renderComponent({ selectedDocuments: [] }); diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.tsx index 8a7da7ecc..8a009b5c8 100644 --- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.tsx +++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults.tsx @@ -8,6 +8,7 @@ import { SEARCH_AND_DOWNLOAD_STATE } from '../../../../types/pages/documentSearc import ErrorBox from '../../../layout/errorBox/ErrorBox'; import PatientSummary from '../../../generic/patientSummary/PatientSummary'; import BackButton from '../../../generic/backButton/BackButton'; +import formatFileSize from '../../../../helpers/utils/formatFileSize'; export type Props = { searchResults: Array; @@ -108,6 +109,7 @@ const AvailableFilesTable = ({ )} Filename Upload date + File size @@ -147,6 +149,12 @@ const AvailableFilesTable = ({ > {getFormattedDatetime(new Date(result.created))} + + {formatFileSize(result.fileSize)} + ))} diff --git a/app/src/components/generic/recordCard/RecordCard.tsx b/app/src/components/generic/recordCard/RecordCard.tsx index 42035c7c9..6825921c2 100644 --- a/app/src/components/generic/recordCard/RecordCard.tsx +++ b/app/src/components/generic/recordCard/RecordCard.tsx @@ -72,10 +72,12 @@ function RecordCard({ View in full screen )} -

- To search within this record use Control and{' '} - F -

+ {cloudFrontUrl && ( +

+ To search within this record use Control and{' '} + F +

+ )}
{children}
diff --git a/app/src/components/layout/footer/Footer.test.tsx b/app/src/components/layout/footer/Footer.test.tsx index 258b5728c..40450f578 100644 --- a/app/src/components/layout/footer/Footer.test.tsx +++ b/app/src/components/layout/footer/Footer.test.tsx @@ -8,6 +8,10 @@ describe('Footer', () => { render(