Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f4e4cb2
add useIndividualViewableExperimentResults hook
noctillion Oct 30, 2025
438dd21
centralize drs fetches in ExplorerIndividualContent
noctillion Oct 30, 2025
90cd292
rm child DRS dispatches
noctillion Oct 30, 2025
ad29462
rm child drs dispatches
noctillion Oct 30, 2025
83b30e3
lint
noctillion Oct 30, 2025
dbffca6
lint
noctillion Oct 30, 2025
0cb3de3
rm unused dispatch/imports post-DRS
noctillion Oct 30, 2025
440fdf0
rm unused code
noctillion Oct 31, 2025
796bce8
move and export file format constants
noctillion Nov 27, 2025
ccf5d1a
rf to deduplicate by id
noctillion Nov 27, 2025
10a1902
rm local definitions of file format
noctillion Nov 27, 2025
a0bab4b
rf allExperimentResults
noctillion Nov 27, 2025
4350358
lint
noctillion Nov 27, 2025
8ef71bf
refacto for isViewableInIgv
noctillion Nov 28, 2025
d6aac5c
lint
noctillion Nov 28, 2025
186a77c
refactor unify DRS fetch actions into single retrieveDrsUrls flow
noctillion Jan 22, 2026
a0c85fa
refactor IGV and download URL state into urlsByFilename
noctillion Jan 22, 2026
452211e
implement unified DRS action and remove redundant hooks
noctillion Jan 22, 2026
6b3ccc3
update selector to access new urlsByFilename state
noctillion Jan 22, 2026
cc3c497
update IGV selector to use unified urlsByFilename state
noctillion Jan 22, 2026
a530349
remove unused hook
noctillion Jan 22, 2026
0d0f8bb
Merge branch 'master' of github.com:bento-platform/bento_web into ref…
noctillion Jan 22, 2026
01ded59
refactor useffect for early return
noctillion Feb 2, 2026
c71d062
fix defaut array for fuzzySearchResponse
noctillion Feb 2, 2026
1aa33de
refactor dispatch for drs urls search
noctillion Feb 2, 2026
e311854
add null check for allExperimentResults
noctillion Feb 3, 2026
3b737a0
Merge branch 'master' into refact/drs_individual_overview
davidlougheed Feb 5, 2026
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
32 changes: 32 additions & 0 deletions src/components/explorer/ExplorerIndividualContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
useIsDataEmpty,
useDeduplicatedIndividualBiosamples,
useIndividualResources,
useIndividualViewableExperimentResults,
explorerIndividualUrl,
} from "./utils";

Expand All @@ -28,6 +29,10 @@ import IndividualInterpretations from "./IndividualInterpretations";
import IndividualMedicalActions from "./IndividualMedicalActions";
import IndividualMeasurements from "./IndividualMeasurements";

import { useAppDispatch } from "@/store";
import { getFileDownloadUrlsFromDrs, getIgvUrlsFromDrs } from "@/modules/drs/actions";
import { guessFileType } from "@/utils/files";

const MENU_STYLE = {
marginLeft: "-24px",
marginRight: "-24px",
Expand Down Expand Up @@ -62,6 +67,33 @@ const ExplorerIndividualContent = () => {
const resourcesTuple = useIndividualResources(individual);
const individualContext = useMemo(() => ({ individualID, resourcesTuple }), [individualID, resourcesTuple]);

const dispatch = useAppDispatch();

const biosamplesData = useDeduplicatedIndividualBiosamples(individual);

const allExperimentResults = useMemo(
() => biosamplesData.flatMap((b) => (b?.experiments ?? []).flatMap((e) => e?.experiment_results ?? [])),
[biosamplesData],
);

const viewableExperimentResultsForIgv = useIndividualViewableExperimentResults(individual);

useEffect(() => {
if (allExperimentResults.length > 0) {
const downloadableFiles = allExperimentResults.map((r) => ({
...r,
file_format: r.file_format ?? guessFileType(r.filename),
}));
dispatch(getFileDownloadUrlsFromDrs(downloadableFiles)).catch(console.error);
}
}, [dispatch, allExperimentResults]);

useEffect(() => {
if (viewableExperimentResultsForIgv.length > 0) {
dispatch(getIgvUrlsFromDrs(viewableExperimentResultsForIgv)).catch(console.error);
}
}, [dispatch, viewableExperimentResultsForIgv]);

const individualUrl = explorerIndividualUrl(individualID);

const overviewPath = "overview";
Expand Down
20 changes: 1 addition & 19 deletions src/components/explorer/IndividualExperiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {
individualPropTypesShape,
ontologyShape,
} from "@/propTypes";
import { getFileDownloadUrlsFromDrs } from "@/modules/drs/actions";
import { useAppDispatch, useAppSelector } from "@/store";
import { guessFileType } from "@/utils/files";
import { useAppSelector } from "@/store";

import { useDeduplicatedIndividualBiosamples } from "./utils";
import { VIEWABLE_FILE_EXTENSIONS } from "@/components/display/FileDisplay";
Expand Down Expand Up @@ -305,8 +303,6 @@ const EXPERIMENT_COLUMNS = [
];

const Experiments = ({ individual, handleExperimentClick }) => {
const dispatch = useAppDispatch();

const { selectedExperiment } = useParams();

useEffect(() => {
Expand All @@ -325,20 +321,6 @@ const Experiments = ({ individual, handleExperimentClick }) => {
const biosamplesData = useDeduplicatedIndividualBiosamples(individual);
const experimentsData = useMemo(() => biosamplesData.flatMap((b) => b?.experiments ?? []), [biosamplesData]);

useEffect(() => {
// retrieve any download urls if experiments data changes

const downloadableFiles = experimentsData
.flatMap((e) => e?.experiment_results ?? [])
.map((r) => ({
// enforce file_format property
...r,
file_format: r.file_format ?? guessFileType(r.filename),
}));

dispatch(getFileDownloadUrlsFromDrs(downloadableFiles)).catch(console.error);
}, [dispatch, experimentsData]);

return (
<RoutedIndividualContentTable
data={experimentsData}
Expand Down
15 changes: 1 addition & 14 deletions src/components/explorer/IndividualTracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { SettingOutlined } from "@ant-design/icons";

import { BENTO_PUBLIC_URL, BENTO_URL } from "@/config";
import { individualPropTypesShape } from "@/propTypes";
import { getIgvUrlsFromDrs } from "@/modules/drs/actions";
import { setIgvPosition } from "@/modules/explorer/actions";
import { useIgvGenomes } from "@/modules/explorer/hooks";
import { useReferenceGenomes } from "@/modules/reference/hooks";
Expand Down Expand Up @@ -151,6 +150,7 @@ const IGV_JS_ANNOTATION_ALIASES = {
const IndividualTracks = ({ individual }) => {
const accessToken = useAccessToken();

const dispatch = useAppDispatch();
const igvDivRef = useRef();
const igvBrowserRef = useRef(null);
const [creatingIgvBrowser, setCreatingIgvBrowser] = useState(false);
Expand All @@ -163,8 +163,6 @@ const IndividualTracks = ({ individual }) => {
() => true, // We don't want to re-render anything when the position changes
);

const dispatch = useAppDispatch();

const referenceService = useService("reference");
// Built-in igv.js genomes (with annotations):
const { hasAttempted: igvGenomesAttempted, itemsByID: igvGenomesByID } = useIgvGenomes();
Expand Down Expand Up @@ -293,17 +291,6 @@ const IndividualTracks = ({ individual }) => {
[dispatch],
);

// retrieve urls on mount
useEffect(() => {
if (allTracks.length) {
// don't search if all urls already known
if (hasFreshUrls(allTracks, igvUrls)) {
return;
}
dispatch(getIgvUrlsFromDrs(allTracks)).catch(console.error);
}
}, [dispatch, allTracks, igvUrls]);

// update access token whenever necessary
useEffect(() => {
if (BENTO_URL) {
Expand Down
26 changes: 26 additions & 0 deletions src/components/explorer/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useEffect, useMemo } from "react";
import { fetchDatasetResourcesIfNecessary } from "@/modules/datasets/actions";
import { useAppDispatch, useAppSelector } from "@/store";
import { guessFileType } from "@/utils/files";

const VIEWABLE_FORMATS_LOWER = ["bam", "cram", "bigbed", "maf", "bigwig", "vcf", "gvcf"];

export const useDeduplicatedIndividualBiosamples = (individual) =>
useMemo(
Expand Down Expand Up @@ -136,3 +139,26 @@ export const ontologyTermSorter = (k) => (a, b) => {
};

export const explorerIndividualUrl = (individualID) => `/data/explorer/individuals/${individualID}`;

export const useIndividualViewableExperimentResults = (individual) => {
const biosamplesData = useDeduplicatedIndividualBiosamples(individual);
return useMemo(() => {
const experiments = biosamplesData.flatMap((b) => b?.experiments ?? []);
const vr = Object.values(
Object.fromEntries(
experiments
.flatMap((e) => e?.experiment_results ?? [])
.filter(
(expRes) =>
!!expRes.genome_assembly_id &&
VIEWABLE_FORMATS_LOWER.includes(expRes.file_format?.toLowerCase() ?? guessFileType(expRes.filename)),
)
.map((expRes) => {
const fileFormatLower = expRes.file_format?.toLowerCase() ?? guessFileType(expRes.filename);
return [expRes.filename, { ...expRes, fileFormatLower }];
}),
),
).sort((r1, r2) => (r1.fileFormatLower ?? "").localeCompare(r2.fileFormatLower ?? ""));
return vr;
}, [biosamplesData]);
};