Skip to content

Commit 2805933

Browse files
authored
Merge pull request #502 from bento-platform/refact/drs_individual_overview
refact: drs individual overview
2 parents 5727c7d + 3b737a0 commit 2805933

File tree

6 files changed

+151
-198
lines changed

6 files changed

+151
-198
lines changed

src/components/explorer/ExplorerIndividualContent.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import IndividualInterpretations from "./IndividualInterpretations";
2828
import IndividualMedicalActions from "./IndividualMedicalActions";
2929
import IndividualMeasurements from "./IndividualMeasurements";
3030

31+
import { useAppDispatch } from "@/store";
32+
import { retrieveDrsUrls } from "@/modules/drs/actions";
33+
import { guessFileType } from "@/utils/files";
34+
3135
const MENU_STYLE = {
3236
marginLeft: "-24px",
3337
marginRight: "-24px",
@@ -62,6 +66,29 @@ const ExplorerIndividualContent = () => {
6266
const resourcesTuple = useIndividualResources(individual);
6367
const individualContext = useMemo(() => ({ individualID, resourcesTuple }), [individualID, resourcesTuple]);
6468

69+
const dispatch = useAppDispatch();
70+
71+
const biosamplesData = useDeduplicatedIndividualBiosamples(individual);
72+
73+
const allExperimentResults = useMemo(() => {
74+
const rawResults = biosamplesData.flatMap((b) =>
75+
(b?.experiments ?? []).flatMap((e) => e?.experiment_results ?? []),
76+
);
77+
return Object.values(Object.fromEntries(rawResults.map((r) => [r.id, r])));
78+
}, [biosamplesData]);
79+
80+
useEffect(() => {
81+
if (!allExperimentResults || !allExperimentResults.length) {
82+
return;
83+
}
84+
85+
const downloadableFiles = allExperimentResults.map((r) => ({
86+
...r,
87+
file_format: r.file_format ?? guessFileType(r.filename),
88+
}));
89+
dispatch(retrieveDrsUrls(downloadableFiles)).catch(console.error);
90+
}, [dispatch, allExperimentResults]);
91+
6592
const individualUrl = explorerIndividualUrl(individualID);
6693

6794
const overviewPath = "overview";

src/components/explorer/IndividualExperiments.js

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import {
1212
individualPropTypesShape,
1313
ontologyShape,
1414
} from "@/propTypes";
15-
import { getFileDownloadUrlsFromDrs } from "@/modules/drs/actions";
16-
import { useAppDispatch, useAppSelector } from "@/store";
17-
import { guessFileType } from "@/utils/files";
15+
import { useAppSelector } from "@/store";
1816

1917
import { useDeduplicatedIndividualBiosamples } from "./utils";
2018
import { VIEWABLE_FILE_EXTENSIONS } from "@/components/display/FileDisplay";
@@ -37,8 +35,8 @@ const VIEWABLE_FILE_FORMATS = ["PDF", "CSV", "TSV"];
3735
const ExperimentResultActions = ({ result }) => {
3836
const { filename } = result;
3937

40-
const downloadUrls = useAppSelector((state) => state.drs.downloadUrlsByFilename);
41-
const url = downloadUrls[filename]?.url;
38+
const drsUrls = useAppSelector((state) => state.drs.urlsByFilename);
39+
const url = drsUrls[filename]?.url;
4240

4341
const [viewModalVisible, setViewModalVisible] = useState(false);
4442

@@ -319,8 +317,6 @@ const EXPERIMENT_COLUMNS = [
319317
];
320318

321319
const Experiments = ({ individual, handleExperimentClick }) => {
322-
const dispatch = useAppDispatch();
323-
324320
const { selectedExperiment } = useParams();
325321

326322
useEffect(() => {
@@ -339,20 +335,6 @@ const Experiments = ({ individual, handleExperimentClick }) => {
339335
const biosamplesData = useDeduplicatedIndividualBiosamples(individual);
340336
const experimentsData = useMemo(() => biosamplesData.flatMap((b) => b?.experiments ?? []), [biosamplesData]);
341337

342-
useEffect(() => {
343-
// retrieve any download urls if experiments data changes
344-
345-
const downloadableFiles = experimentsData
346-
.flatMap((e) => e?.experiment_results ?? [])
347-
.map((r) => ({
348-
// enforce file_format property
349-
...r,
350-
file_format: r.file_format ?? guessFileType(r.filename),
351-
}));
352-
353-
dispatch(getFileDownloadUrlsFromDrs(downloadableFiles)).catch(console.error);
354-
}, [dispatch, experimentsData]);
355-
356338
return (
357339
<RoutedIndividualContentTable
358340
data={experimentsData}

src/components/explorer/IndividualTracks.js

Lines changed: 9 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,20 @@ import { SettingOutlined } from "@ant-design/icons";
1111

1212
import { BENTO_PUBLIC_URL, BENTO_URL } from "@/config";
1313
import { individualPropTypesShape } from "@/propTypes";
14-
import { getIgvUrlsFromDrs } from "@/modules/drs/actions";
1514
import { setIgvPosition } from "@/modules/explorer/actions";
1615
import { useIgvGenomes } from "@/modules/explorer/hooks";
1716
import { useReferenceGenomes } from "@/modules/reference/hooks";
1817
import { useService } from "@/modules/services/hooks";
1918
import { useAppDispatch, useAppSelector } from "@/store";
20-
import { guessFileType } from "@/utils/files";
2119
import { simpleDeepCopy } from "@/utils/misc";
2220

23-
import { useDeduplicatedIndividualBiosamples } from "./utils";
21+
import {
22+
useDeduplicatedIndividualBiosamples,
23+
ALIGNMENT_FORMATS_LOWER,
24+
expResFileFormatLower,
25+
isViewableInIgv,
26+
expResFileFormatToIgvTypeAndFormat,
27+
} from "./utils";
2428

2529
const SQUISHED_CALL_HEIGHT = 10;
2630
const EXPANDED_CALL_HEIGHT = 50;
@@ -51,39 +55,6 @@ const DEBOUNCE_WAIT = 500;
5155
// verify url set is for this individual (may have stale urls from previous request)
5256
const hasFreshUrls = (files, urls) => files.every((f) => urls.hasOwnProperty(f.filename));
5357

54-
const ALIGNMENT_FORMATS_LOWER = ["bam", "cram"];
55-
const ANNOTATION_FORMATS_LOWER = ["bigbed"]; // TODO: experiment result: support more
56-
const MUTATION_FORMATS_LOWER = ["maf"];
57-
const WIG_FORMATS_LOWER = ["bigwig"]; // TODO: experiment result: support wig/bedGraph?
58-
const VARIANT_FORMATS_LOWER = ["vcf", "gvcf"];
59-
const VIEWABLE_FORMATS_LOWER = [
60-
...ALIGNMENT_FORMATS_LOWER,
61-
...ANNOTATION_FORMATS_LOWER,
62-
...MUTATION_FORMATS_LOWER,
63-
...WIG_FORMATS_LOWER,
64-
...VARIANT_FORMATS_LOWER,
65-
];
66-
67-
const expResFileFormatLower = (expRes) => expRes.file_format?.toLowerCase() ?? guessFileType(expRes.filename);
68-
69-
// For an experiment result to be viewable in IGV.js, it must have:
70-
// - an assembly ID, so we can contextualize it correctly
71-
// - a file format in the list of file formats we know how to handle
72-
const isViewableInIgv = (expRes) =>
73-
!!expRes.genome_assembly_id && VIEWABLE_FORMATS_LOWER.includes(expResFileFormatLower(expRes));
74-
75-
const expResFileFormatToIgvTypeAndFormat = (fileFormat) => {
76-
const ff = fileFormat.toLowerCase();
77-
78-
if (ALIGNMENT_FORMATS_LOWER.includes(ff)) return ["alignment", ff];
79-
if (ANNOTATION_FORMATS_LOWER.includes(ff)) return ["annotation", "bigBed"]; // TODO: expand if we support more
80-
if (MUTATION_FORMATS_LOWER.includes(ff)) return ["mut", ff];
81-
if (WIG_FORMATS_LOWER.includes(ff)) return ["wig", "bigWig"]; // TODO: expand if we support wig/bedGraph
82-
if (VARIANT_FORMATS_LOWER.includes(ff)) return ["variant", "vcf"];
83-
84-
return [undefined, undefined];
85-
};
86-
8758
const TrackControlTable = memo(({ toggleView, allFoundFiles }) => {
8859
const trackTableColumns = [
8960
{
@@ -151,20 +122,19 @@ const IGV_JS_ANNOTATION_ALIASES = {
151122
const IndividualTracks = ({ individual }) => {
152123
const accessToken = useAccessToken();
153124

125+
const dispatch = useAppDispatch();
154126
const igvDivRef = useRef();
155127
const igvBrowserRef = useRef(null);
156128
const [creatingIgvBrowser, setCreatingIgvBrowser] = useState(false);
157129

158-
const { igvUrlsByFilename: igvUrls, isFetchingIgvUrls } = useAppSelector((state) => state.drs);
130+
const { urlsByFilename: igvUrls, isFetchingUrls: isFetchingIgvUrls } = useAppSelector((state) => state.drs);
159131

160132
// read stored position only on first render
161133
const { igvPosition } = useAppSelector(
162134
(state) => state.explorer,
163135
() => true, // We don't want to re-render anything when the position changes
164136
);
165137

166-
const dispatch = useAppDispatch();
167-
168138
const referenceService = useService("reference");
169139
// Built-in igv.js genomes (with annotations):
170140
const { hasAttempted: igvGenomesAttempted, itemsByID: igvGenomesByID } = useIgvGenomes();
@@ -293,17 +263,6 @@ const IndividualTracks = ({ individual }) => {
293263
[dispatch],
294264
);
295265

296-
// retrieve urls on mount
297-
useEffect(() => {
298-
if (allTracks.length) {
299-
// don't search if all urls already known
300-
if (hasFreshUrls(allTracks, igvUrls)) {
301-
return;
302-
}
303-
dispatch(getIgvUrlsFromDrs(allTracks)).catch(console.error);
304-
}
305-
}, [dispatch, allTracks, igvUrls]);
306-
307266
// update access token whenever necessary
308267
useEffect(() => {
309268
if (BENTO_URL) {

src/components/explorer/utils.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
import { useEffect, useMemo } from "react";
22
import { fetchDatasetResourcesIfNecessary } from "@/modules/datasets/actions";
33
import { useAppDispatch, useAppSelector } from "@/store";
4+
import { guessFileType } from "@/utils/files";
5+
6+
// --- CONSTANTS ---
7+
export const ALIGNMENT_FORMATS_LOWER = ["bam", "cram"];
8+
export const ANNOTATION_FORMATS_LOWER = ["bigbed"];
9+
export const MUTATION_FORMATS_LOWER = ["maf"];
10+
export const WIG_FORMATS_LOWER = ["bigwig"];
11+
export const VARIANT_FORMATS_LOWER = ["vcf", "gvcf"];
12+
13+
export const IGV_VIEWABLE_FORMATS_LOWER = [
14+
...ALIGNMENT_FORMATS_LOWER,
15+
...ANNOTATION_FORMATS_LOWER,
16+
...MUTATION_FORMATS_LOWER,
17+
...WIG_FORMATS_LOWER,
18+
...VARIANT_FORMATS_LOWER,
19+
];
20+
21+
// --- HELPER FUNCTIONS ---
22+
23+
export const expResFileFormatLower = (expRes) => expRes.file_format?.toLowerCase() ?? guessFileType(expRes.filename);
24+
25+
// For an experiment result to be viewable in IGV.js, it must have:
26+
// - an assembly ID, so we can contextualize it correctly
27+
// - a file format in the list of file formats we know how to handle
28+
export const isViewableInIgv = (expRes) =>
29+
!!expRes.genome_assembly_id && IGV_VIEWABLE_FORMATS_LOWER.includes(expResFileFormatLower(expRes));
30+
31+
export const expResFileFormatToIgvTypeAndFormat = (fileFormat) => {
32+
const ff = fileFormat.toLowerCase();
33+
34+
if (ALIGNMENT_FORMATS_LOWER.includes(ff)) return ["alignment", ff];
35+
if (ANNOTATION_FORMATS_LOWER.includes(ff)) return ["annotation", "bigBed"]; // TODO: experiment result: support more
36+
if (MUTATION_FORMATS_LOWER.includes(ff)) return ["mut", ff];
37+
if (WIG_FORMATS_LOWER.includes(ff)) return ["wig", "bigWig"]; // TODO: expand if we support wig/bedGraph
38+
if (VARIANT_FORMATS_LOWER.includes(ff)) return ["variant", "vcf"];
39+
40+
return [undefined, undefined];
41+
};
42+
43+
// --- HOOKS ---
444

545
export const useDeduplicatedIndividualBiosamples = (individual) =>
646
useMemo(

0 commit comments

Comments
 (0)