From 9167af2e6a1c2c74bcd177b60f815037cdf0c3ef Mon Sep 17 00:00:00 2001 From: James Hughes Date: Tue, 4 Oct 2022 14:54:10 +0100 Subject: [PATCH 01/18] added results_list route --- src/App.tsx | 2 ++ src/components/layout/NavBar.tsx | 6 ++++++ src/pages/ResultsList.tsx | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/pages/ResultsList.tsx diff --git a/src/App.tsx b/src/App.tsx index 4b853f4..95fa72d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import Layout from "./components/layout/Layout"; import { Route, Routes } from "react-router-dom"; import NewReport from "./pages/NewReport"; +import ResultsList from "./pages/ResultsList"; import FhirAuthoriser from "./components/fhir/FhirAuthoriser"; function App() { @@ -9,6 +10,7 @@ function App() { } /> } /> + } /> ); diff --git a/src/components/layout/NavBar.tsx b/src/components/layout/NavBar.tsx index 5965556..903eab1 100644 --- a/src/components/layout/NavBar.tsx +++ b/src/components/layout/NavBar.tsx @@ -16,6 +16,12 @@ const NavBar = () => { New report + +
  • + (navData.isActive ? classes.active : "")}> + Results list + +
  • diff --git a/src/pages/ResultsList.tsx b/src/pages/ResultsList.tsx new file mode 100644 index 0000000..638ef9d --- /dev/null +++ b/src/pages/ResultsList.tsx @@ -0,0 +1,16 @@ +import { FC } from "react"; +import { FhirProvider } from "../components/fhir/FhirContext"; + + + +const ResultsList: FC = () => { + + + return ( + +
    results list
    +
    + ); +}; + +export default ResultsList; From 97e51ccd5c71e7f6f3f711437f4619b5cc0ebb6d Mon Sep 17 00:00:00 2001 From: James Hughes Date: Tue, 4 Oct 2022 16:45:56 +0100 Subject: [PATCH 02/18] added FHIR search query to fetch observations --- src/App.tsx | 4 +-- src/components/results-list/ResultsList.tsx | 30 +++++++++++++++++++++ src/pages/{ResultsList.tsx => Results.tsx} | 10 +++---- 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 src/components/results-list/ResultsList.tsx rename src/pages/{ResultsList.tsx => Results.tsx} (52%) diff --git a/src/App.tsx b/src/App.tsx index 95fa72d..beedffb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import Layout from "./components/layout/Layout"; import { Route, Routes } from "react-router-dom"; import NewReport from "./pages/NewReport"; -import ResultsList from "./pages/ResultsList"; +import Results from "./pages/Results"; import FhirAuthoriser from "./components/fhir/FhirAuthoriser"; function App() { @@ -10,7 +10,7 @@ function App() { } /> } /> - } /> + } /> ); diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx new file mode 100644 index 0000000..b456ed0 --- /dev/null +++ b/src/components/results-list/ResultsList.tsx @@ -0,0 +1,30 @@ +import { FC, useContext, useEffect, useState } from "react"; +import { FhirContext } from "../fhir/FhirContext"; + +const FHIR_URL = process.env.REACT_APP_FHIR_URL; + +const ResultsList: FC = () => { + const ctx = useContext(FhirContext); + const [observationResults, setObservationResults] = useState(null); + + useEffect(() => { + const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject` + + const requestObservations = async () => { + ctx.client + ?.request(observationQueryUrl) + .then((response) => { + console.log(response); + setObservationResults(response); + }) + .catch((error) => console.error(error)); + } + requestObservations(); + }, [ctx]) + + return ( +
    results list
    + ); +}; + +export default ResultsList; diff --git a/src/pages/ResultsList.tsx b/src/pages/Results.tsx similarity index 52% rename from src/pages/ResultsList.tsx rename to src/pages/Results.tsx index 638ef9d..142512c 100644 --- a/src/pages/ResultsList.tsx +++ b/src/pages/Results.tsx @@ -1,16 +1,14 @@ import { FC } from "react"; import { FhirProvider } from "../components/fhir/FhirContext"; +import ResultsList from "../components/results-list/ResultsList"; - -const ResultsList: FC = () => { - - +const Results: FC = () => { return ( -
    results list
    +
    ); }; -export default ResultsList; +export default Results; From a43aa3e98f50b763422a6b8ea55e83c537f9581e Mon Sep 17 00:00:00 2001 From: James Hughes Date: Tue, 11 Oct 2022 10:26:25 +0100 Subject: [PATCH 03/18] initial implementation for displaying results --- src/components/results-list/ResultsList.tsx | 146 +++++++++++++++++--- src/fhir/api.ts | 2 +- src/pages/NewReport.tsx | 4 +- 3 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index b456ed0..a368fd5 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -1,30 +1,138 @@ import { FC, useContext, useEffect, useState } from "react"; import { FhirContext } from "../fhir/FhirContext"; +import LoadingSpinner from "../UI/LoadingSpinner"; +import ModalWrapper from "../UI/ModalWrapper"; +import { ModalState } from "../UI/ModalWrapper"; + const FHIR_URL = process.env.REACT_APP_FHIR_URL; const ResultsList: FC = () => { - const ctx = useContext(FhirContext); - const [observationResults, setObservationResults] = useState(null); - - useEffect(() => { - const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject` - - const requestObservations = async () => { - ctx.client - ?.request(observationQueryUrl) - .then((response) => { - console.log(response); - setObservationResults(response); - }) - .catch((error) => console.error(error)); + const ctx = useContext(FhirContext); + const [modal, setModal] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [parsedObservations, setParsedObservations] = useState(null); + + useEffect(() => { + const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; + + setIsLoading(true); + + const requestObservations = async () => { + ctx.client + ?.request(observationQueryUrl) + .then((response) => { + console.log(response); + parseObservations(response.entry); + }) + .catch((error) => { + setModal({ + message: "Something went wrong fetching observations from the FHIR server.", + isError: true, + }); + console.error(error); + }) + .finally(() => { + setIsLoading(false); + }); + }; + requestObservations(); + }, [ctx]); + + // const obervations = [ + // { fname: "john", lname: "doe", data: { 0: "data1", 1: "data2" } }, + // { fname: "jane", lname: "doe", data: { 0: "data1", 1: "data2" } }, + // ]; + + const parseObservations = (rawObservations: any) => { + console.log(rawObservations); + + let parsedObservations: any = []; + + const patients = rawObservations.filter((element: any) => { + const url = element.fullUrl; + const regex = /Patient/; + const isPatient = url.match(regex); + + if (isPatient) return element; + }); + + const observations = rawObservations.filter((element: any) => { + const url = element.fullUrl; + const regex = /Observation/; + const isObservation = url.match(regex); + + if (isObservation) return element; + }); + + console.log(patients); + console.log(observations); + + // loop through each patient + // for each patient we extract the patient id + // we then loop through each observation + // if the subject of the observation matches the patient id, then we add the observation to the current patient data structure + + patients.forEach((patient: any, index: number) => { + let newPatientObj: any = patient; + let patientObservations: any = []; + + const patientId = patient.resource.id; + + observations.forEach((observation: any, index: number) => { + const subjectIdLong = observation.resource.subject.reference; + const parsedSubjectId = subjectIdLong.split("Patient/")[1]; + + if (patientId === parsedSubjectId) { + patientObservations = [...patientObservations, observation]; + + newPatientObj = { + ...newPatientObj, + ["observations"]: patientObservations, + }; + console.log(newPatientObj); } - requestObservations(); - }, [ctx]) + }); + + parsedObservations = [...parsedObservations, newPatientObj]; + }); + + setParsedObservations(parsedObservations); + }; + + if (!parsedObservations) { + return
    Something went wrong fetching observations from the FHIR server.
    ; + } + + console.log(parsedObservations); + + return ( + <> + setModal(null)} /> + {isLoading && } + + {parsedObservations.map((obervation: any, index: number) => { + return ( +
    +

    Observation {index}

    +
    First name: {obervation.resource.name[0].given[0]}
    +
    Last name: {obervation.resource.name[0].family}
    +
    + cDNA changes: + {obervation.observations.map((innerObservation: any) => { + const res = innerObservation.resource.component.map((component: any) => { + const loincCode = "48004-6"; + if (component.code.coding[0].code === loincCode) component.valueCodeableConcept.coding[0].display; + }); - return ( -
    results list
    - ); + return {res}, ; + })} +
    +
    + ); + })} + + ); }; export default ResultsList; diff --git a/src/fhir/api.ts b/src/fhir/api.ts index ad976e9..cf7e257 100644 --- a/src/fhir/api.ts +++ b/src/fhir/api.ts @@ -95,8 +95,8 @@ export const createBundle = (form: FormValues, reportedGenes: RequiredCoding[]) resourceType: "Bundle", type: "batch", entry: [ - createEntry(patient.resource, patient.identifier), createEntry(org.resource, org.identifier), + createEntry(patient.resource, patient.identifier), createEntry(specimen.resource, specimen.identifier), createEntry(authoriser.resource), createEntry(reporter.resource), diff --git a/src/pages/NewReport.tsx b/src/pages/NewReport.tsx index 280c099..7fea652 100644 --- a/src/pages/NewReport.tsx +++ b/src/pages/NewReport.tsx @@ -1,7 +1,7 @@ import ReportForm from "../components/reports/ReportForm"; import { FC } from "react"; import { FhirProvider } from "../components/fhir/FhirContext"; -import { initialWithNoVariant, noValues } from "../components/reports/FormDefaults"; +import { initialWithNoVariant, noValues, initialValues } from "../components/reports/FormDefaults"; const PREFILLED_REPORT = process.env.REACT_APP_PREFILLED_REPORT; @@ -13,7 +13,7 @@ const NewReport: FC = () => { return ( - + ); }; From 84d4a66c1e9d921f2ee1ba2659abc94f12348aad Mon Sep 17 00:00:00 2001 From: James Hughes Date: Tue, 11 Oct 2022 12:15:03 +0100 Subject: [PATCH 04/18] cleaner extraction of results --- src/components/results-list/ResultsList.tsx | 69 ++++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index a368fd5..0759e04 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -47,8 +47,6 @@ const ResultsList: FC = () => { const parseObservations = (rawObservations: any) => { console.log(rawObservations); - let parsedObservations: any = []; - const patients = rawObservations.filter((element: any) => { const url = element.fullUrl; const regex = /Patient/; @@ -68,36 +66,35 @@ const ResultsList: FC = () => { console.log(patients); console.log(observations); - // loop through each patient - // for each patient we extract the patient id - // we then loop through each observation - // if the subject of the observation matches the patient id, then we add the observation to the current patient data structure + const readableResults = createReadableResults(patients, observations); - patients.forEach((patient: any, index: number) => { - let newPatientObj: any = patient; - let patientObservations: any = []; + setParsedObservations(readableResults); + }; + const createReadableResults = (patients: any, observations: any) => { + let readableResults: any = []; + + patients.forEach((patient: any, index: number) => { const patientId = patient.resource.id; + const firstName = patient.resource.name[0].given[0]; + const lastName = patient.resource.name[0].family; - observations.forEach((observation: any, index: number) => { + const patientObservations = observations.filter((observation: any) => { const subjectIdLong = observation.resource.subject.reference; - const parsedSubjectId = subjectIdLong.split("Patient/")[1]; - - if (patientId === parsedSubjectId) { - patientObservations = [...patientObservations, observation]; - - newPatientObj = { - ...newPatientObj, - ["observations"]: patientObservations, - }; - console.log(newPatientObj); - } + return subjectIdLong.includes(patientId); }); + console.log(patientObservations); + + // filter the observations by loinc code here instead of in the html? - parsedObservations = [...parsedObservations, newPatientObj]; + readableResults = [ + ...readableResults, + { patientId: patientId, firstName: firstName, lastName: lastName, observations: patientObservations }, + ]; }); - setParsedObservations(parsedObservations); + console.log(readableResults); + return readableResults; }; if (!parsedObservations) { @@ -111,21 +108,31 @@ const ResultsList: FC = () => { setModal(null)} /> {isLoading && } - {parsedObservations.map((obervation: any, index: number) => { + {parsedObservations.map((patient: any, index: number) => { return (

    Observation {index}

    -
    First name: {obervation.resource.name[0].given[0]}
    -
    Last name: {obervation.resource.name[0].family}
    +
    First name: {patient.firstName}
    +
    Last name: {patient.lastName}
    +
    - cDNA changes: - {obervation.observations.map((innerObservation: any) => { - const res = innerObservation.resource.component.map((component: any) => { + cDNA changes: {""} + {patient.observations.map((observation: any, index: number) => { + const isLast = patient.observations.length === index + 1; + + const res = observation.resource.component.map((component: any) => { const loincCode = "48004-6"; - if (component.code.coding[0].code === loincCode) component.valueCodeableConcept.coding[0].display; + if (component.code.coding[0].code === loincCode) { + return component.valueCodeableConcept.coding[0].display; + } }); - return {res}, ; + return ( + + {res} + {!isLast && ", "} + + ); })}
    From 95baa012b16d40ddb86a8bb662c10ef6bbc5fa88 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Wed, 12 Oct 2022 12:53:13 +0100 Subject: [PATCH 05/18] some refactoring --- src/components/results-list/ResultsList.tsx | 84 +++++++++------------ 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 0759e04..205320b 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -11,7 +11,7 @@ const ResultsList: FC = () => { const ctx = useContext(FhirContext); const [modal, setModal] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [parsedObservations, setParsedObservations] = useState(null); + const [parsedResults, setParsedResults] = useState(null); useEffect(() => { const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; @@ -22,8 +22,8 @@ const ResultsList: FC = () => { ctx.client ?.request(observationQueryUrl) .then((response) => { - console.log(response); - parseObservations(response.entry); + const parsedResults = parseResults(response.entry); + setParsedResults(parsedResults); }) .catch((error) => { setModal({ @@ -39,78 +39,73 @@ const ResultsList: FC = () => { requestObservations(); }, [ctx]); - // const obervations = [ - // { fname: "john", lname: "doe", data: { 0: "data1", 1: "data2" } }, - // { fname: "jane", lname: "doe", data: { 0: "data1", 1: "data2" } }, - // ]; - - const parseObservations = (rawObservations: any) => { - console.log(rawObservations); - - const patients = rawObservations.filter((element: any) => { - const url = element.fullUrl; - const regex = /Patient/; - const isPatient = url.match(regex); - - if (isPatient) return element; + const parseResults = (entries: any) => { + // extract patients from the data + const patients = entries.filter((entry: any) => { + return entry.fullUrl.includes("Patient"); }); - const observations = rawObservations.filter((element: any) => { - const url = element.fullUrl; - const regex = /Observation/; - const isObservation = url.match(regex); - - if (isObservation) return element; + // extract observations from the data + const observations = entries.filter((entry: any) => { + return entry.fullUrl.includes("Observation"); }); - console.log(patients); - console.log(observations); - const readableResults = createReadableResults(patients, observations); - - setParsedObservations(readableResults); + return readableResults; }; const createReadableResults = (patients: any, observations: any) => { let readableResults: any = []; - patients.forEach((patient: any, index: number) => { + // extract the required data and store in a trimmed down data structure + patients.forEach((patient: any) => { const patientId = patient.resource.id; const firstName = patient.resource.name[0].given[0]; const lastName = patient.resource.name[0].family; + // return observations belonging to a patient based on the patient ID const patientObservations = observations.filter((observation: any) => { const subjectIdLong = observation.resource.subject.reference; return subjectIdLong.includes(patientId); }); - console.log(patientObservations); - // filter the observations by loinc code here instead of in the html? + // extract required data from each observation + let trimmedObservations: any = []; + patientObservations.forEach((observation: any) => { + let trimmedObservation = observation.resource.component.filter((component: any) => { + const loincCode = "48004-6"; + if (component.code.coding[0].code === loincCode) { + return component.valueCodeableConcept.coding[0].display; + } + }); + trimmedObservation = { ...trimmedObservation[0], id: observation.resource.id }; + + trimmedObservations = [...trimmedObservations, trimmedObservation]; + }); readableResults = [ ...readableResults, - { patientId: patientId, firstName: firstName, lastName: lastName, observations: patientObservations }, + { patientId: patientId, firstName: firstName, lastName: lastName, observations: trimmedObservations }, ]; }); - console.log(readableResults); return readableResults; }; - if (!parsedObservations) { - return
    Something went wrong fetching observations from the FHIR server.
    ; + if (!parsedResults) { + return
    Getting observations.
    ; } - console.log(parsedObservations); + console.log(parsedResults); return ( <> setModal(null)} /> - {isLoading && } + {isLoading && } - {parsedObservations.map((patient: any, index: number) => { + {parsedResults.map((patient: any, index: number) => { return ( -
    +

    Observation {index}

    First name: {patient.firstName}
    Last name: {patient.lastName}
    @@ -120,16 +115,9 @@ const ResultsList: FC = () => { {patient.observations.map((observation: any, index: number) => { const isLast = patient.observations.length === index + 1; - const res = observation.resource.component.map((component: any) => { - const loincCode = "48004-6"; - if (component.code.coding[0].code === loincCode) { - return component.valueCodeableConcept.coding[0].display; - } - }); - return ( - - {res} + + {observation.valueCodeableConcept.coding[0].display} {!isLast && ", "} ); From 0dff6948fef949af3d08537cabe98175d31ac0e1 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Wed, 12 Oct 2022 14:30:36 +0100 Subject: [PATCH 06/18] adding types --- src/components/results-list/ResultsList.tsx | 39 ++++++++++++--------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 205320b..cbded0b 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -6,12 +6,19 @@ import ModalWrapper from "../UI/ModalWrapper"; import { ModalState } from "../UI/ModalWrapper"; const FHIR_URL = process.env.REACT_APP_FHIR_URL; +type patientResult = { + firstName: string; + lastName: string; + patientId: string; + observations: { [key: string]: any }[]; // using any since it is a deeply nested object from an external api} +}; +type parsedResultsModel = patientResult[]; const ResultsList: FC = () => { const ctx = useContext(FhirContext); const [modal, setModal] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [parsedResults, setParsedResults] = useState(null); + const [parsedResults, setParsedResults] = useState(null); useEffect(() => { const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; @@ -39,14 +46,14 @@ const ResultsList: FC = () => { requestObservations(); }, [ctx]); - const parseResults = (entries: any) => { + const parseResults = (entries: { [key: string]: any }[]) => { // extract patients from the data - const patients = entries.filter((entry: any) => { + const patients = entries.filter((entry: { [key: string]: any }) => { return entry.fullUrl.includes("Patient"); }); // extract observations from the data - const observations = entries.filter((entry: any) => { + const observations = entries.filter((entry: { [key: string]: any }) => { return entry.fullUrl.includes("Observation"); }); @@ -54,31 +61,31 @@ const ResultsList: FC = () => { return readableResults; }; - const createReadableResults = (patients: any, observations: any) => { - let readableResults: any = []; + const createReadableResults = (patients: { [key: string]: any }[], observations: { [key: string]: any }[]) => { + let readableResults: parsedResultsModel = []; // extract the required data and store in a trimmed down data structure - patients.forEach((patient: any) => { + patients.forEach((patient: { [key: string]: any }) => { const patientId = patient.resource.id; const firstName = patient.resource.name[0].given[0]; const lastName = patient.resource.name[0].family; // return observations belonging to a patient based on the patient ID - const patientObservations = observations.filter((observation: any) => { + const patientObservations = observations.filter((observation: { [key: string]: any }) => { const subjectIdLong = observation.resource.subject.reference; return subjectIdLong.includes(patientId); }); // extract required data from each observation - let trimmedObservations: any = []; - patientObservations.forEach((observation: any) => { - let trimmedObservation = observation.resource.component.filter((component: any) => { + let trimmedObservations: { [key: string]: any }[] = []; + patientObservations.forEach((observation: { [key: string]: any }) => { + let trimmedObservation = observation.resource.component.filter((component: { [key: string]: any }) => { const loincCode = "48004-6"; if (component.code.coding[0].code === loincCode) { return component.valueCodeableConcept.coding[0].display; } }); - trimmedObservation = { ...trimmedObservation[0], id: observation.resource.id }; + trimmedObservation = { ...trimmedObservation[0], observationId: observation.resource.id }; trimmedObservations = [...trimmedObservations, trimmedObservation]; }); @@ -103,20 +110,20 @@ const ResultsList: FC = () => { setModal(null)} /> {isLoading && } - {parsedResults.map((patient: any, index: number) => { + {parsedResults.map((patient: patientResult, index: number) => { return ( -
    +

    Observation {index}

    First name: {patient.firstName}
    Last name: {patient.lastName}
    cDNA changes: {""} - {patient.observations.map((observation: any, index: number) => { + {patient.observations.map((observation: { [key: string]: any }, index: number) => { const isLast = patient.observations.length === index + 1; return ( - + {observation.valueCodeableConcept.coding[0].display} {!isLast && ", "} From 717b40af59860b8524fdfd59ff2a6422f881bbd9 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Wed, 12 Oct 2022 15:10:31 +0100 Subject: [PATCH 07/18] results now displayed in an html table --- .../results-list/ResultsList.module.css | 24 ++++++++ src/components/results-list/ResultsList.tsx | 56 ++++++++++--------- 2 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 src/components/results-list/ResultsList.module.css diff --git a/src/components/results-list/ResultsList.module.css b/src/components/results-list/ResultsList.module.css new file mode 100644 index 0000000..13a72d2 --- /dev/null +++ b/src/components/results-list/ResultsList.module.css @@ -0,0 +1,24 @@ +table.results-table { + background-color: #8c88d9; + width: 100%; + text-align: center; + border-collapse: collapse; +} + +table.results-table td, +table.results-table th { + border: 1px solid #646464; + padding: 3px 2px; +} + +table.results-table tbody td { + font-size: 13px; +} + +table.results-table th { + border-bottom: 2px solid #646464; +} + +table.results-table tr:nth-child(odd) { + background: #d0e4f5; +} diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index cbded0b..77fde86 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -5,6 +5,8 @@ import LoadingSpinner from "../UI/LoadingSpinner"; import ModalWrapper from "../UI/ModalWrapper"; import { ModalState } from "../UI/ModalWrapper"; +import classes from "./ResultsList.module.css"; + const FHIR_URL = process.env.REACT_APP_FHIR_URL; type patientResult = { firstName: string; @@ -103,36 +105,40 @@ const ResultsList: FC = () => { return
    Getting observations.
    ; } - console.log(parsedResults); - return ( <> setModal(null)} /> {isLoading && } - {parsedResults.map((patient: patientResult, index: number) => { - return ( -
    -

    Observation {index}

    -
    First name: {patient.firstName}
    -
    Last name: {patient.lastName}
    - -
    - cDNA changes: {""} - {patient.observations.map((observation: { [key: string]: any }, index: number) => { - const isLast = patient.observations.length === index + 1; - - return ( - - {observation.valueCodeableConcept.coding[0].display} - {!isLast && ", "} - - ); - })} -
    -
    - ); - })} +

    Patient results table

    + + + + + + + + {parsedResults.map((patient, index) => { + return ( + + + + + + ); + })} +
    First nameLast namecDNA changes
    {patient.firstName}{patient.lastName} + {patient.observations.map((observation: { [key: string]: any }, index) => { + const isLast = patient.observations.length === index + 1; + + return ( + + {observation.valueCodeableConcept.coding[0].display} + {!isLast && ", "} + + ); + })} +
    ); }; From 7ee9ad239e4fa054c8b19f0299791e31ad49d115 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Wed, 12 Oct 2022 15:19:56 +0100 Subject: [PATCH 08/18] added thead and tbody to table --- .../results-list/ResultsList.module.css | 4 -- src/components/results-list/ResultsList.tsx | 55 ++++++++++--------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/components/results-list/ResultsList.module.css b/src/components/results-list/ResultsList.module.css index 13a72d2..37d5d9e 100644 --- a/src/components/results-list/ResultsList.module.css +++ b/src/components/results-list/ResultsList.module.css @@ -11,10 +11,6 @@ table.results-table th { padding: 3px 2px; } -table.results-table tbody td { - font-size: 13px; -} - table.results-table th { border-bottom: 2px solid #646464; } diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 77fde86..5b7bcf2 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -49,6 +49,7 @@ const ResultsList: FC = () => { }, [ctx]); const parseResults = (entries: { [key: string]: any }[]) => { + console.log(entries); // extract patients from the data const patients = entries.filter((entry: { [key: string]: any }) => { return entry.fullUrl.includes("Patient"); @@ -113,31 +114,35 @@ const ResultsList: FC = () => {

    Patient results table

    - - - - - - {parsedResults.map((patient, index) => { - return ( - - - - - - ); - })} + + + + + + + + + {parsedResults.map((patient, index) => { + return ( + + + + + + ); + })} +
    First nameLast namecDNA changes
    {patient.firstName}{patient.lastName} - {patient.observations.map((observation: { [key: string]: any }, index) => { - const isLast = patient.observations.length === index + 1; - - return ( - - {observation.valueCodeableConcept.coding[0].display} - {!isLast && ", "} - - ); - })} -
    First nameLast namecDNA changes
    {patient.firstName}{patient.lastName} + {patient.observations.map((observation: { [key: string]: any }, index) => { + const isLast = patient.observations.length === index + 1; + + return ( + + {observation.valueCodeableConcept.coding[0].display} + {!isLast && ", "} + + ); + })} +
    ); From 82d2f6bb2b607b8c39b6565598360c7c6e057340 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Wed, 12 Oct 2022 15:29:19 +0100 Subject: [PATCH 09/18] reverted back to initial form values being populated automatically --- src/pages/NewReport.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/NewReport.tsx b/src/pages/NewReport.tsx index 7fea652..280c099 100644 --- a/src/pages/NewReport.tsx +++ b/src/pages/NewReport.tsx @@ -1,7 +1,7 @@ import ReportForm from "../components/reports/ReportForm"; import { FC } from "react"; import { FhirProvider } from "../components/fhir/FhirContext"; -import { initialWithNoVariant, noValues, initialValues } from "../components/reports/FormDefaults"; +import { initialWithNoVariant, noValues } from "../components/reports/FormDefaults"; const PREFILLED_REPORT = process.env.REACT_APP_PREFILLED_REPORT; @@ -13,7 +13,7 @@ const NewReport: FC = () => { return ( - + ); }; From c832d19e4fe8c1c83c5d0998a3486f4eed006548 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Wed, 12 Oct 2022 15:44:15 +0100 Subject: [PATCH 10/18] moved results business logic to parent Results component --- src/App.tsx | 17 +-- src/components/results-list/ResultsList.tsx | 114 ++----------------- src/pages/NewReport.tsx | 7 +- src/pages/Results.tsx | 115 +++++++++++++++++++- 4 files changed, 129 insertions(+), 124 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index beedffb..46eb23b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,16 +3,19 @@ import { Route, Routes } from "react-router-dom"; import NewReport from "./pages/NewReport"; import Results from "./pages/Results"; import FhirAuthoriser from "./components/fhir/FhirAuthoriser"; +import { FhirProvider } from "./components/fhir/FhirContext"; function App() { return ( - - - } /> - } /> - } /> - - + + + + } /> + } /> + } /> + + + ); } diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 5b7bcf2..0477f7b 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -1,116 +1,18 @@ -import { FC, useContext, useEffect, useState } from "react"; -import { FhirContext } from "../fhir/FhirContext"; +import { FC } from "react"; -import LoadingSpinner from "../UI/LoadingSpinner"; -import ModalWrapper from "../UI/ModalWrapper"; -import { ModalState } from "../UI/ModalWrapper"; +import { parsedResultsModel } from "../../pages/Results"; import classes from "./ResultsList.module.css"; -const FHIR_URL = process.env.REACT_APP_FHIR_URL; -type patientResult = { - firstName: string; - lastName: string; - patientId: string; - observations: { [key: string]: any }[]; // using any since it is a deeply nested object from an external api} -}; -type parsedResultsModel = patientResult[]; - -const ResultsList: FC = () => { - const ctx = useContext(FhirContext); - const [modal, setModal] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [parsedResults, setParsedResults] = useState(null); - - useEffect(() => { - const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; - - setIsLoading(true); - - const requestObservations = async () => { - ctx.client - ?.request(observationQueryUrl) - .then((response) => { - const parsedResults = parseResults(response.entry); - setParsedResults(parsedResults); - }) - .catch((error) => { - setModal({ - message: "Something went wrong fetching observations from the FHIR server.", - isError: true, - }); - console.error(error); - }) - .finally(() => { - setIsLoading(false); - }); - }; - requestObservations(); - }, [ctx]); - - const parseResults = (entries: { [key: string]: any }[]) => { - console.log(entries); - // extract patients from the data - const patients = entries.filter((entry: { [key: string]: any }) => { - return entry.fullUrl.includes("Patient"); - }); - - // extract observations from the data - const observations = entries.filter((entry: { [key: string]: any }) => { - return entry.fullUrl.includes("Observation"); - }); - - const readableResults = createReadableResults(patients, observations); - return readableResults; - }; +interface Props { + results: parsedResultsModel; +} - const createReadableResults = (patients: { [key: string]: any }[], observations: { [key: string]: any }[]) => { - let readableResults: parsedResultsModel = []; - - // extract the required data and store in a trimmed down data structure - patients.forEach((patient: { [key: string]: any }) => { - const patientId = patient.resource.id; - const firstName = patient.resource.name[0].given[0]; - const lastName = patient.resource.name[0].family; - - // return observations belonging to a patient based on the patient ID - const patientObservations = observations.filter((observation: { [key: string]: any }) => { - const subjectIdLong = observation.resource.subject.reference; - return subjectIdLong.includes(patientId); - }); - - // extract required data from each observation - let trimmedObservations: { [key: string]: any }[] = []; - patientObservations.forEach((observation: { [key: string]: any }) => { - let trimmedObservation = observation.resource.component.filter((component: { [key: string]: any }) => { - const loincCode = "48004-6"; - if (component.code.coding[0].code === loincCode) { - return component.valueCodeableConcept.coding[0].display; - } - }); - trimmedObservation = { ...trimmedObservation[0], observationId: observation.resource.id }; - - trimmedObservations = [...trimmedObservations, trimmedObservation]; - }); - - readableResults = [ - ...readableResults, - { patientId: patientId, firstName: firstName, lastName: lastName, observations: trimmedObservations }, - ]; - }); - - return readableResults; - }; - - if (!parsedResults) { - return
    Getting observations.
    ; - } +const ResultsList: FC = (props) => { + const { results } = props; return ( <> - setModal(null)} /> - {isLoading && } -

    Patient results table

    @@ -122,7 +24,7 @@ const ResultsList: FC = () => { - {parsedResults.map((patient, index) => { + {results.map((patient, index) => { return ( diff --git a/src/pages/NewReport.tsx b/src/pages/NewReport.tsx index 280c099..6c2057d 100644 --- a/src/pages/NewReport.tsx +++ b/src/pages/NewReport.tsx @@ -1,6 +1,5 @@ import ReportForm from "../components/reports/ReportForm"; import { FC } from "react"; -import { FhirProvider } from "../components/fhir/FhirContext"; import { initialWithNoVariant, noValues } from "../components/reports/FormDefaults"; const PREFILLED_REPORT = process.env.REACT_APP_PREFILLED_REPORT; @@ -11,11 +10,7 @@ const NewReport: FC = () => { initialFormValues = initialWithNoVariant; } - return ( - - - - ); + return ; }; export default NewReport; diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index 142512c..c2f1fff 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -1,13 +1,118 @@ -import { FC } from "react"; -import { FhirProvider } from "../components/fhir/FhirContext"; +import { FC, useEffect, useState, useContext } from "react"; +import { FhirContext } from "../components/fhir/FhirContext"; +import LoadingSpinner from "../components/UI/LoadingSpinner"; +import ModalWrapper from "../components/UI/ModalWrapper"; +import { ModalState } from "../components/UI/ModalWrapper"; import ResultsList from "../components/results-list/ResultsList"; +const FHIR_URL = process.env.REACT_APP_FHIR_URL; + +type patientResult = { + firstName: string; + lastName: string; + patientId: string; + observations: { [key: string]: any }[]; // using any since it is a deeply nested object from an external api} +}; +export type parsedResultsModel = patientResult[]; + const Results: FC = () => { + const ctx = useContext(FhirContext); + const [modal, setModal] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [parsedResults, setParsedResults] = useState(null); + + useEffect(() => { + const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; + + setIsLoading(true); + + const requestObservations = async () => { + ctx.client + ?.request(observationQueryUrl) + .then((response) => { + const parsedResults = parseResults(response.entry); + setParsedResults(parsedResults); + }) + .catch((error) => { + setModal({ + message: "Something went wrong fetching observations from the FHIR server.", + isError: true, + }); + console.error(error); + }) + .finally(() => { + setIsLoading(false); + }); + }; + requestObservations(); + }, [ctx]); + + const parseResults = (entries: { [key: string]: any }[]) => { + console.log(entries); + // extract patients from the data + const patients = entries.filter((entry: { [key: string]: any }) => { + return entry.fullUrl.includes("Patient"); + }); + + // extract observations from the data + const observations = entries.filter((entry: { [key: string]: any }) => { + return entry.fullUrl.includes("Observation"); + }); + + const readableResults = createReadableResults(patients, observations); + return readableResults; + }; + + const createReadableResults = (patients: { [key: string]: any }[], observations: { [key: string]: any }[]) => { + let readableResults: parsedResultsModel = []; + + // extract the required data and store in a trimmed down data structure + patients.forEach((patient: { [key: string]: any }) => { + const patientId = patient.resource.id; + const firstName = patient.resource.name[0].given[0]; + const lastName = patient.resource.name[0].family; + + // return observations belonging to a patient based on the patient ID + const patientObservations = observations.filter((observation: { [key: string]: any }) => { + const subjectIdLong = observation.resource.subject.reference; + return subjectIdLong.includes(patientId); + }); + + // extract required data from each observation + let trimmedObservations: { [key: string]: any }[] = []; + patientObservations.forEach((observation: { [key: string]: any }) => { + let trimmedObservation = observation.resource.component.filter((component: { [key: string]: any }) => { + const loincCode = "48004-6"; + if (component.code.coding[0].code === loincCode) { + return component.valueCodeableConcept.coding[0].display; + } + }); + trimmedObservation = { ...trimmedObservation[0], observationId: observation.resource.id }; + + trimmedObservations = [...trimmedObservations, trimmedObservation]; + }); + + readableResults = [ + ...readableResults, + { patientId: patientId, firstName: firstName, lastName: lastName, observations: trimmedObservations }, + ]; + }); + + return readableResults; + }; + + if (!parsedResults) { + return
    Getting observations.
    ; + } + return ( - - - + <> + setModal(null)} /> + {isLoading && } + + + ); }; From da6cf08bf0b717b066942489f20b153295995c49 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 14:30:34 +0100 Subject: [PATCH 11/18] added more robust typings --- src/App.tsx | 30 ++++++--- src/components/results-list/ResultsList.tsx | 2 +- src/pages/Results.tsx | 69 +++++++++++++-------- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 46eb23b..f7a1520 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,15 +7,27 @@ import { FhirProvider } from "./components/fhir/FhirContext"; function App() { return ( - - - - } /> - } /> - } /> - - - + + + } /> + + + + } + /> + + + + } + /> + + ); } diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 0477f7b..be8b3eb 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -35,7 +35,7 @@ const ResultsList: FC = (props) => { return ( - {observation.valueCodeableConcept.coding[0].display} + {observation.cDnaChange} {!isLast && ", "} ); diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index c2f1fff..8370dda 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -6,13 +6,16 @@ import ModalWrapper from "../components/UI/ModalWrapper"; import { ModalState } from "../components/UI/ModalWrapper"; import ResultsList from "../components/results-list/ResultsList"; +import { Patient, Observation } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; + const FHIR_URL = process.env.REACT_APP_FHIR_URL; +type trimmedObservation = { cDnaChange: string | undefined; observationId: string | undefined }; type patientResult = { - firstName: string; - lastName: string; - patientId: string; - observations: { [key: string]: any }[]; // using any since it is a deeply nested object from an external api} + firstName: string | undefined; + lastName: string | undefined; + patientId: string | undefined; + observations: trimmedObservation[]; }; export type parsedResultsModel = patientResult[]; @@ -49,48 +52,62 @@ const Results: FC = () => { }, [ctx]); const parseResults = (entries: { [key: string]: any }[]) => { - console.log(entries); // extract patients from the data - const patients = entries.filter((entry: { [key: string]: any }) => { - return entry.fullUrl.includes("Patient"); - }); + const patients = entries + .filter((entry) => { + return entry.fullUrl.includes("Patient"); + }) + .map((entry) => entry.resource as Patient); + + console.log(patients); // extract observations from the data - const observations = entries.filter((entry: { [key: string]: any }) => { - return entry.fullUrl.includes("Observation"); - }); + const observations = entries + .filter((entry) => { + return entry.fullUrl.includes("Observation"); + }) + .map((entry) => entry.resource as Observation); const readableResults = createReadableResults(patients, observations); + return readableResults; }; - const createReadableResults = (patients: { [key: string]: any }[], observations: { [key: string]: any }[]) => { + const createReadableResults = (patients: Patient[], observations: Observation[]) => { let readableResults: parsedResultsModel = []; // extract the required data and store in a trimmed down data structure - patients.forEach((patient: { [key: string]: any }) => { - const patientId = patient.resource.id; - const firstName = patient.resource.name[0].given[0]; - const lastName = patient.resource.name[0].family; + patients.forEach((patient) => { + if (!patient.id || !patient.name || !patient.name[0].given) return; + + const patientId = patient.id; + const firstName = patient.name[0].given[0]; + const lastName = patient.name[0].family; // return observations belonging to a patient based on the patient ID - const patientObservations = observations.filter((observation: { [key: string]: any }) => { - const subjectIdLong = observation.resource.subject.reference; + const patientObservations = observations.filter((observation) => { + if (!observation.subject || !observation.subject.reference) return; + + const subjectIdLong = observation.subject.reference; + return subjectIdLong.includes(patientId); }); // extract required data from each observation - let trimmedObservations: { [key: string]: any }[] = []; - patientObservations.forEach((observation: { [key: string]: any }) => { - let trimmedObservation = observation.resource.component.filter((component: { [key: string]: any }) => { + let trimmedObservations: trimmedObservation[] = []; + patientObservations.forEach((observation) => { + if (!observation || !observation.component) return; + + const trimmedObservation = observation.component.filter((component) => { const loincCode = "48004-6"; - if (component.code.coding[0].code === loincCode) { - return component.valueCodeableConcept.coding[0].display; + if (component?.code?.coding?.[0].code === loincCode) { + return component?.valueCodeableConcept?.coding?.[0].display; } }); - trimmedObservation = { ...trimmedObservation[0], observationId: observation.resource.id }; - trimmedObservations = [...trimmedObservations, trimmedObservation]; + const cDnaChange = trimmedObservation[0].valueCodeableConcept?.coding?.[0].display; + + trimmedObservations = [...trimmedObservations, { cDnaChange: cDnaChange, observationId: observation.id }]; }); readableResults = [ @@ -106,6 +123,8 @@ const Results: FC = () => { return
    Getting observations.
    ; } + console.log(parsedResults); + return ( <> setModal(null)} /> From f9af419b377f7b8c134032a6d7ff98aa395fc544 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 14:41:00 +0100 Subject: [PATCH 12/18] refactored fhir authoriser connection --- src/App.tsx | 26 +++++++------------------- src/components/fhir/FhirContext.tsx | 14 +++++++++++--- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f7a1520..f4b24d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,25 +8,13 @@ import { FhirProvider } from "./components/fhir/FhirContext"; function App() { return ( - - } /> - - - - } - /> - - - - } - /> - + + + } /> + } /> + } /> + + ); } diff --git a/src/components/fhir/FhirContext.tsx b/src/components/fhir/FhirContext.tsx index 37a800c..ac1bc09 100644 --- a/src/components/fhir/FhirContext.tsx +++ b/src/components/fhir/FhirContext.tsx @@ -26,9 +26,17 @@ export const FhirProvider: FC = (props) => { useEffect(() => { if (!client) { SMART.ready() - .then((client) => setClient(client)) - .catch((error) => setError(error)) - .finally(() => console.debug("FHIR client ready")); + .then((client) => { + setClient(client); + }) + .catch((error) => { + setError(error); + console.error(error); + }) + .finally(() => { + console.debug("FHIR client ready"); + setError(null); + }); } }, []); From 8655e8dd6e6303dc1d002d1cf343fe8b98d1cc35 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 15:41:49 +0100 Subject: [PATCH 13/18] added a wrapper component around resultsList so that context can be passed successfully --- src/App.tsx | 13 +- src/components/fhir/FhirContext.tsx | 1 - .../results-list/ResultsDataFetcher.tsx | 138 ++++++++++++++++++ src/components/results-list/ResultsList.tsx | 2 +- src/pages/NewReport.tsx | 7 +- src/pages/Results.tsx | 136 +---------------- 6 files changed, 156 insertions(+), 141 deletions(-) create mode 100644 src/components/results-list/ResultsDataFetcher.tsx diff --git a/src/App.tsx b/src/App.tsx index f4b24d3..beedffb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,18 +3,15 @@ import { Route, Routes } from "react-router-dom"; import NewReport from "./pages/NewReport"; import Results from "./pages/Results"; import FhirAuthoriser from "./components/fhir/FhirAuthoriser"; -import { FhirProvider } from "./components/fhir/FhirContext"; function App() { return ( - - - } /> - } /> - } /> - - + + } /> + } /> + } /> + ); } diff --git a/src/components/fhir/FhirContext.tsx b/src/components/fhir/FhirContext.tsx index ac1bc09..1572907 100644 --- a/src/components/fhir/FhirContext.tsx +++ b/src/components/fhir/FhirContext.tsx @@ -35,7 +35,6 @@ export const FhirProvider: FC = (props) => { }) .finally(() => { console.debug("FHIR client ready"); - setError(null); }); } }, []); diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx new file mode 100644 index 0000000..15c680b --- /dev/null +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -0,0 +1,138 @@ +import { FC, useEffect, useState, useContext } from "react"; +import { FhirContext } from "../fhir/FhirContext"; + +import LoadingSpinner from "../UI/LoadingSpinner"; +import ModalWrapper from "../UI/ModalWrapper"; +import { ModalState } from "../UI/ModalWrapper"; +import ResultsList from "../results-list/ResultsList"; + +import { Patient, Observation } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; + +const FHIR_URL = process.env.REACT_APP_FHIR_URL; + +type trimmedObservation = { cDnaChange: string | undefined; observationId: string | undefined }; +type patientResult = { + firstName: string | undefined; + lastName: string | undefined; + patientId: string | undefined; + observations: trimmedObservation[]; +}; +export type parsedResultsModel = patientResult[]; + +const ResultsDataFetcher: FC = () => { + const ctx = useContext(FhirContext); + const [modal, setModal] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [parsedResults, setParsedResults] = useState(null); + + useEffect(() => { + const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; + + setIsLoading(true); + + const requestObservations = async () => { + ctx.client + ?.request(observationQueryUrl) + .then((response) => { + const parsedResults = parseResults(response.entry); + setParsedResults(parsedResults); + }) + .catch((error) => { + setModal({ + message: "Something went wrong fetching observations from the FHIR server.", + isError: true, + }); + console.error(error); + }) + .finally(() => { + setIsLoading(false); + }); + }; + requestObservations(); + }, [ctx]); + + const parseResults = (entries: { [key: string]: any }[]) => { + // extract patients from the data + const patients = entries + .filter((entry) => { + return entry.fullUrl.includes("Patient"); + }) + .map((entry) => entry.resource as Patient); + + console.log(patients); + + // extract observations from the data + const observations = entries + .filter((entry) => { + return entry.fullUrl.includes("Observation"); + }) + .map((entry) => entry.resource as Observation); + + const readableResults = createReadableResults(patients, observations); + + return readableResults; + }; + + const createReadableResults = (patients: Patient[], observations: Observation[]) => { + let readableResults: parsedResultsModel = []; + + // extract the required data and store in a trimmed down data structure + patients.forEach((patient) => { + if (!patient.id || !patient.name || !patient.name[0].given) return; + + const patientId = patient.id; + const firstName = patient.name[0].given[0]; + const lastName = patient.name[0].family; + + // return observations belonging to a patient based on the patient ID + const patientObservations = observations.filter((observation) => { + if (!observation.subject || !observation.subject.reference) return; + + const subjectIdLong = observation.subject.reference; + + return subjectIdLong.includes(patientId); + }); + + // extract required data from each observation + let trimmedObservations: trimmedObservation[] = []; + patientObservations.forEach((observation) => { + if (!observation || !observation.component) return; + + const trimmedObservation = observation.component.filter((component) => { + const loincCode = "48004-6"; + if (component?.code?.coding?.[0].code === loincCode) { + return component?.valueCodeableConcept?.coding?.[0].display; + } + }); + + const cDnaChange = trimmedObservation[0].valueCodeableConcept?.coding?.[0].display; + + trimmedObservations = [...trimmedObservations, { cDnaChange: cDnaChange, observationId: observation.id }]; + }); + + readableResults = [ + ...readableResults, + { patientId: patientId, firstName: firstName, lastName: lastName, observations: trimmedObservations }, + ]; + }); + + return readableResults; + }; + + if (!parsedResults) { + return
    Getting observations.
    ; + } + + console.log(parsedResults); + + return ( + <> + setModal(null)} /> + {isLoading && } + + + + ); +}; + +export default ResultsDataFetcher; diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index be8b3eb..415d712 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -1,6 +1,6 @@ import { FC } from "react"; -import { parsedResultsModel } from "../../pages/Results"; +import { parsedResultsModel } from "./ResultsDataFetcher"; import classes from "./ResultsList.module.css"; diff --git a/src/pages/NewReport.tsx b/src/pages/NewReport.tsx index 6c2057d..658ef3d 100644 --- a/src/pages/NewReport.tsx +++ b/src/pages/NewReport.tsx @@ -1,6 +1,7 @@ import ReportForm from "../components/reports/ReportForm"; import { FC } from "react"; import { initialWithNoVariant, noValues } from "../components/reports/FormDefaults"; +import { FhirProvider } from "../components/fhir/FhirContext"; const PREFILLED_REPORT = process.env.REACT_APP_PREFILLED_REPORT; @@ -10,7 +11,11 @@ const NewReport: FC = () => { initialFormValues = initialWithNoVariant; } - return ; + return ( + + + + ); }; export default NewReport; diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index 8370dda..cb61712 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -1,137 +1,13 @@ -import { FC, useEffect, useState, useContext } from "react"; -import { FhirContext } from "../components/fhir/FhirContext"; +import { FC } from "react"; +import { FhirProvider } from "../components/fhir/FhirContext"; -import LoadingSpinner from "../components/UI/LoadingSpinner"; -import ModalWrapper from "../components/UI/ModalWrapper"; -import { ModalState } from "../components/UI/ModalWrapper"; -import ResultsList from "../components/results-list/ResultsList"; - -import { Patient, Observation } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; - -const FHIR_URL = process.env.REACT_APP_FHIR_URL; - -type trimmedObservation = { cDnaChange: string | undefined; observationId: string | undefined }; -type patientResult = { - firstName: string | undefined; - lastName: string | undefined; - patientId: string | undefined; - observations: trimmedObservation[]; -}; -export type parsedResultsModel = patientResult[]; +import ResultsDataFetcher from "../components/results-list/ResultsDataFetcher"; const Results: FC = () => { - const ctx = useContext(FhirContext); - const [modal, setModal] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [parsedResults, setParsedResults] = useState(null); - - useEffect(() => { - const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; - - setIsLoading(true); - - const requestObservations = async () => { - ctx.client - ?.request(observationQueryUrl) - .then((response) => { - const parsedResults = parseResults(response.entry); - setParsedResults(parsedResults); - }) - .catch((error) => { - setModal({ - message: "Something went wrong fetching observations from the FHIR server.", - isError: true, - }); - console.error(error); - }) - .finally(() => { - setIsLoading(false); - }); - }; - requestObservations(); - }, [ctx]); - - const parseResults = (entries: { [key: string]: any }[]) => { - // extract patients from the data - const patients = entries - .filter((entry) => { - return entry.fullUrl.includes("Patient"); - }) - .map((entry) => entry.resource as Patient); - - console.log(patients); - - // extract observations from the data - const observations = entries - .filter((entry) => { - return entry.fullUrl.includes("Observation"); - }) - .map((entry) => entry.resource as Observation); - - const readableResults = createReadableResults(patients, observations); - - return readableResults; - }; - - const createReadableResults = (patients: Patient[], observations: Observation[]) => { - let readableResults: parsedResultsModel = []; - - // extract the required data and store in a trimmed down data structure - patients.forEach((patient) => { - if (!patient.id || !patient.name || !patient.name[0].given) return; - - const patientId = patient.id; - const firstName = patient.name[0].given[0]; - const lastName = patient.name[0].family; - - // return observations belonging to a patient based on the patient ID - const patientObservations = observations.filter((observation) => { - if (!observation.subject || !observation.subject.reference) return; - - const subjectIdLong = observation.subject.reference; - - return subjectIdLong.includes(patientId); - }); - - // extract required data from each observation - let trimmedObservations: trimmedObservation[] = []; - patientObservations.forEach((observation) => { - if (!observation || !observation.component) return; - - const trimmedObservation = observation.component.filter((component) => { - const loincCode = "48004-6"; - if (component?.code?.coding?.[0].code === loincCode) { - return component?.valueCodeableConcept?.coding?.[0].display; - } - }); - - const cDnaChange = trimmedObservation[0].valueCodeableConcept?.coding?.[0].display; - - trimmedObservations = [...trimmedObservations, { cDnaChange: cDnaChange, observationId: observation.id }]; - }); - - readableResults = [ - ...readableResults, - { patientId: patientId, firstName: firstName, lastName: lastName, observations: trimmedObservations }, - ]; - }); - - return readableResults; - }; - - if (!parsedResults) { - return
    Getting observations.
    ; - } - - console.log(parsedResults); - return ( - <> - setModal(null)} /> - {isLoading && } - - - + + + ); }; From b5973d2304672fb58038fd4a2b836e9b501a7032 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 15:53:27 +0100 Subject: [PATCH 14/18] removed unnecessary lines --- src/components/fhir/FhirContext.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/fhir/FhirContext.tsx b/src/components/fhir/FhirContext.tsx index 1572907..37a800c 100644 --- a/src/components/fhir/FhirContext.tsx +++ b/src/components/fhir/FhirContext.tsx @@ -26,16 +26,9 @@ export const FhirProvider: FC = (props) => { useEffect(() => { if (!client) { SMART.ready() - .then((client) => { - setClient(client); - }) - .catch((error) => { - setError(error); - console.error(error); - }) - .finally(() => { - console.debug("FHIR client ready"); - }); + .then((client) => setClient(client)) + .catch((error) => setError(error)) + .finally(() => console.debug("FHIR client ready")); } }, []); From a24bc9e8032afc90f904e9f38ead014db8f5fe63 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 15:54:32 +0100 Subject: [PATCH 15/18] changed order of import --- src/pages/NewReport.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewReport.tsx b/src/pages/NewReport.tsx index 658ef3d..280c099 100644 --- a/src/pages/NewReport.tsx +++ b/src/pages/NewReport.tsx @@ -1,7 +1,7 @@ import ReportForm from "../components/reports/ReportForm"; import { FC } from "react"; -import { initialWithNoVariant, noValues } from "../components/reports/FormDefaults"; import { FhirProvider } from "../components/fhir/FhirContext"; +import { initialWithNoVariant, noValues } from "../components/reports/FormDefaults"; const PREFILLED_REPORT = process.env.REACT_APP_PREFILLED_REPORT; From c8362acceb9a95b9e1cf858217056bc3ab3c3749 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 16:04:32 +0100 Subject: [PATCH 16/18] removed console.logs --- src/components/results-list/ResultsDataFetcher.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index 15c680b..6d35a57 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -59,8 +59,6 @@ const ResultsDataFetcher: FC = () => { }) .map((entry) => entry.resource as Patient); - console.log(patients); - // extract observations from the data const observations = entries .filter((entry) => { @@ -123,8 +121,6 @@ const ResultsDataFetcher: FC = () => { return
    Getting observations.
    ; } - console.log(parsedResults); - return ( <> setModal(null)} /> From 88d671368e23f648e84239e336fd9f3744547af1 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Thu, 13 Oct 2022 16:24:11 +0100 Subject: [PATCH 17/18] Added html if no results were found. Improved guard clauses during data fetching --- src/components/results-list/ResultsDataFetcher.tsx | 4 +++- src/components/results-list/ResultsList.tsx | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index 6d35a57..064f4eb 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -84,13 +84,15 @@ const ResultsDataFetcher: FC = () => { // return observations belonging to a patient based on the patient ID const patientObservations = observations.filter((observation) => { - if (!observation.subject || !observation.subject.reference) return; + if (!observation.subject?.reference) return; const subjectIdLong = observation.subject.reference; return subjectIdLong.includes(patientId); }); + if (!patientObservations || patientObservations.length === 0) return; + // extract required data from each observation let trimmedObservations: trimmedObservation[] = []; patientObservations.forEach((observation) => { diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 415d712..e19e59e 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -11,6 +11,10 @@ interface Props { const ResultsList: FC = (props) => { const { results } = props; + if (!results || results.length === 0) { + return
    No results were returned from the fhir query.
    ; + } + return ( <>

    Patient results table

    From 22adbb419a80cca8a11cf7f06b6387794abccf98 Mon Sep 17 00:00:00 2001 From: James Hughes Date: Fri, 14 Oct 2022 09:29:14 +0100 Subject: [PATCH 18/18] implemented feedback --- .../results-list/ResultsDataFetcher.tsx | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index 064f4eb..9879a2d 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -8,16 +8,16 @@ import ResultsList from "../results-list/ResultsList"; import { Patient, Observation } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; -const FHIR_URL = process.env.REACT_APP_FHIR_URL; - -type trimmedObservation = { cDnaChange: string | undefined; observationId: string | undefined }; -type patientResult = { +type TrimmedObservation = { cDnaChange: string | undefined; observationId: string | undefined }; +type PatientResult = { firstName: string | undefined; lastName: string | undefined; patientId: string | undefined; - observations: trimmedObservation[]; + observations: TrimmedObservation[]; }; -export type parsedResultsModel = patientResult[]; +export type parsedResultsModel = PatientResult[]; + +const HGVS_CDNA_LOINC = "48004-6"; const ResultsDataFetcher: FC = () => { const ctx = useContext(FhirContext); @@ -26,7 +26,7 @@ const ResultsDataFetcher: FC = () => { const [parsedResults, setParsedResults] = useState(null); useEffect(() => { - const observationQueryUrl = `${FHIR_URL}/Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; + const observationQueryUrl = `Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; setIsLoading(true); @@ -76,7 +76,9 @@ const ResultsDataFetcher: FC = () => { // extract the required data and store in a trimmed down data structure patients.forEach((patient) => { - if (!patient.id || !patient.name || !patient.name[0].given) return; + if (!patient.id || !patient.name || !patient.name[0].given) { + return; + } const patientId = patient.id; const firstName = patient.name[0].given[0]; @@ -91,21 +93,24 @@ const ResultsDataFetcher: FC = () => { return subjectIdLong.includes(patientId); }); - if (!patientObservations || patientObservations.length === 0) return; + if (!patientObservations || patientObservations.length === 0) { + return; + } // extract required data from each observation - let trimmedObservations: trimmedObservation[] = []; + let trimmedObservations: TrimmedObservation[] = []; patientObservations.forEach((observation) => { - if (!observation || !observation.component) return; + if (!observation || !observation.component) { + return; + } - const trimmedObservation = observation.component.filter((component) => { - const loincCode = "48004-6"; - if (component?.code?.coding?.[0].code === loincCode) { + const TrimmedObservation = observation.component.filter((component) => { + if (component?.code?.coding?.[0].code === HGVS_CDNA_LOINC) { return component?.valueCodeableConcept?.coding?.[0].display; } }); - const cDnaChange = trimmedObservation[0].valueCodeableConcept?.coding?.[0].display; + const cDnaChange = TrimmedObservation[0].valueCodeableConcept?.coding?.[0].display; trimmedObservations = [...trimmedObservations, { cDnaChange: cDnaChange, observationId: observation.id }]; }); @@ -120,7 +125,7 @@ const ResultsDataFetcher: FC = () => { }; if (!parsedResults) { - return
    Getting observations.
    ; + return
    Something went wrong getting observations. Please try again later.
    ; } return (
    {patient.firstName}