Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[15] feat: phenopacket v2 frontend integration #311

Merged
merged 41 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
06e4b70
fix individual disease view, temp gene fix
v-rocheleau Oct 11, 2023
32b311f
update individual age in overview
v-rocheleau Oct 11, 2023
29a4f80
filter individual diseases
v-rocheleau Oct 11, 2023
2db5a84
display new biosample info
v-rocheleau Oct 12, 2023
1127f46
TimeElement component
v-rocheleau Oct 16, 2023
be7091c
TimeElement with spans
v-rocheleau Oct 16, 2023
0a8b281
fix phenotypic_feature.modifiers key and path
v-rocheleau Oct 16, 2023
0a1afed
GenomicInterpretations hook
v-rocheleau Oct 18, 2023
6f5893f
GenomicInterpretation component
v-rocheleau Oct 18, 2023
3a2f0e6
Individual interpretations route, interp components and utils
v-rocheleau Oct 19, 2023
d10e2eb
Variant descriptors tab, linked from interpretations
v-rocheleau Oct 20, 2023
2ad2f6e
lint
v-rocheleau Oct 20, 2023
f3f4e8f
disable explorer individual content when no data
v-rocheleau Oct 25, 2023
1a7b9d6
individual interpretations table refactor
v-rocheleau Oct 25, 2023
9618f8a
expandable genomic interp table
v-rocheleau Oct 26, 2023
f5cafe5
fix variant descriptor component for gene context
v-rocheleau Oct 27, 2023
29184f5
genomic interp table routing
v-rocheleau Oct 27, 2023
542ed44
lint
v-rocheleau Oct 27, 2023
b759cc0
add redux store for individual explorer url and resources
v-rocheleau Oct 27, 2023
8f53131
explorer individual medical actions
v-rocheleau Nov 8, 2023
106b623
measurements
v-rocheleau Nov 9, 2023
d2539fd
RoutedIndividualContent extract component refactor
v-rocheleau Nov 10, 2023
cc4f62b
full measurements with Value and ComplexValue
v-rocheleau Nov 10, 2023
10af941
complex value inner table
v-rocheleau Nov 13, 2023
417e540
measurements and medical actions display improvement and column sorters
v-rocheleau Nov 13, 2023
5be8769
disease onset timeelement
v-rocheleau Nov 14, 2023
709c0d1
genedescriptor, warning fixes
v-rocheleau Nov 14, 2023
6e57036
Merge branch 'master' into feat/phenov2
v-rocheleau Nov 22, 2023
8553813
remove resourcesTuple props drilling
v-rocheleau Nov 22, 2023
46512a0
typo
v-rocheleau Nov 23, 2023
8858f0b
code cleanup
v-rocheleau Nov 23, 2023
f93ab89
biosample and tracks links
v-rocheleau Nov 24, 2023
db4f845
reorder explorer menu, simple measurements table in IndividualBiosamples
v-rocheleau Nov 24, 2023
6855a59
replace age with time_at_last_encounter in individual overview
v-rocheleau Nov 24, 2023
45ce87f
move table columns outside of components, styling
v-rocheleau Nov 24, 2023
b556414
reorder individual content routes
v-rocheleau Nov 24, 2023
620c805
interpretations styling, consistent tables sizes
v-rocheleau Nov 24, 2023
a583a1f
add space before variant expression tracks button
v-rocheleau Nov 24, 2023
11da2cb
new phenopacket search options
v-rocheleau Nov 28, 2023
ade95f4
lint
v-rocheleau Nov 28, 2023
379e298
fix interpretation.diagnosis.disease field change
v-rocheleau Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 82 additions & 25 deletions src/components/explorer/ExplorerIndividualContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { fetchIndividualIfNecessary } from "../../modules/metadata/actions";
import { LAYOUT_CONTENT_STYLE } from "../../styles/layoutContent";
import { matchingMenuKeys, renderMenuItem } from "../../utils/menu";
import { urlPath } from "../../utils/url";
import { useIndividualResources } from "./utils";
import { useIsDataEmpty, useDeduplicatedIndividualBiosamples, useIndividualResources } from "./utils";

import SitePageHeader from "../SitePageHeader";
import IndividualOverview from "./IndividualOverview";
Expand All @@ -18,10 +18,12 @@ import IndividualBiosamples from "./IndividualBiosamples";
import IndividualExperiments from "./IndividualExperiments";
import IndividualDiseases from "./IndividualDiseases";
import IndividualOntologies from "./IndividualOntologies";
import IndividualVariants from "./IndividualVariants";
import IndividualGenes from "./IndividualGenes";
import IndividualTracks from "./IndividualTracks";
import IndividualPhenopackets from "./IndividualPhenopackets";
import IndividualInterpretations from "./IndividualInterpretations";
import { setIndividualId, setIndividualResourcesTuple } from "../../modules/explorer/actions";
import IndividualMedicalActions from "./IndividualMedicalActions";
import IndividualMeasurements from "./IndividualMeasurements";

const MENU_STYLE = {
marginLeft: "-24px",
Expand Down Expand Up @@ -55,6 +57,12 @@ const ExplorerIndividualContent = () => {
}
}, [location]);

useEffect(() => {
if (individualID) {
dispatch(setIndividualId(individualID));
}
}, [dispatch, individualID]);

const metadataService = useSelector((state) => state.services.metadataService);
const individuals = useSelector((state) => state.individuals.itemsByID);

Expand All @@ -68,33 +76,75 @@ const ExplorerIndividualContent = () => {

const { isFetching: individualIsFetching, data: individual } = individuals[individualID] ?? {};

// Trigger resource loading
useIndividualResources(individual);
const resources = useIndividualResources(individual);
useEffect(() => {
// Set individual resources in the store for OntologyTerm rendering with no prop drilling
if (resources) {
dispatch(setIndividualResourcesTuple(resources));
v-rocheleau marked this conversation as resolved.
Show resolved Hide resolved
}
}, [dispatch, resources]);

const overviewUrl = `${individualUrl}/overview`;
const phenotypicFeaturesUrl = `${individualUrl}/phenotypic-features`;
const biosamplesUrl = `${individualUrl}/biosamples`;
const experimentsUrl = `${individualUrl}/experiments`;
const variantsUrl = `${individualUrl}/variants`;
const genesUrl = `${individualUrl}/genes`;
const diseasesUrl = `${individualUrl}/diseases`;
const ontologiesUrl = `${individualUrl}/ontologies`;
const tracksUrl = `${individualUrl}/tracks`;
const phenopacketsUrl = `${individualUrl}/phenopackets`;
const interpretationsUrl = `${individualUrl}/interpretations`;
const medicalActionsUrl = `${individualUrl}/medical-actions`;
const measurementsUrl = `${individualUrl}/measurements`;

const individualPhenopackets = individual?.phenopackets ?? [];
const individualMenu = [
// Overview
{url: overviewUrl, style: {marginLeft: "4px"}, text: "Overview"},
{url: phenotypicFeaturesUrl, text: "Phenotypic Features"},
{url: biosamplesUrl, text: "Biosamples"},
{url: experimentsUrl, text: "Experiments"},
// Biosamples related menu items
{
url: biosamplesUrl,
text: "Biosamples",
disabled: useIsDataEmpty(individualPhenopackets, "biosamples"),
},
{
url: measurementsUrl,
text: "Measurements",
disabled: useIsDataEmpty(individualPhenopackets, "measurements"),
},
{
url: phenotypicFeaturesUrl,
text: "Phenotypic Features",
disabled: useIsDataEmpty(individualPhenopackets, "phenotypic_features"),
},
{
url: diseasesUrl,
text: "Diseases",
disabled: useIsDataEmpty(individualPhenopackets, "diseases"),
},
{
url: interpretationsUrl,
text: "Interpretations",
disabled: useIsDataEmpty(individualPhenopackets, "interpretations"),
},
{
url: medicalActionsUrl,
text: "Medical Actions",
disabled: useIsDataEmpty(individualPhenopackets, "medical_actions"),
},
// Experiments related menu items
{
url: experimentsUrl,
text: "Experiments",
disabled: useIsDataEmpty(
useDeduplicatedIndividualBiosamples(individual),
"experiments",
),
},
{url: tracksUrl, text: "Tracks"},
{url: variantsUrl, text: "Variants"},
{url: genesUrl, text: "Genes"},
{url: diseasesUrl, text: "Diseases"},
// Extra
{url: ontologiesUrl, text: "Ontologies"},
{url: phenopacketsUrl, text: "Phenopackets JSON"},
davidlougheed marked this conversation as resolved.
Show resolved Hide resolved
];

const selectedKeys = matchingMenuKeys(individualMenu, urlPath(BENTO_URL));

return <>
Expand All @@ -114,30 +164,37 @@ const ExplorerIndividualContent = () => {
<Layout>
<Layout.Content style={LAYOUT_CONTENT_STYLE}>
{(individual && !individualIsFetching) ? <Switch>
{/* OVERVIEW */}
<Route path={overviewUrl.replace(":", "\\:")}>
<IndividualOverview individual={individual} />
</Route>
{/* BIOSAMPLES RELATED */}
<Route path={biosamplesUrl.replace(":", "\\:")}>
<IndividualBiosamples individual={individual} experimentsUrl={experimentsUrl}/>
</Route>
<Route path={measurementsUrl.replace(":", "\\:")}>
<IndividualMeasurements individual={individual} />
</Route>
<Route path={phenotypicFeaturesUrl.replace(":", "\\:")}>
<IndividualPhenotypicFeatures individual={individual} />
</Route>
<Route path={biosamplesUrl.replace(":", "\\:")}>
<IndividualBiosamples individual={individual} experimentsUrl={experimentsUrl} />
<Route path={diseasesUrl.replace(":", "\\:")}>
<IndividualDiseases individual={individual} />
</Route>
<Route path={interpretationsUrl.replace(":", "\\:")}>
<IndividualInterpretations individual={individual} />
</Route>
<Route path={medicalActionsUrl.replace(":", "\\:")}>
<IndividualMedicalActions individual={individual}/>
</Route>
{/* EXPERIMENTS RELATED*/}
<Route path={experimentsUrl.replace(":", "\\:")}>
<IndividualExperiments individual={individual} />
</Route>
<Route path={tracksUrl.replace(":", "\\:")}>
<IndividualTracks individual={individual} />
</Route>
<Route path={variantsUrl.replace(":", "\\:")}>
<IndividualVariants individual={individual} tracksUrl={tracksUrl}/>
</Route>
<Route path={genesUrl.replace(":", "\\:")}>
<IndividualGenes individual={individual} tracksUrl={tracksUrl} />
</Route>
<Route path={diseasesUrl.replace(":", "\\:")}>
<IndividualDiseases individual={individual} />
</Route>
{/* EXTRA */}
<Route path={ontologiesUrl.replace(":", "\\:")}>
<IndividualOntologies individual={individual} />
</Route>
Expand Down
62 changes: 40 additions & 22 deletions src/components/explorer/IndividualBiosamples.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Route, Switch, useHistory, useRouteMatch, useParams } from "react-route
import { Button, Descriptions, Table } from "antd";

import { EM_DASH } from "../../constants";
import { useDeduplicatedIndividualBiosamples, useIndividualResources } from "./utils";
import { useDeduplicatedIndividualBiosamples } from "./utils";
import {
biosamplePropTypesShape,
experimentPropTypesShape,
Expand All @@ -15,28 +15,37 @@ import {

import JsonView from "./JsonView";
import OntologyTerm from "./OntologyTerm";
import TimeElement from "./TimeElement";

import "./explorer.css";
import BiosampleIDCell from "./searchResultsTables/BiosampleIDCell";
import { MeasurementsTable } from "./IndividualMeasurements";

// TODO: Only show biosamples from the relevant dataset, if specified;
// highlight those found in search results, if specified

const BiosampleProcedure = ({ resourcesTuple, procedure }) => (
const BiosampleProcedure = ({ procedure }) => (
<div>
<strong>Code:</strong>{" "}<OntologyTerm resourcesTuple={resourcesTuple} term={procedure.code} />
<strong>Code:</strong>{" "}<OntologyTerm term={procedure.code} />
{procedure.body_site ? (
<div>
<strong>Body Site:</strong>{" "}
<OntologyTerm resourcesTuple={resourcesTuple} term={procedure.body_site} />
<OntologyTerm term={procedure.body_site} />
</div>
) : null}
{procedure.performed ? (
<div>
<strong>Performed:</strong>{" "}
<TimeElement timeElement={procedure.performed} />
</div>
) : null}
</div>
);
BiosampleProcedure.propTypes = {
resourcesTuple: PropTypes.array,
procedure: PropTypes.shape({
code: ontologyShape.isRequired,
body_site: ontologyShape,
performed: PropTypes.object,
}).isRequired,
};

Expand All @@ -60,28 +69,40 @@ ExperimentsClickList.propTypes = {
handleExperimentClick: PropTypes.func,
};

const BiosampleDetail = ({ individual, biosample, handleExperimentClick }) => {
const resourcesTuple = useIndividualResources(individual);
const BiosampleDetail = ({ biosample, handleExperimentClick }) => {
return (
<Descriptions bordered={true} column={1} size="small" style={{maxWidth: 800}}>
<Descriptions bordered={true} column={1} size="small">
<Descriptions.Item label="ID">
{biosample.id}
</Descriptions.Item>
<Descriptions.Item label="Derived from ID">
{biosample.derived_from_id ? <BiosampleIDCell biosample={biosample.derived_from_id} /> : EM_DASH}
</Descriptions.Item>
<Descriptions.Item label="Sampled Tissue">
<OntologyTerm resourcesTuple={resourcesTuple} term={biosample.sampled_tissue} />
<OntologyTerm term={biosample.sampled_tissue} />
</Descriptions.Item>
<Descriptions.Item label="Sample Type">
<OntologyTerm term={biosample.sample_type} />
</Descriptions.Item>
<Descriptions.Item label="Procedure">
<BiosampleProcedure resourcesTuple={resourcesTuple} procedure={biosample.procedure} />
<BiosampleProcedure procedure={biosample.procedure} />
</Descriptions.Item>
<Descriptions.Item label="Histological Diagnosis">
<OntologyTerm resourcesTuple={resourcesTuple} term={biosample.histological_diagnosis} />
<OntologyTerm term={biosample.histological_diagnosis} />
</Descriptions.Item>
<Descriptions.Item label="Pathological Stage">
<OntologyTerm term={biosample.pathological_stage} />
</Descriptions.Item>
<Descriptions.Item label="Ind. Age At Collection">
{biosample.individual_age_at_collection
? biosample.individual_age_at_collection.age ??
`Between ${biosample.individual_age_at_collection.start.age}` +
`and ${biosample.individual_age_at_collection.end.age}`
: EM_DASH}
<Descriptions.Item label="Time of Collection">
<TimeElement timeElement={biosample.time_of_collection}/>
</Descriptions.Item>
<Descriptions.Item label="Measurements">
{biosample.hasOwnProperty("measurements") &&
Object.keys(biosample.measurements).length ? (
<MeasurementsTable measurements={biosample.measurements}/>
) : (
EM_DASH
)}
</Descriptions.Item>
<Descriptions.Item label="Extra Properties">
{biosample.hasOwnProperty("extra_properties") &&
Expand All @@ -101,7 +122,6 @@ const BiosampleDetail = ({ individual, biosample, handleExperimentClick }) => {
);
};
BiosampleDetail.propTypes = {
individual: individualPropTypesShape,
biosample: biosamplePropTypesShape,
handleExperimentClick: PropTypes.func,
};
Expand All @@ -127,7 +147,6 @@ const Biosamples = ({ individual, handleBiosampleClick, handleExperimentClick })
}, []);

const biosamples = useDeduplicatedIndividualBiosamples(individual);
const resourcesTuple = useIndividualResources(individual);

const columns = useMemo(
() => [
Expand All @@ -139,7 +158,7 @@ const Biosamples = ({ individual, handleBiosampleClick, handleExperimentClick })
{
title: "Sampled Tissue",
dataIndex: "sampled_tissue",
render: tissue => <OntologyTerm resourcesTuple={resourcesTuple} term={tissue} />,
render: tissue => <OntologyTerm term={tissue} />,
},
{
title: "Experiments",
Expand All @@ -149,7 +168,7 @@ const Biosamples = ({ individual, handleBiosampleClick, handleExperimentClick })
),
},
],
[resourcesTuple, handleExperimentClick],
[handleExperimentClick],
);

const onExpand = useCallback(
Expand All @@ -162,7 +181,6 @@ const Biosamples = ({ individual, handleBiosampleClick, handleExperimentClick })
const expandedRowRender = useCallback(
(biosample) => (
<BiosampleDetail
individual={individual}
biosample={biosample}
handleExperimentClick={handleExperimentClick}
/>
Expand Down
Loading