Skip to content

Commit

Permalink
Genericize checkIfSomeElementWithPropertyExists to show more availabl…
Browse files Browse the repository at this point in the history
…e data + add formatCoding (#302)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
m-goggins and pre-commit-ci[bot] authored Jan 21, 2025
1 parent 8687971 commit 3377c9e
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 90 deletions.
4 changes: 2 additions & 2 deletions query-connector/e2e/query_workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,13 @@ test.describe("querying with the Query Connector", () => {
.filter({ hasText: "Sexual overexposure" })
.getByRole("row"),
).toHaveCount(5);
// Conditions
// Conditions + Medication Requests (Reason Code)
await expect(
page
.getByRole("table")
.filter({ hasText: "Chlamydial infection, unspecified" })
.getByRole("row"),
).toHaveCount(3);
).toHaveCount(10);
// Diagnostic Reports
await expect(
page
Expand Down
12 changes: 0 additions & 12 deletions query-connector/src/app/CustomQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ interface CustomQuerySpec {
labCodes?: string[];
snomedCodes?: string[];
rxnormCodes?: string[];
classTypeCodes?: string[];
hasSecondEncounterQuery?: boolean;
}

Expand All @@ -19,7 +18,6 @@ export class CustomQuery {
labCodes: string[] = [];
snomedCodes: string[] = [];
rxnormCodes: string[] = [];
classTypeCodes: string[] = [];

// Need default initialization of query strings outstide constructor,
// since the `try` means we might not find the JSON spec
Expand All @@ -29,7 +27,6 @@ export class CustomQuery {
medicationRequestQuery: string = "";
socialHistoryQuery: string = "";
encounterQuery: string = "";
encounterClassTypeQuery: string = "";
immunizationQuery: string = "";

// Some queries need to be batched in waves because their encounter references
Expand All @@ -49,7 +46,6 @@ export class CustomQuery {
this.labCodes = jsonSpec?.labCodes || [];
this.snomedCodes = jsonSpec?.snomedCodes || [];
this.rxnormCodes = jsonSpec?.rxnormCodes || [];
this.classTypeCodes = jsonSpec?.classTypeCodes || [];
this.hasSecondEncounterQuery = jsonSpec?.hasSecondEncounterQuery || false;
this.compileQueries(patientId);
} catch (error) {
Expand All @@ -68,7 +64,6 @@ export class CustomQuery {
const labsFilter = this.labCodes.join(",");
const snomedFilter = this.snomedCodes.join(",");
const rxnormFilter = this.rxnormCodes.join(",");
const classTypeFilter = this.classTypeCodes.join(",");

this.observationQuery =
labsFilter !== ""
Expand All @@ -91,10 +86,6 @@ export class CustomQuery {
snomedFilter !== ""
? `/Encounter?subject=${patientId}&reason-code=${snomedFilter}`
: "";
this.encounterClassTypeQuery =
classTypeFilter !== ""
? `/Encounter?subject=${patientId}&class=${classTypeFilter}`
: "";

this.immunizationQuery = `/Immunization?patient=${patientId}`;
}
Expand All @@ -111,7 +102,6 @@ export class CustomQuery {
this.medicationRequestQuery,
this.socialHistoryQuery,
this.encounterQuery,
this.encounterClassTypeQuery,
this.immunizationQuery,
];
const filteredRequests = queryRequests.filter((q) => q !== "");
Expand All @@ -138,8 +128,6 @@ export class CustomQuery {
return this.socialHistoryQuery;
case "encounter":
return this.encounterQuery;
case "encounterClass":
return this.encounterClassTypeQuery;
case "immunization":
return this.immunizationQuery;
default:
Expand Down
19 changes: 18 additions & 1 deletion query-connector/src/app/format-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ export const formatValueSetsAsQuerySpec = async (
labCodes: labCodes,
snomedCodes: snomedCodes,
rxnormCodes: rxnormCodes,
classTypeCodes: [] as string[],
hasSecondEncounterQuery: secondEncounter,
};

Expand All @@ -320,3 +319,21 @@ export const formatImmunizationRoute = (immunization: Immunization): string => {
);
return readable?.[0].display ?? initial;
};

/**
* Formats a Coding object for display. If the object has a coding array,
* the first coding object is used.
* @param coding - The Coding object.
* @returns The Coding data formatted for display.
*/
export function formatCoding(coding: Coding | undefined) {
if (!coding) {
return "";
}
return (
<>
{" "}
{coding?.display} <br /> {coding?.code} <br /> {coding?.system}{" "}
</>
);
}
1 change: 0 additions & 1 deletion query-connector/src/app/query-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export type QueryStruct = {
labCodes: string[];
snomedCodes: string[];
rxnormCodes: string[];
classTypeCodes: string[];
hasSecondEncounterQuery: boolean;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,11 @@ export interface ConditionTableProps {
* @returns - The ConditionTable component.
*/
const ConditionsTable: React.FC<ConditionTableProps> = ({ conditions }) => {
const anyResolution = checkIfSomeElementWithPropertyExists(
conditions,
const availableElements = checkIfSomeElementWithPropertyExists(conditions, [
"abatementDateTime",
);

const anyStatus = checkIfSomeElementWithPropertyExists(
conditions,
"clinicalStatus",
);

const anyOnset = checkIfSomeElementWithPropertyExists(
conditions,
"onsetDateTime",
);
]);

return (
<Table
Expand All @@ -46,20 +37,22 @@ const ConditionsTable: React.FC<ConditionTableProps> = ({ conditions }) => {
<thead>
<tr className={styles.conditionRow}>
<th>Condition</th>
{anyStatus && <th>Status</th>}
{anyOnset && <th>Onset</th>}
{anyResolution && <th>Resolution</th>}
{availableElements.clinicalStatus && <th>Status</th>}
{availableElements.onsetDateTime && <th>Onset</th>}
{availableElements.abatementDateTime && <th>Resolution</th>}
</tr>
</thead>
<tbody>
{conditions.map((condition) => (
<tr className={styles.conditionRow} key={condition.id}>
<td>{formatCodeableConcept(condition.code ?? {})}</td>
{anyStatus && (
{availableElements.clinicalStatus && (
<td>{formatCodeableConcept(condition.clinicalStatus ?? {})}</td>
)}
{anyOnset && <td>{formatDate(condition.onsetDateTime)}</td>}
{anyResolution && (
{availableElements.onsetDateTime && (
<td>{formatDate(condition.onsetDateTime)}</td>
)}
{availableElements.abatementDateTime && (
<td>{formatDate(condition.abatementDateTime)}</td>
)}
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React from "react";
import Table from "@/app/query/designSystem/table/Table";
import { Encounter } from "fhir/r4";
import { formatCodeableConcept, formatDate } from "../../../../format-service";
import {
formatCodeableConcept,
formatCoding,
formatDate,
} from "../../../../format-service";
import { checkIfSomeElementWithPropertyExists } from "./utils";
import styles from "./resultsTables.module.scss";

Expand All @@ -21,21 +25,19 @@ export interface EncounterTableProps {
const EncounterTable: React.FC<EncounterTableProps> = ({
encounters: encounters,
}) => {
const anyClinicType =
checkIfSomeElementWithPropertyExists(encounters, "class") ||
checkIfSomeElementWithPropertyExists(encounters, "serviceType");

const anyServiceType = checkIfSomeElementWithPropertyExists(
encounters,
const availableElements = checkIfSomeElementWithPropertyExists(encounters, [
"class",
"serviceProvider",
);
"serviceType",
]);

return (
<Table bordered={false} className="margin-top-0-important">
<thead>
<tr className={styles.encountersRow}>
<th>Visit Reason</th>
{anyClinicType && <th>Clinic Type</th>}
{anyServiceType && <th>Service Provider</th>}
{availableElements?.class && <th>Clinic Type</th>}
{availableElements?.serviceProvider && <th>Service Provider</th>}
<th>Encounter Status</th>
<th>Encounter Start</th>
<th>Encounter End</th>
Expand All @@ -45,15 +47,12 @@ const EncounterTable: React.FC<EncounterTableProps> = ({
{encounters.map((encounter) => (
<tr className={styles.encountersRow} key={encounter.id}>
<td>{formatCodeableConcept(encounter?.reasonCode?.[0])} </td>
{anyClinicType && (
<td>
{formatCodeableConcept(encounter?.class)} <br></br>
{encounter?.serviceType
? formatCodeableConcept(encounter.serviceType)
: ""}
</td>
{availableElements?.class && (
<td>{formatCoding(encounter?.class)}</td>
)}
{availableElements?.serviceProvider && (
<td>{encounter?.serviceProvider?.display}</td>
)}
{anyServiceType && <td>{encounter?.serviceProvider?.display}</td>}
<td>{encounter?.status}</td>
<td>{formatDate(encounter?.period?.start)}</td>
<td>{formatDate(encounter?.period?.end)}</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export interface MedicationRequestTableProps {
const MedicationRequestTable: React.FC<MedicationRequestTableProps> = ({
medicationRequests,
}) => {
const anyReasonCode = checkIfSomeElementWithPropertyExists(
const availableElements = checkIfSomeElementWithPropertyExists(
medicationRequests,
"reasonCode",
["reasonCode"],
);

return (
Expand All @@ -32,7 +32,7 @@ const MedicationRequestTable: React.FC<MedicationRequestTableProps> = ({
<tr className={styles.medicationRow}>
<th>Order Date</th>
<th>Medication</th>
{anyReasonCode && <th>Reason Code</th>}
{availableElements.reasonCode && <th>Reason Code</th>}
<th>Status</th>
</tr>
</thead>
Expand All @@ -45,7 +45,7 @@ const MedicationRequestTable: React.FC<MedicationRequestTableProps> = ({
medicationRequest.medicationCodeableConcept,
)}
</td>
{anyReasonCode && (
{availableElements.reasonCode && (
<td>
{formatCodeableConcept(medicationRequest?.reasonCode?.[0])}
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,37 @@ export interface ObservationTableProps {
const ObservationTable: React.FC<ObservationTableProps> = ({
observations,
}) => {
const anyObsInterpretation = checkIfSomeElementWithPropertyExists(
observations,
const availableElements = checkIfSomeElementWithPropertyExists(observations, [
"interpretation",
);
const anyReferenceRange = checkIfSomeElementWithPropertyExists(
observations,
"referenceRange",
);
]);
return (
<Table bordered={false} className={classNames("margin-top-0-important")}>
<thead>
<tr className={styles.observationRow}>
<th>Date</th>
<th>Type</th>
{anyObsInterpretation && <th>Interpretation</th>}
{availableElements.interpretation && <th>Interpretation</th>}
<th>Value</th>
{anyReferenceRange && <th>Reference Range</th>}
{availableElements.referenceRange && <th>Reference Range</th>}
</tr>
</thead>
<tbody>
{observations.map((obs) => (
<tr className={styles.observationRow} key={obs.id}>
<td>{formatDate(obs?.issued || obs?.effectiveDateTime)}</td>
<td>{formatCodeableConcept(obs.code)}</td>
{anyObsInterpretation && (
{availableElements.interpretation && (
<td>
{obs?.interpretation && obs.interpretation.length > 0
? formatCodeableConcept(obs.interpretation[0])
: ""}
</td>
)}
<td>{formatValue(obs)}</td>
{anyReferenceRange && <td>{formatReferenceRange(obs)}</td>}
{availableElements.referenceRange && (
<td>{formatReferenceRange(obs)}</td>
)}
</tr>
))}
</tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,50 @@ $demographicsColumnSpacing: 1fr 3fr;
$immunizationColumnSpacing: 1fr 3fr 1fr 1fr;

.demographicsRow {
grid-template-columns: $demographicsColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}

.conditionRow {
grid-template-columns: $conditionColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}

.observationRow {
grid-template-columns: $observationColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}

.medicationRow {
grid-template-columns: $medicationColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}

.encountersRow {
grid-template-columns: $encountersColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}

.diagnosticsRow {
grid-template-columns: $diagnosticsColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}

.immunizationRow {
grid-template-columns: $immunizationColumnSpacing;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: $columnGap;
word-wrap: break-word;
overflow-wrap: break-word;
}
Loading

0 comments on commit 3377c9e

Please sign in to comment.