Skip to content

Commit

Permalink
Merge branch 'main' into PRMP-1348
Browse files Browse the repository at this point in the history
"merge main"
  • Loading branch information
abid-nhs committed Jan 24, 2025
2 parents 570394b + a29502b commit 988a1ec
Show file tree
Hide file tree
Showing 57 changed files with 1,385 additions and 252 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/base-cypress-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/base-cypress-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
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
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/base-lambdas-reusable-deploy-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -408,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,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,
},
];

Expand All @@ -39,6 +42,7 @@ const singleTestFile = [
created: '2024-01-01T14:52:00.827602Z',
virusScannerResult: 'Clean',
id: 'test-id-3',
fileSize: 200,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ function LloydGeorgeDownloadStage({
navigate(routes.LLOYD_GEORGE);
}}
>
Cancel
Cancel and return to patient record
</Link>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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: [] });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SearchResult>;
Expand Down Expand Up @@ -108,6 +109,7 @@ const AvailableFilesTable = ({
)}
<Table.Cell className={'table-column-header'}>Filename</Table.Cell>
<Table.Cell className={'table-column-header'}>Upload date</Table.Cell>
<Table.Cell className={'table-column-header'}>File size</Table.Cell>
</Table.Row>
</Table.Head>
<Table.Body>
Expand Down Expand Up @@ -146,6 +148,12 @@ const AvailableFilesTable = ({
>
{getFormattedDatetime(new Date(result.created))}
</Table.Cell>
<Table.Cell
id={'available-files-row-' + index + '-file-size'}
data-testid="file-size"
>
{formatFileSize(result.fileSize)}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
Expand Down
10 changes: 6 additions & 4 deletions app/src/components/generic/recordCard/RecordCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ function RecordCard({
View in full screen
</button>
)}
<p>
To search within this record use <strong>Control</strong> and{' '}
<strong>F</strong>
</p>
{cloudFrontUrl && (
<p>
To search within this record use <strong>Control</strong> and{' '}
<strong>F</strong>
</p>
)}
</Card.Content>
<div>{children}</div>
</Card>
Expand Down
1 change: 1 addition & 0 deletions app/src/helpers/test/testBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const buildSearchResult = (searchResultOverride?: Partial<SearchResult>) => {
created: moment().format(),
virusScannerResult: 'Clean',
ID: '1234qwer-241ewewr',
fileSize: 224,
...searchResultOverride,
};
return result;
Expand Down
35 changes: 35 additions & 0 deletions app/src/helpers/utils/formatFileSize.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import formatFileSize from './formatFileSize';

describe('formatFileSize', () => {
it('returns rounded file size formats for valid inputs', () => {
expect(formatFileSize(0)).toBe('0 bytes');
expect(formatFileSize(-0)).toBe('0 bytes');
expect(formatFileSize(1)).toBe('1 bytes');
expect(formatFileSize(1.5)).toBe('2 bytes');

expect(formatFileSize(1023)).toBe('1023 bytes');
expect(formatFileSize(1024)).toBe('1 KB');
expect(formatFileSize(1025)).toBe('1 KB');

expect(formatFileSize(1535)).toBe('1 KB');
expect(formatFileSize(1536)).toBe('2 KB');
expect(formatFileSize(2048)).toBe('2 KB');

expect(formatFileSize(Math.pow(2, 20) - 1)).toBe('1024 KB');
expect(formatFileSize(Math.pow(2, 20))).toBe('1 MB');
expect(formatFileSize(Math.pow(2, 20) + 1)).toBe('1 MB');

expect(formatFileSize(Math.pow(2, 30) - 1)).toBe('1024 MB');
expect(formatFileSize(Math.pow(2, 30))).toBe('1 GB');
expect(formatFileSize(Math.pow(2, 30) + 1)).toBe('1 GB');
});

it('throws "Invalid file size" exception for invalid inputs', () => {
expect(() => formatFileSize(Number.MIN_SAFE_INTEGER)).toThrow('Invalid file size');
expect(() => formatFileSize(-1)).toThrow('Invalid file size');
expect(() => formatFileSize(NaN)).toThrow('Invalid file size');
expect(() => formatFileSize(undefined as unknown as number)).toThrow('Invalid file size');
expect(() => formatFileSize(Math.pow(2, 40))).toThrow('Invalid file size'); // 1TB
expect(() => formatFileSize(Number.MAX_SAFE_INTEGER)).toThrow('Invalid file size');
});
});
4 changes: 2 additions & 2 deletions app/src/pages/privacyPage/PrivacyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function PrivacyPage() {
</section>

<section>
<h1>Our permission to process and store patient data</h1>
<h2>Our permission to process and store patient data</h2>
<p>
This service has legal permission to process and store patient data through the
<strong> National Data Processing Deed</strong>.
Expand Down Expand Up @@ -137,7 +137,7 @@ function PrivacyPage() {
</section>

<section>
<h1>Contact us</h1>
<h2>Contact us</h2>
<p>
If you have any questions about the National Data Processing Deed, or our
privacy policy, you can contact the team on{' '}
Expand Down
1 change: 1 addition & 0 deletions app/src/types/generic/searchResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export type SearchResult = {
created: string;
virusScannerResult: string;
ID: string;
fileSize: number;
};
File renamed without changes.
15 changes: 15 additions & 0 deletions lambdas/enums/fhir/fhir_issue_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from enum import Enum


class FhirIssueCoding(Enum):
INVALID = ("invalid", "Invalid")
FORBIDDEN = ("forbidden", "Forbidden")
NOT_FOUND = ("not-found", "Not Found")
EXCEPTION = ("exception", "Exception")
UNKNOWN = ("unknown", "Unknown User")

def code(self):
return self.value[0]

def display(self):
return self.value[1]
File renamed without changes.
38 changes: 35 additions & 3 deletions lambdas/enums/lambda_error.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
from enum import Enum
from typing import Optional

from enums.fhir.fhir_issue_type import FhirIssueCoding
from utils.error_response import ErrorResponse
from utils.request_context import request_context


class LambdaError(Enum):
def create_error_body(self, params: Optional[dict] = None) -> str:
def create_error_response(self, params: Optional[dict] = None) -> ErrorResponse:
err_code = self.value["err_code"]
message = self.value["message"]
if "%" in message and params:
message = message % params

interaction_id = getattr(request_context, "request_id", None)
error_response = ErrorResponse(
err_code=err_code, message=message, interaction_id=interaction_id
)
return error_response.create()
return error_response

def to_str(self) -> str:
return f"[{self.value['err_code']}] {self.value['message']}"

def create_error_body(self, params: Optional[dict] = None) -> str:
return self.create_error_response(params).create()

"""
Errors for SearchPatientException
"""
Expand Down Expand Up @@ -398,7 +401,35 @@ def to_str(self) -> str:
"err_code": "US_5001",
"message": "Dynamo client error",
}
"""
Errors for fhir get document reference lambda
"""
DocumentReferenceNotFound = {
"err_code": "NRL_DR_4041",
"message": "Document reference not found",
"fhir_coding": FhirIssueCoding.NOT_FOUND,
}
DocumentReferenceGeneralError = {
"err_code": "NRL_DR_4002",
"message": "An error occurred while fetching the document",
"fhir_coding": FhirIssueCoding.EXCEPTION,
}
DocumentReferenceUnauthorised = {
"err_code": "NRL_DR_4011",
"message": "The user was not able to be authenticated",
"fhir_coding": FhirIssueCoding.UNKNOWN,
}
DocumentReferenceInvalidRequest = {
"err_code": "NRL_DR_4001",
"message": "Invalid request",
"fhir_coding": FhirIssueCoding.INVALID,
}

DocumentReferenceForbidden = {
"err_code": "NRL_DR_4031",
"message": "User is unauthorised to view record",
"fhir_coding": FhirIssueCoding.FORBIDDEN,
}
"""
Edge Lambda Errors
"""
Expand Down Expand Up @@ -487,6 +518,7 @@ def to_str(self) -> str:
"message": "Client error",
"err_code": "AB_XXXX",
"interaction_id": "88888888-4444-4444-4444-121212121212",
"fhir_coding": FhirIssueCoding.FORBIDDEN,
}

"""
Expand Down
26 changes: 17 additions & 9 deletions lambdas/handlers/manage_nrl_pointer_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import json
from datetime import datetime

from enums.nrl_sqs_upload import NrlActionTypes
from models.nrl_fhir_document_reference import FhirDocumentReference
from models.fhir.R4.nrl_fhir_document_reference import DocumentReferenceInfo
from models.nrl_sqs_message import NrlSqsMessage
from services.base.nhs_oauth_service import NhsOauthService
from services.base.ssm_service import SSMService
Expand Down Expand Up @@ -49,16 +50,23 @@ def lambda_handler(event, context):
)
match nrl_message.action:
case NrlActionTypes.CREATE:
document = (
FhirDocumentReference(
**nrl_verified_message,
custodian=nrl_api_service.end_user_ods_code,
)
.build_fhir_dict()
.json()
document = DocumentReferenceInfo(
**nrl_verified_message,
custodian=nrl_api_service.end_user_ods_code,
).create_fhir_document_reference_object()
nrl_api_service.create_new_pointer(
document.model_dump(exclude_none=True)
)

logger.info(
f"Create pointer request: Body: {json.loads(document)}, "
f"RequestURL: {nrl_api_service.endpoint}, "
"HTTP Verb: POST, "
f"NHS Number: {nrl_message.nhs_number}, "
f"ODS Code: {nrl_api_service.end_user_ods_code}, "
f"Datetime: {int(datetime.now().timestamp())} "
)

nrl_api_service.create_new_pointer(json.loads(document))
case NrlActionTypes.DELETE:
nrl_api_service.delete_pointer(
nrl_message.nhs_number, nrl_message.snomed_code_doc_type
Expand Down
Loading

0 comments on commit 988a1ec

Please sign in to comment.