Skip to content

Commit

Permalink
Merge branch 'main' into PRMP-1074
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverbeumkes-nhs authored Dec 17, 2024
2 parents 3858025 + 8bdeea0 commit 3e63f0e
Show file tree
Hide file tree
Showing 27 changed files with 206 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('GP Workflow: Patient search and verify', () => {
cy.get('#nhs-number-input').type(testPatient);
cy.title().should(
'eq',
'Search for patient - Access and store digital patient documents',
'Search for a patient - Access and store digital patient documents',
);

cy.get('#search-submit').click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ describe('GP Workflow: View Lloyd George record', () => {

// cancel delete
cy.wait('@searchDocs');
cy.getByTestId('start-again-btn').click();
cy.contains('Go back').click();

// assert user is returned to view Lloyd George page
cy.contains('Lloyd George record').should('be.visible');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function DeleteResultStage({ numberOfFiles, setDownloadStage }: Props) {
</Card.Content>
</Card>
<p>You can no longer access this record using our storage.</p>
<p className="lloydgeorge_delete-complete_paragraph-headers">What happens next</p>
<h2 className="nhsuk-heading-l">What happens next</h2>
<ol>
<li>
Make sure you safely store the paper version of this record, as it may be the
Expand All @@ -56,17 +56,13 @@ function DeleteResultStage({ numberOfFiles, setDownloadStage }: Props) {
If you think you’ve made a mistake, you will need to upload this record again
</li>
</ol>
<p className="lloydgeorge_delete-complete_paragraph-subheaders">
Your responsibilities after removing this record
</p>
<h3 className="nhsuk-heading-m">Your responsibilities after removing this record</h3>
<p>
Everyone in a health and care organisation is responsible for managing records
appropriately. It is important all general practice staff understand their
responsibilities for creating, and disposing of records appropriately.
</p>
<p className="lloydgeorge_delete-complete_paragraph-subheaders">
Follow the Record Management Code of Practice
</p>
<h3 className="nhsuk-heading-m">Follow the Record Management Code of Practice</h3>
<p>
The{' '}
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ describe('RemoveRecordStage', () => {
).toBeInTheDocument();
});

expect(screen.getByRole('button', { name: 'Start again' })).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: 'Remove all files' }),
).not.toBeInTheDocument();
Expand All @@ -124,7 +123,6 @@ describe('RemoveRecordStage', () => {
).toBeInTheDocument();
});

expect(screen.getByRole('button', { name: 'Start again' })).toBeInTheDocument();
expect(screen.getByText(searchResults[0].fileName)).toBeInTheDocument();
expect(screen.getByText(searchResults[1].fileName)).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
import useTitle from '../../../../helpers/hooks/useTitle';
import { Button, Table, WarningCallout } from 'nhsuk-react-components';
import LinkButton from '../../../generic/linkButton/LinkButton';
import { SearchResult } from '../../../../types/generic/searchResult';
import getDocumentSearchResults from '../../../../helpers/requests/getDocumentSearchResults';
import usePatient from '../../../../helpers/hooks/usePatient';
Expand Down Expand Up @@ -165,17 +164,6 @@ function RemoveRecordStage({ numberOfFiles, recordType, setDownloadStage, resetD
Remove all files
</Button>
)}
<LinkButton
id="start-again-link"
data-testid="start-again-btn"
type="button"
className="mb-7 ml-3"
onClick={() => {
navigate(routes.LLOYD_GEORGE);
}}
>
Start again
</LinkButton>
</div>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,13 @@ function LloydGeorgeDownloadComplete({
</ol>
</>
)}
<p className="lloydgeorge_download-complete_paragraph-headers">
Your responsibilities with this record
</p>
<h2 className="nhsuk-heading-l">Your responsibilities with this record</h2>
<p>
Everyone in a health and care organisation is responsible for managing records
appropriately. It is important all general practice staff understand their
responsibilities for creating, maintaining, and disposing of records appropriately.
</p>
<p className="lloydgeorge_download-complete_paragraph-subheaders">
Follow the Record Management Code of Practice
</p>
<h3 className="nhsuk-heading-m">Follow the Record Management Code of Practice</h3>
<p>
The{' '}
<a href="https://transform.england.nhs.uk/information-governance/guidance/records-management-code">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('LloydGeorgeUploadInfectedStage', () => {
});

describe('Navigation', () => {
it('navigates to search for patient page when button is clicked', () => {
it('navigates to search for a patient page when button is clicked', () => {
render(
<LloydGeorgeUploadInfectedStage
documents={[uploadDocument]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,21 @@ function LloydGeorgeViewRecordStage({

<h1>{pageHeader}</h1>
<PatientSimpleSummary />
{fullScreen ? (
<p>
To search within this record use <strong>Control</strong> and <strong>F</strong>
</p>
) : (
<p />
)}

{!fullScreen ? (
<div className="lloydgeorge_record-stage_flex">
<RecordMenuCard
recordLinks={recordLinksToShow}
setStage={setStage}
showMenu={showMenu}
/>

<div
className={`lloydgeorge_record-stage_flex-row lloydgeorge_record-stage_flex-row${menuClass}`}
>
Expand Down
4 changes: 4 additions & 0 deletions app/src/components/generic/recordCard/RecordCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ function RecordCard({
View in full screen
</button>
)}
<p>
To search within this record use <strong>Control</strong> and{' '}
<strong>F</strong>
</p>
</Card.Content>
<div>{children}</div>
</Card>
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/patientSearchPage/PatientSearchPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('PatientSearchPage', () => {
mockedUseRole.mockReturnValue(role);

renderPatientSearchPage();
expect(screen.getByText('Search for patient')).toBeInTheDocument();
expect(screen.getByText('Search for a patient')).toBeInTheDocument();
expect(
screen.getByRole('textbox', { name: 'Enter NHS number' }),
).toBeInTheDocument();
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/patientSearchPage/PatientSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function PatientSearchPage() {
setSubmissionState(SEARCH_STATES.SUCCEEDED);
navigate(routes.VERIFY_PATIENT);
};
const pageTitle = 'Search for patient';
const pageTitle = 'Search for a patient';
useTitle({ pageTitle: pageTitle });

const handleSearch = async (data: FieldValues) => {
Expand Down
2 changes: 1 addition & 1 deletion lambdas/handlers/manage_nrl_pointer_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def lambda_handler(event, context):
f"Processing SQS message for nhs number: {nrl_message.nhs_number}"
)
nrl_verified_message = nrl_message.model_dump(
by_alias=True, exclude_none=True, exclude_defaults=True
by_alias=True, exclude_none=True
)
match nrl_message.action:
case NrlActionTypes.CREATE:
Expand Down
2 changes: 1 addition & 1 deletion lambdas/models/nrl_fhir_document_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class FhirDocumentReference(BaseModel):
snomed_code_doc_type: str = "None"
snomed_code_category: str = "None"
snomed_code_category_display: str = "Care plan"
attachment: Optional[NrlAttachment] = {}
attachment: Optional[NrlAttachment] = NrlAttachment()

def build_fhir_dict(self):
snomed_url = "http://snomed.info/sct"
Expand Down
9 changes: 5 additions & 4 deletions lambdas/models/nrl_sqs_message.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import Optional

from enums.snomed_codes import SnomedCodesCategory, SnomedCodesType
from pydantic import AliasGenerator, BaseModel, ConfigDict
from pydantic.alias_generators import to_camel


class NrlAttachment(BaseModel):
content_type: str = ""
language: str = "en-US"
content_type: str = "application/pdf"
language: str = "en-UK"
url: str = ""
size: int = 0
hash: str = ""
Expand All @@ -20,8 +21,8 @@ class NrlSqsMessage(BaseModel):
)

nhs_number: str
snomed_code_doc_type: str
snomed_code_category: str
snomed_code_doc_type: str = SnomedCodesType.LLOYD_GEORGE
snomed_code_category: str = SnomedCodesCategory.CARE_PLAN
description: str = ""
attachment: Optional[NrlAttachment] = None
action: str
11 changes: 11 additions & 0 deletions lambdas/repositories/bulk_upload/bulk_upload_sqs_repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import uuid

from models.nrl_sqs_message import NrlSqsMessage
from models.staging_metadata import StagingMetadata
from services.base.sqs_service import SQSService
from utils.audit_logging_setup import LoggingService
Expand Down Expand Up @@ -38,4 +39,14 @@ def put_sqs_message_back_to_queue(self, sqs_message: dict):
queue_url=self.metadata_queue_url,
message_body=sqs_message["body"],
nhs_number=nhs_number,
group_id=f"back_to_queue_bulk_upload_{uuid.uuid4()}",
)

def send_message_to_nrl_fifo(
self, queue_url: str, message: NrlSqsMessage, group_id: str
):
self.sqs_repository.send_message_fifo(
queue_url=queue_url,
message_body=message.model_dump_json(),
group_id=group_id,
)
4 changes: 4 additions & 0 deletions lambdas/services/base/s3_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ def list_all_objects(self, bucket_name: str) -> list[dict]:
for paginated_result in s3_paginator.paginate(Bucket=bucket_name):
s3_list_objects_result += paginated_result.get("Contents", [])
return s3_list_objects_result

def get_file_size(self, s3_bucket_name: str, object_key: str) -> int:
response = self.client.head_object(Bucket=s3_bucket_name, Key=object_key)
return response.get("ContentLength", 0)
4 changes: 3 additions & 1 deletion lambdas/services/bulk_upload_metadata_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ def csv_to_staging_metadata(csv_file_path: str) -> list[StagingMetadata]:
logger.info("Parsing bulk upload metadata")

patients = {}
with open(csv_file_path, mode="r") as csv_file_handler:
with open(
csv_file_path, mode="r", encoding="utf-8", errors="replace"
) as csv_file_handler:
csv_reader: Iterable[dict] = csv.DictReader(csv_file_handler)
for row in csv_reader:
file_metadata = MetadataFile.model_validate(row)
Expand Down
30 changes: 28 additions & 2 deletions lambdas/services/bulk_upload_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

import pydantic
from botocore.exceptions import ClientError
from enums.nrl_sqs_upload import NrlActionTypes
from enums.patient_ods_inactive_status import PatientOdsInactiveStatus
from enums.upload_status import UploadStatus
from enums.virus_scan_result import VirusScanResult
from models.nhs_document_reference import NHSDocumentReference
from models.nrl_sqs_message import NrlAttachment, NrlSqsMessage
from models.staging_metadata import MetadataFile, StagingMetadata
from repositories.bulk_upload.bulk_upload_dynamo_repository import (
BulkUploadDynamoRepository,
Expand Down Expand Up @@ -52,6 +54,7 @@ def __init__(self):
self.pdf_content_type = "application/pdf"
self.unhandled_messages = []
self.file_path_cache = {}
self.nrl_queue_url = os.environ["NRL_SQS_URL"]

def process_message_queue(self, records: list):
for index, message in enumerate(records, start=1):
Expand Down Expand Up @@ -231,7 +234,9 @@ def handle_sqs_message(self, message: dict):
)

try:
self.create_lg_records_and_copy_files(staging_metadata, patient_ods_code)
last_document_processed = self.create_lg_records_and_copy_files(
staging_metadata, patient_ods_code
)
logger.info(
f"Successfully uploaded the Lloyd George records for patient: {staging_metadata.nhs_number}",
{"Result": "Successful upload"},
Expand Down Expand Up @@ -268,6 +273,25 @@ def handle_sqs_message(self, message: dict):
accepted_reason,
patient_ods_code,
)
if len(file_names) == 1:
document_api_endpoint = (
os.environ.get("APIM_API_URL", "")
+ "/DocumentReference/"
+ last_document_processed.id
)
doc_details = NrlAttachment(
url=document_api_endpoint,
)
nrl_sqs_message = NrlSqsMessage(
nhs_number=staging_metadata.nhs_number,
action=NrlActionTypes.CREATE,
attachment=doc_details,
)
self.sqs_repository.send_message_to_nrl_fifo(
queue_url=self.nrl_queue_url,
message=nrl_sqs_message,
group_id=f"nrl_sqs_{uuid.uuid4()}",
)

def resolve_source_file_path(self, staging_metadata: StagingMetadata):
sample_file_path = staging_metadata.files[0].file_path
Expand Down Expand Up @@ -313,7 +337,7 @@ def create_lg_records_and_copy_files(
self, staging_metadata: StagingMetadata, current_gp_ods: str
):
nhs_number = staging_metadata.nhs_number

document_reference = None
for file_metadata in staging_metadata.files:
document_reference = self.convert_to_document_reference(
file_metadata, nhs_number, current_gp_ods
Expand All @@ -327,6 +351,8 @@ def create_lg_records_and_copy_files(
)
document_reference.set_uploaded_to_true()
self.dynamo_repository.create_record_in_lg_dynamo_table(document_reference)
# returning last document ref until stitching as default is implemented
return document_reference

def rollback_transaction(self):
try:
Expand Down
3 changes: 0 additions & 3 deletions lambdas/services/login_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ def generate_repository_role(self, organisation: dict, smartcard_role: str):
organisation, self.token_handler_ssm_service.get_org_role_codes()[0]
):
return RepositoryRole.GP_ADMIN
return RepositoryRole.NONE

if (
smartcard_role
Expand All @@ -188,15 +187,13 @@ def generate_repository_role(self, organisation: dict, smartcard_role: str):
organisation, self.token_handler_ssm_service.get_org_role_codes()[0]
):
return RepositoryRole.GP_CLINICAL
return RepositoryRole.NONE

if smartcard_role in self.token_handler_ssm_service.get_smartcard_role_pcse():
logger.info("PCSE: smartcard ODS identified")
if self.has_role_org_ods_code(
organisation, self.token_handler_ssm_service.get_org_ods_codes()[0]
):
return RepositoryRole.PCSE
return RepositoryRole.NONE

logger.error(
f"{LambdaError.LoginNoRole.to_str()}", {"Result": "Unsuccessful login"}
Expand Down
2 changes: 1 addition & 1 deletion lambdas/services/nrl_api_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def create_new_pointer(self, body, retry_on_expired: bool = True):
response.raise_for_status()
logger.info("Successfully created new pointer")
except HTTPError as e:
logger.error(e.response)
logger.error(e.response.content)
if e.response.status_code == 401 and retry_on_expired:
self.headers["Authorization"] = (
f"Bearer {self.auth_service.get_active_access_token()}"
Expand Down
3 changes: 3 additions & 0 deletions lambdas/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

MOCK_ZIP_OUTPUT_BUCKET_ENV_NAME = "ZIPPED_STORE_BUCKET_NAME"
MOCK_ZIP_TRACE_TABLE_ENV_NAME = "ZIPPED_STORE_DYNAMODB_NAME"
MOCK_METADATA_NRL_SQS_URL_ENV_NAME = "NRL_SQS_URL"

MOCK_LG_STAGING_STORE_BUCKET_ENV_NAME = "STAGING_STORE_BUCKET_NAME"
MOCK_LG_METADATA_SQS_QUEUE_ENV_NAME = "METADATA_SQS_QUEUE_URL"
Expand Down Expand Up @@ -76,6 +77,7 @@
TEST_CURRENT_GP_ODS = "Y12345"

AUTH_STATE_TABLE_NAME = "test_state_table"
NRL_SQS_URL = "https://test-queue.com"
AUTH_SESSION_TABLE_NAME = "test_session_table"
FAKE_URL = "https://fake-url.com"
OIDC_CALLBACK_URL = FAKE_URL
Expand Down Expand Up @@ -125,6 +127,7 @@ def set_env(monkeypatch):
monkeypatch.setenv(MOCK_LG_METADATA_SQS_QUEUE_ENV_NAME, MOCK_LG_METADATA_SQS_QUEUE)
monkeypatch.setenv(MOCK_LG_INVALID_SQS_QUEUE_ENV_NAME, MOCK_LG_INVALID_SQS_QUEUE)
monkeypatch.setenv(MOCK_AUTH_STATE_TABLE_NAME_ENV_NAME, AUTH_STATE_TABLE_NAME)
monkeypatch.setenv(MOCK_METADATA_NRL_SQS_URL_ENV_NAME, NRL_SQS_URL)
monkeypatch.setenv(MOCK_AUTH_SESSION_TABLE_NAME_ENV_NAME, AUTH_SESSION_TABLE_NAME)
monkeypatch.setenv(MOCK_OIDC_CALLBACK_URL_ENV_NAME, OIDC_CALLBACK_URL)
monkeypatch.setenv(MOCK_OIDC_CLIENT_ID_ENV_NAME, OIDC_CLIENT_ID)
Expand Down
Loading

0 comments on commit 3e63f0e

Please sign in to comment.