Skip to content

Commit

Permalink
(feat) O3-2953: Add the ability to edit and delete vitals and biometr…
Browse files Browse the repository at this point in the history
…ics (#1812)

* (feat) Implement Edit and Delete actions for vitals and biometrics form

* Review feedback

---------

Co-authored-by: Dennis Kigen <[email protected]>
  • Loading branch information
senthil-athiban and denniskigen authored Jun 14, 2024
1 parent 42e246c commit 8427782
Show file tree
Hide file tree
Showing 16 changed files with 663 additions and 91 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ yarn turbo run test:watch
To run tests for a specific package, pass the package name to the `--filter` flag. For example, to run tests for `esm-patient-conditions-app`, run:

```bash
yarn turbo run test --filter="esm-patient-conditions-app"
yarn turbo run test --filter=@openmrs/esm-patient-conditions-app
```

To run a specific test file, run:
Expand Down
22 changes: 22 additions & 0 deletions __mocks__/vitals-and-biometrics.mock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { type PatientVitalsAndBiometrics } from '../packages/esm-patient-vitals-app/src/common';

export const mockBiometricsResponse = {
resourceType: 'Bundle',
id: '0b0f8529-3e6e-41d9-8007-0ef639fb893b',
Expand Down Expand Up @@ -6057,6 +6059,26 @@ export const formattedVitals = [
},
];

export const formattedVitalsAndBiometrics: Array<PatientVitalsAndBiometrics> = [
{
id: '0',
uuid: 'fd481c9f-f81f-4aaf-b00b-747900af9935',
date: '2024-04-21T09:31:27.000Z',
height: 12,
weight: 12,
bmi: 833.3,
bloodPressureRenderInterpretation: 'normal',
systolic: 1,
diastolic: 2,
pulse: 12,
respiratoryRate: 12,
spo2: 12,
temperature: 31,
muac: 12,
generalPatientNote: 'notes',
},
];

export const mockVitalsConceptMetadata = mockVitalsSignsConcepts.data.results[0].setMembers;

export const mockConceptUnits = new Map<string, string>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,12 @@ describe('BiometricsOverview: ', () => {
expect(screen.getByRole('tab', { name: /table view/i })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /chart view/i })).toBeInTheDocument();
expect(screen.getByRole('link', { name: /see all/i })).toBeInTheDocument();

const initialRowElements = screen.getAllByRole('row');
const extractCellsExcludingActions = (row) => {
const cells = Array.from(row.querySelectorAll('td'));
return cells.filter((cell: HTMLElement) => cell.id !== 'actions');
};
const initialRows = screen.getAllByRole('row');
const initialCellsExcludingActions = initialRows.map(extractCellsExcludingActions);

const expectedColumnHeaders = [/date/, /weight/, /height/, /bmi/, /muac/];
expectedColumnHeaders.map((header) =>
Expand All @@ -117,15 +121,15 @@ describe('BiometricsOverview: ', () => {
await user.click(sortRowsButton);
// Sorting in ascending order
await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).not.toEqual(initialRowElements);
const initialSortedCellsExcludingActions = screen.getAllByRole('row').map(extractCellsExcludingActions);
expect(initialSortedCellsExcludingActions).not.toEqual(initialCellsExcludingActions);

// Sorting order = NONE, hence it is still in the ascending order
await user.click(sortRowsButton);
// Sorting in descending order
await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).toEqual(initialRowElements);
const finalCellsSortedExcludingActions = screen.getAllByRole('row').map(extractCellsExcludingActions);
expect(finalCellsSortedExcludingActions).toEqual(initialCellsExcludingActions);
});

it('toggles between rendering either a tabular view or a chart view', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { useLayoutType, usePagination } from '@openmrs/esm-framework';
import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
import type { BiometricsTableHeader, BiometricsTableRow } from './types';
import { VitalsAndBiometricsActionMenu } from '../vitals-biometrics-form/vitals-biometrics-action-menu.component';

interface PaginatedBiometricsProps {
tableRows: Array<BiometricsTableRow>;
Expand Down Expand Up @@ -94,6 +95,7 @@ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
{header.header?.content ?? header.header}
</TableHeader>
))}
<TableHeader />
</TableRow>
</TableHead>
<TableBody>
Expand All @@ -102,6 +104,9 @@ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
))}
<TableCell className="cds--table-column-menu" id="actions">
<VitalsAndBiometricsActionMenu rowId={row.id} formType="biometrics" />
</TableCell>
</TableRow>
))}
</TableBody>
Expand Down
71 changes: 59 additions & 12 deletions packages/esm-patient-vitals-app/src/common/data.resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import { type ObsRecord } from '@openmrs/esm-patient-common-lib';
import { type KeyedMutator } from 'swr';
import { type ConfigObject } from '../config-schema';
import { assessValue, calculateBodyMassIndex, getReferenceRangesForConcept, interpretBloodPressure } from './helpers';
import type { FHIRSearchBundleResponse, MappedVitals, PatientVitalsAndBiometrics, VitalsResponse } from './types';
import type {
Encounter,
FHIRSearchBundleResponse,
MappedVitals,
PatientVitalsAndBiometrics,
VitalsResponse,
} from './types';
import { type VitalsBiometricsFormData } from '../vitals-biometrics-form/vitals-biometrics-form.workspace';

const pageSize = 100;
Expand Down Expand Up @@ -120,7 +126,6 @@ export function useVitalsAndBiometrics(patientUuid: string, mode: VitalsAndBiome
() => [concepts.heightUuid, concepts.midUpperArmCircumferenceUuid, concepts.weightUuid],
[concepts.heightUuid, concepts.midUpperArmCircumferenceUuid, concepts.weightUuid],
);

const conceptUuids = useMemo(
() =>
(mode === 'both'
Expand Down Expand Up @@ -205,7 +210,6 @@ export function useVitalsAndBiometrics(patientUuid: string, mode: VitalsAndBiome
.map(vitalsProperties(conceptMetadata))
?.reduce((vitalsHashTable, vitalSign) => {
const recordedDate = new Date(new Date(vitalSign.recordedDate)).toISOString();

if (vitalsHashTable.has(recordedDate) && vitalsHashTable.get(recordedDate)) {
vitalsHashTable.set(recordedDate, {
...vitalsHashTable.get(recordedDate),
Expand All @@ -215,6 +219,7 @@ export function useVitalsAndBiometrics(patientUuid: string, mode: VitalsAndBiome
} else {
vitalSign.value &&
vitalsHashTable.set(recordedDate, {
uuid: vitalSign.uuid,
[getVitalsMapKey(vitalSign.code)]: vitalSign.value,
[getInterpretationKey(getVitalsMapKey(vitalSign.code))]: vitalSign.interpretation,
});
Expand Down Expand Up @@ -292,15 +297,25 @@ function handleFetch({ patientUuid, conceptUuids, page, prevPageData }: VitalsAn
* @internal
*/
function vitalsProperties(conceptMetadata: Array<ConceptMetadata> | undefined) {
return (resource: FHIRResource['resource']): MappedVitals => ({
code: resource?.code?.coding?.[0]?.code,
interpretation: assessValue(
resource?.valueQuantity?.value,
getReferenceRangesForConcept(resource?.code?.coding?.[0]?.code, conceptMetadata),
),
recordedDate: resource?.effectiveDateTime,
value: resource?.valueQuantity?.value,
});
return (resource: FHIRResource['resource']): MappedVitals => {
const { code, effectiveDateTime, encounter, valueQuantity, valueString } = resource || {};
let uuid;
const idx = encounter?.reference.lastIndexOf('/') ?? -1;
if (idx >= 0) {
uuid = encounter.reference.slice(idx + 1);
}
const value = valueQuantity?.value || valueString;
return {
code: code?.coding?.[0]?.code,
interpretation: assessValue(
valueQuantity?.value,
getReferenceRangesForConcept(code?.coding?.[0]?.code, conceptMetadata),
),
recordedDate: effectiveDateTime,
uuid,
value,
};
};
}

export function saveVitalsAndBiometrics(
Expand Down Expand Up @@ -353,6 +368,21 @@ export function updateVitalsAndBiometrics(
});
}

export function deleteVitalsAndBiometrics(encounterUuid: string, encounter: Encounter) {
const abortController = new AbortController();

return openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
signal: abortController.signal,
body: {
obs: encounter.obs,
},
});
}

function createObsObject(
vitals: VitalsBiometricsFormData,
concepts: ConfigObject['concepts'],
Expand All @@ -373,3 +403,20 @@ function createObsObject(
export async function invalidateCachedVitalsAndBiometrics() {
vitalsHooksMutates.forEach((mutate) => mutate());
}

export function getEncounterByUuid(encounterUuid: string) {
const customRepresentation =
'custom:(uuid,encounterDatetime,' +
'patient:(uuid,uuid,person,identifiers:full),' +
'visit:(uuid,visitType,display,startDatetime,stopDatetime),' +
'location:ref,encounterType:ref,' +
'encounterProviders:(uuid,display,provider:(uuid,display)),orders:full,' +
'obs:(uuid,obsDatetime,concept:(uuid,uuid,name:(display)),value:ref),' +
'diagnoses:(uuid,diagnosis,certainty,rank,voided,display))';
return openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}?v=${customRepresentation}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
}
134 changes: 127 additions & 7 deletions packages/esm-patient-vitals-app/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,76 @@ export type FHIRSearchBundleResponse = FetchResponse<{
link: Array<{ relation: string; url: string }>;
}>;

export interface Diagnosis {
uuid: string;
display: string;
diagnosis: {
coded?: {
uuid: string;
display?: string;
};
nonCoded?: string;
};
certainty: string;
rank: number;
}

export interface Encounter {
uuid: string;
encounterDatetime: string;
encounterProviders: Array<{
uuid: string;
display: string;
encounterRole: {
uuid: string;
display: string;
};
provider: {
uuid: string;
person: {
uuid: string;
display: string;
};
};
}>;
encounterType: {
uuid: string;
display: string;
};
visit?: Visit;
obs: Array<Observation>;
// obs: Array<any>;
orders: Array<Order>;
diagnoses: Array<Diagnosis>;
patient: OpenmrsResource;
location: OpenmrsResource;
}

export type MappedVitals = {
code: string;
interpretation: string;
uuid: string;
recordedDate: Date;
value: number | string;
};

export interface Observation {
uuid: string;
concept: {
uuid: string;
display: string;
conceptClass: {
uuid: string;
display: string;
};
};
display: string;
value: any;
obsDatetime: string;
}

export type ObservationInterpretation = 'critically_low' | 'critically_high' | 'high' | 'low' | 'normal';

export interface ObsReferenceRanges {
hiAbsolute: ReferenceRangeValue;
hiCritical: ReferenceRangeValue;
Expand All @@ -16,17 +86,60 @@ export interface ObsReferenceRanges {
lowAbsolute: ReferenceRangeValue;
}

export type ObservationInterpretation = 'critically_low' | 'critically_high' | 'high' | 'low' | 'normal';
export interface OpenmrsResource {
display: string;
uuid: string;
links?: Array<{ rel: string; uri: string }>;
}

export type MappedVitals = {
code: string;
interpretation: string;
recordedDate: Date;
value: number;
};
export interface Order {
uuid: string;
dateActivated: string;
dose: number;
doseUnits: {
uuid: string;
display: string;
};
orderNumber: number;
display: string;
drug: {
uuid: string;
name: string;
strength: string;
};
duration: number;
durationUnits: {
uuid: string;
display: string;
};
frequency: {
uuid: string;
display: string;
};
numRefills: number;
orderer: {
uuid: string;
person: {
uuid: string;
display: string;
};
};
orderType: {
uuid: string;
display: string;
};
route: {
uuid: string;
display: string;
};
auditInfo: {
dateVoided: string;
};
}

export interface PatientVitalsAndBiometrics {
id: string;
uuid?: string;
date: string;
systolic?: number;
diastolic?: number;
Expand All @@ -39,6 +152,7 @@ export interface PatientVitalsAndBiometrics {
bmi?: number | null;
respiratoryRate?: number;
muac?: number;
generalPatientNote?: string;
}

export interface VitalsResponse {
Expand All @@ -57,3 +171,9 @@ export interface VitalsResponse {
total: number;
type: string;
}

export interface Visit {
uuid: string;
startDatetime: string;
stopDatetime?: string;
}
1 change: 1 addition & 0 deletions packages/esm-patient-vitals-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export interface ConfigObject {
weightUuid: string;
respiratoryRateUuid: string;
midUpperArmCircumferenceUuid: string;
generalPatientNoteUuid: string;
};
vitals: {
useFormEngine: boolean;
Expand Down
Loading

0 comments on commit 8427782

Please sign in to comment.