Skip to content

Commit

Permalink
feat: add reference service slice + gene search hook
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlougheed committed Nov 21, 2024
1 parent 50ea993 commit a7662f4
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/js/components/BentoAppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fetchGohanData, fetchKatsuData } from '@/features/ingestion/lastIngesti
import { makeGetDataTypes } from '@/features/dataTypes/dataTypes.store';
import { useMetadata } from '@/features/metadata/hooks';
import { getProjects, markScopeSet, selectScope } from '@/features/metadata/metadata.store';
import { getGenomes } from '@/features/reference/reference.store';

import Loader from '@/components/Loader';
import DefaultLayout from '@/components/Util/DefaultLayout';
Expand Down Expand Up @@ -107,6 +108,7 @@ const BentoAppRouter = () => {
dispatch(fetchGohanData());
dispatch(makeGetServiceInfoRequest());
dispatch(makeGetDataTypes());
dispatch(getGenomes());
}, [dispatch]);

if (isAutoAuthenticating || projectsStatus === RequestStatus.Pending) {
Expand Down
4 changes: 3 additions & 1 deletion src/js/constants/configConstants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PORTAL_URL } from '@/config';
import { PUBLIC_URL_NO_TRAILING_SLASH, PORTAL_URL } from '@/config';

export const MAX_CHARTS = 3;

Expand All @@ -10,6 +10,8 @@ export const projectsUrl = `${PORTAL_URL}/api/metadata/api/projects`;
export const katsuLastIngestionsUrl = `${PORTAL_URL}/api/metadata/data-types`;
export const gohanLastIngestionsUrl = `${PORTAL_URL}/api/gohan/data-types`;

export const referenceGenomesUrl = `${PUBLIC_URL_NO_TRAILING_SLASH}/api/reference/genomes`;

export const DEFAULT_TRANSLATION = 'default_translation';
export const CUSTOMIZABLE_TRANSLATION = 'translation';

Expand Down
45 changes: 45 additions & 0 deletions src/js/features/reference/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useEffect, useState } from 'react';
import { useAuthorizationHeader } from 'bento-auth-js';
import { referenceGenomesUrl } from '@/constants/configConstants';
import { RequestStatus } from '@/types/requests';
import type { GenomeFeature } from './types';

export const useGeneNameSearch = (referenceGenomeID: string | undefined, nameQuery: string | null | undefined) => {
const authHeader = useAuthorizationHeader();

const [status, setStatus] = useState<RequestStatus>(RequestStatus.Idle);
const [data, setData] = useState<GenomeFeature[]>([]);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!referenceGenomeID || !nameQuery) return;

const params = new URLSearchParams({ name: nameQuery, name_fzy: 'true', limit: '10' });
const searchUrl = `${referenceGenomesUrl}/${referenceGenomeID}/features?${params.toString()}`;

setError(null);

(async () => {
setStatus(RequestStatus.Pending);

try {
const res = await fetch(searchUrl, { headers: { Accept: 'application/json', ...authHeader } });
const resData = await res.json();
if (res.ok) {
console.debug('Genome feature search - got results:', resData.results);
setData(resData.results);
setStatus(RequestStatus.Fulfilled);
} else {
setError(`Genome feature search failed with message: ${resData.message}`);
setStatus(RequestStatus.Rejected);
}
} catch (e) {
console.error(e);
setError(`Genome feature search failed: ${(e as Error).toString()}`);
setStatus(RequestStatus.Rejected);
}
})();
}, [referenceGenomeID, nameQuery, authHeader]);

return { status, data, error };
};
60 changes: 60 additions & 0 deletions src/js/features/reference/reference.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios from 'axios';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

import { referenceGenomesUrl } from '@/constants/configConstants';
import type { RootState } from '@/store';
import { RequestStatus } from '@/types/requests';
import { printAPIError } from '@/utils/error.util';

import type { Genome } from './types';

const storeName = 'reference';

export type ReferenceState = {
genomesStatus: RequestStatus;
genomes: Genome[];
genomesByID: Record<string, Genome>;
};

const initialState: ReferenceState = {
genomesStatus: RequestStatus.Idle,
genomes: [],
genomesByID: {},
};

export const getGenomes = createAsyncThunk<Genome[], void, { state: RootState }>(
`${storeName}/getGenomes`,
(_, { rejectWithValue }) => {
return axios
.get(referenceGenomesUrl)
.then((res) => res.data)
.catch(printAPIError(rejectWithValue));
},
{
condition(_, { getState }) {
const { genomesStatus } = getState().reference;
return genomesStatus === RequestStatus.Idle;
},
}
);

const reference = createSlice({
name: storeName,
initialState,
reducers: {},
extraReducers(builder) {
builder.addCase(getGenomes.pending, (state) => {
state.genomesStatus = RequestStatus.Pending;
});
builder.addCase(getGenomes.fulfilled, (state, { payload }) => {
state.genomes = payload;
state.genomesByID = Object.fromEntries(payload.map((g) => [g.id, g]));
state.genomesStatus = RequestStatus.Fulfilled;
});
builder.addCase(getGenomes.rejected, (state) => {
state.genomesStatus = RequestStatus.Rejected;
});
},
});

export default reference.reducer;
41 changes: 41 additions & 0 deletions src/js/features/reference/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { OntologyTerm } from '@/types/ontology';

// See also: https://github.com/bento-platform/bento_reference_service/blob/main/bento_reference_service/models.py

export type Contig = {
name: string;
aliases: string[];
md5: string;
ga4gh: string;
length: number;
circular: boolean;
refget_uris: string[];
};

export type Genome = {
id: string;
aliases: string[];
md5: string;
ga4gh: string;
fasta: string;
fai: string;
gff3_gz: string;
gff3_gz_tbi: string;
taxon: OntologyTerm;
contigs: Contig[];
uri: string;
};

export type GenomeFeature = {
genome_id: string;
contig_name: string;

strand: '-' | '+' | '?' | '.';

feature_id: string;
feature_name: string;
feature_type: string;

source: string;
entries: { start_pos: number; end_pos: number; score: number | null; phase: number | null }[];
};
2 changes: 2 additions & 0 deletions src/js/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import lastIngestionDataReducer from '@/features/ingestion/lastIngestion.store';
import beaconReducer from './features/beacon/beacon.store';
import beaconNetworkReducer from './features/beacon/network.store';
import metadataReducer from '@/features/metadata/metadata.store';
import reference from '@/features/reference/reference.store';
import { getValue, saveValue } from './utils/localStorage';

interface PersistedState {
Expand All @@ -39,6 +40,7 @@ export const store = configureStore({
beacon: beaconReducer,
beaconNetwork: beaconNetworkReducer,
metadata: metadataReducer,
reference,
},
preloadedState: persistedState,
});
Expand Down
4 changes: 4 additions & 0 deletions src/js/types/ontology.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type OntologyTerm = {
id: string;
label: string;
};

0 comments on commit a7662f4

Please sign in to comment.