From 7f8c69a457af4f9f85478d1894b25f5ad575c9a8 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 18:27:23 -0500 Subject: [PATCH 01/39] chore(client): support node switching in client w/ state resets --- client/src/components/Header.js | 15 +++++++-- client/src/helpers/reducers.js | 13 -------- client/src/reducers.js | 55 ++++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/client/src/components/Header.js b/client/src/components/Header.js index d4d313f3..8b050148 100644 --- a/client/src/components/Header.js +++ b/client/src/components/Header.js @@ -1,5 +1,5 @@ -import React from 'react' -import {useSelector} from "react-redux"; +import React, {useCallback} from 'react' +import {useDispatch, useSelector} from "react-redux"; import {Alert, Button, Container, Input} from 'reactstrap' import {Link, useLocation, useNavigate} from "react-router-dom"; @@ -8,9 +8,11 @@ import Icon from "./Icon"; import {EPIVAR_NODES} from "../config"; import {SITE_SUBTITLE, SITE_TITLE} from "../constants/app"; import {useCurrentDataset, useDatasetsByNode, useDevMode, useNode} from "../hooks"; +import {setNode} from "../actions"; export default function Header({children, onAbout, /*onDatasets, */onDatasetAbout, onOverview, onExplore, onFAQ, /*, onContact*/}) { + const dispatch = useDispatch(); const location = useLocation(); const navigate = useNavigate(); @@ -24,6 +26,13 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou console.debug("Datasets by node:", datasetsByNode); + const onDatasetChange = useCallback((e) => { + const newNode = e.target.value; + if (newNode !== node) { + dispatch(setNode(newNode)); + } + }, [dispatch]); + return
@@ -43,7 +52,7 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou
- + {EPIVAR_NODES.map((n) => { if (n in datasetsByNode) { const d = datasetsByNode[n]; diff --git a/client/src/helpers/reducers.js b/client/src/helpers/reducers.js index 3803f0c8..88bfb33f 100644 --- a/client/src/helpers/reducers.js +++ b/client/src/helpers/reducers.js @@ -22,16 +22,3 @@ export const makeDefaultDataState = (defaultData = undefined) => ({ isLoaded: false, data: defaultData, }); - -export const makeDataReducer = (types, defaultState) => (state = defaultState, action) => { - switch (action.type) { - case types.REQUEST: - return {...state, isLoading: true}; - case types.RECEIVE: - return {...state, isLoading: false, isLoaded: true, data: action.payload}; - case types.ERROR: - return {...state, isLoading: false}; - default: - return state; - } -}; diff --git a/client/src/reducers.js b/client/src/reducers.js index 0b5c89c6..b1263c84 100644 --- a/client/src/reducers.js +++ b/client/src/reducers.js @@ -2,7 +2,7 @@ import { combineReducers } from "redux" import {EPIVAR_NODES} from "./config"; import * as k from "./constants/ActionTypes" -import {makeDataReducer, makeDefaultDataState, makeDefaultListState, makeListReducer} from "./helpers/reducers"; +import {makeDefaultListState, makeListReducer} from "./helpers/reducers"; const defaultChrom = 'rsID'; @@ -62,7 +62,7 @@ const defaultSamples = { end: undefined, ref: undefined, } -} +}; function samplesReducer(state = defaultSamples, action) { switch (action.type) { case k.SAMPLES.REQUEST: { @@ -74,6 +74,12 @@ function samplesReducer(state = defaultSamples, action) { case k.SAMPLES.ERROR: { return { ...state, isLoading: false } } + + // TODO: this can create a race condition with samples fetching + case k.SET_NODE: { + return defaultSamples; + } + default: return state; } @@ -114,7 +120,7 @@ const defaultPositions = { isLoaded: false, lastRequestDispatched: 0, list: [], -} +}; function positionsReducer(state = defaultPositions, action) { switch (action.type) { case k.POSITIONS.REQUEST: { @@ -132,6 +138,12 @@ function positionsReducer(state = defaultPositions, action) { // TODO: Should this use cancel token? return {...state, isLoading: action.meta.dispatchedAt >= state.lastRequestDispatched ? false : state.isLoading} } + + // TODO: this can create a race condition with positions fetching + case k.SET_NODE: { + return defaultPositions; + } + default: return state; } @@ -234,6 +246,12 @@ const overviewReducer = (state = defaultOverview, action) => { case k.OVERVIEW_CONFIG.ERROR: { return {...state, isLoading: false}; } + + // TODO: this can create a race condition with overview config loading + case k.SET_NODE: { + return defaultOverview; + } + default: return state; } @@ -289,13 +307,40 @@ const manhattanReducer = (state = defaultManhattan, action) => { }; } + // TODO: this can create a race condition with manhattan data loading + case k.SET_NODE: { + return defaultManhattan; + } + + default: + return state; + } +}; + +const defaultUser = { + isLoading: false, + isLoaded: false, + data: null, +}; +const userReducer = (state = defaultUser, action) => { + switch (action.type) { + case k.USER.REQUEST: + return {...state, isLoading: true}; + case k.USER.RECEIVE: + return {...state, isLoading: false, isLoaded: true, data: action.payload}; + case k.USER.ERROR: + return {...state, isLoading: false}; + + // TODO: this can create a race condition with user fetching + case k.SET_NODE: { + return defaultUser; + } + default: return state; } }; -const defaultUser = makeDefaultDataState(); -const userReducer = makeDataReducer(k.USER, defaultUser); const defaultMessages = makeDefaultListState(); const messagesReducer = makeListReducer(k.MESSAGES, defaultMessages); From e7844f3ce72546ab7ae20ee2c9e55848d1be9661 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 18:30:00 -0500 Subject: [PATCH 02/39] chore(client): prevent (some) race conditions when switching dataset --- client/src/components/Header.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/components/Header.js b/client/src/components/Header.js index 8b050148..f82f1e32 100644 --- a/client/src/components/Header.js +++ b/client/src/components/Header.js @@ -26,12 +26,20 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou console.debug("Datasets by node:", datasetsByNode); + const isLoadingData = useSelector((state) => + state.samples.isLoading || + state.positions.isLoading || + state.overview.isLoading || + state.user.isLoading); + const onDatasetChange = useCallback((e) => { + if (isLoadingData) return; + const newNode = e.target.value; if (newNode !== node) { dispatch(setNode(newNode)); } - }, [dispatch]); + }, [dispatch, isLoadingData]); return
From a564d1790fcebcd31e3542c93864650724710d20 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 19:58:16 -0500 Subject: [PATCH 03/39] chore(prod): re-enable hg38 [no ci] --- epivar-prod/portal.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epivar-prod/portal.env b/epivar-prod/portal.env index 164b4a0f..8c26ad7b 100644 --- a/epivar-prod/portal.env +++ b/epivar-prod/portal.env @@ -1 +1 @@ -EPIVAR_NODES=https://flu-infection.vhost38.genap.ca/aracena-hg19 +EPIVAR_NODES=https://flu-infection.vhost38.genap.ca/aracena-hg19;https://flu-infection.vhost38.genap.ca/aracena-hg38 From 636342d23837bb894c72620deb11737309abb357 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 20:07:15 -0500 Subject: [PATCH 04/39] chore(prod): set images to pr-15 [no ci] --- epivar-prod/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/epivar-prod/docker-compose.yml b/epivar-prod/docker-compose.yml index f42876d8..dab8786f 100644 --- a/epivar-prod/docker-compose.yml +++ b/epivar-prod/docker-compose.yml @@ -2,7 +2,7 @@ services: # Common ------------------------------------------------------------------------------------------------------------- epivar-portal: - image: ghcr.io/c3g/epivar-portal:edge + image: ghcr.io/c3g/epivar-portal:pr-15 ports: - 80:80 depends_on: @@ -19,7 +19,7 @@ services: epivar-node-1-server: # user: $EPIVAR_UID:$EPIVAR_UID - image: ghcr.io/c3g/epivar-server:edge + image: ghcr.io/c3g/epivar-server:pr-15 networks: - epivar-node-1-server-net - epivar-node-1-redis-net @@ -83,7 +83,7 @@ services: epivar-node-2-server: # user: $USER:$USER - image: ghcr.io/c3g/epivar-server:edge + image: ghcr.io/c3g/epivar-server:pr-15 networks: - epivar-node-2-server-net - epivar-node-2-redis-net From c99f3cd552b1c0d50fa09611dd835baa83c68e5c Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 20:08:08 -0500 Subject: [PATCH 05/39] cleanup --- epivar-prod/docker-compose.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/epivar-prod/docker-compose.yml b/epivar-prod/docker-compose.yml index dab8786f..e5a7cbb3 100644 --- a/epivar-prod/docker-compose.yml +++ b/epivar-prod/docker-compose.yml @@ -18,7 +18,6 @@ services: # Node 1: hg19 data -------------------------------------------------------------------------------------------------- epivar-node-1-server: - # user: $EPIVAR_UID:$EPIVAR_UID image: ghcr.io/c3g/epivar-server:pr-15 networks: - epivar-node-1-server-net @@ -36,8 +35,6 @@ services: - EPIVAR_IMPORT_MAX_P_VAL=0.05 - EPIVAR_LOW_COUNT_THRESHOLD=5 volumes: - # user information -# - /etc/passwd:/etc/passwd:ro # dataset configuration: about Markdown file, EpiVar config.js - ./node1/about.md:/app/data/about.md - ./node1/config.js:/app/config.js:ro @@ -82,7 +79,6 @@ services: # Node 2: hg38 data (lifted over) ------------------------------------------------------------------------------------ epivar-node-2-server: - # user: $USER:$USER image: ghcr.io/c3g/epivar-server:pr-15 networks: - epivar-node-2-server-net @@ -100,8 +96,6 @@ services: - EPIVAR_IMPORT_MAX_P_VAL=0.05 - EPIVAR_LOW_COUNT_THRESHOLD=5 volumes: - # user information -# - /etc/passwd:/etc/passwd:ro # dataset configuration: about Markdown file, EpiVar config.js, tracks metadata.json - ./node2/about.md:/app/data/about.md - ./node2/config.js:/app/config.js:ro From 45e10575bbdca2907d80a9d697ec6db9b0ba44c6 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 20:58:00 -0500 Subject: [PATCH 06/39] fix(client): add values to dataset options --- client/src/components/Header.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/Header.js b/client/src/components/Header.js index f82f1e32..daf771ed 100644 --- a/client/src/components/Header.js +++ b/client/src/components/Header.js @@ -37,6 +37,7 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou const newNode = e.target.value; if (newNode !== node) { + console.info("selecting node", newNode); dispatch(setNode(newNode)); } }, [dispatch, isLoadingData]); @@ -65,9 +66,9 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou if (n in datasetsByNode) { const d = datasetsByNode[n]; console.debug("Adding option for dataset", n, d); - return ; + return ; } else { - return ; + return ; } })} From 6e835ca438690f676f733ab1c5d6e3cad74a9860 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 21:14:42 -0500 Subject: [PATCH 07/39] fix(client): reset more state on dataset switch --- client/src/components/Header.js | 2 ++ client/src/helpers/reducers.js | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/src/components/Header.js b/client/src/components/Header.js index daf771ed..4bfc5bc3 100644 --- a/client/src/components/Header.js +++ b/client/src/components/Header.js @@ -27,7 +27,9 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou console.debug("Datasets by node:", datasetsByNode); const isLoadingData = useSelector((state) => + state.assays.isLoading || state.samples.isLoading || + state.peaks.isLoading || state.positions.isLoading || state.overview.isLoading || state.user.isLoading); diff --git a/client/src/helpers/reducers.js b/client/src/helpers/reducers.js index 88bfb33f..e3fcda12 100644 --- a/client/src/helpers/reducers.js +++ b/client/src/helpers/reducers.js @@ -1,3 +1,5 @@ +import * as k from "../constants/ActionTypes" + export const makeDefaultListState = (defaultList = undefined) => ({ isLoading: false, isLoaded: false, @@ -12,13 +14,12 @@ export const makeListReducer = (types, defaultState) => (state = defaultState, a return {...state, isLoading: false, isLoaded: true, list: action.payload}; case types.ERROR: return {...state, isLoading: false}; + + // TODO: this can create a race condition with fetching + case k.SET_NODE: + return defaultState; + default: return state; } }; - -export const makeDefaultDataState = (defaultData = undefined) => ({ - isLoading: false, - isLoaded: false, - data: defaultData, -}); From 9e2beebc5676f5d2e4dc1824b81d00651a71f5cc Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 21:26:11 -0500 Subject: [PATCH 08/39] fix(client): move node-first-set dispatches to useEffect in App --- client/src/components/App.js | 32 ++++++++++++++++++++++++++++++-- client/src/index.js | 22 ---------------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/client/src/components/App.js b/client/src/components/App.js index b71afa3e..ad80e4a5 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -17,8 +17,10 @@ import ExplorePage from "./pages/ExplorePage"; import DatasetsPage from "./pages/DatasetsPage"; import FAQPage from "./pages/FAQPage"; -import {setDevMode, saveUser} from "../actions"; +import {setDevMode, saveUser, fetchDatasets, setNode, fetchUser, fetchMessages, fetchAssays} from "../actions"; import {SITE_SUBTITLE, SITE_TITLE} from "../constants/app"; +import {EPIVAR_NODES} from "../config"; +import {useNode} from "../hooks"; const RoutedApp = () => { @@ -62,7 +64,33 @@ const RoutedApp = () => { dispatch(setDevMode(true)); } }); - }, []); + + // On first initialization, load datasets: + dispatch(fetchDatasets()); + }, [dispatch]); + + const node = useNode(); + const datasetsByNode = useSelector((state) => state.datasets.datasetsByNode); + + useEffect(() => { + const firstNode = EPIVAR_NODES[0]; + if (!node && firstNode && datasetsByNode[firstNode]) { + // Select first node if we haven't already done so + dispatch(setNode(firstNode)); + } else if (node) { + // TODO: report error to users + console.error("Either no nodes are configured, or dataset information was not fetched successfully"); + } + }, [datasetsByNode]); + + useEffect(() => { + if (node) { + // When the node is set / changed, load relevant data: + dispatch(fetchUser()); + dispatch(fetchMessages()); // Server-side messages, e.g. auth errors + dispatch(fetchAssays()); + } + }, [dispatch, node]); useEffect(() => { if (userData.isLoaded && userData.data && !userData.data.consentedToTerms) { diff --git a/client/src/index.js b/client/src/index.js index 6b453529..78ad7eed 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -14,14 +14,6 @@ import { composeWithDevToolsLogOnlyInProduction } from '@redux-devtools/extensio import './styles.css'; import { rootReducer } from './reducers'; import App from './components/App'; -import { - fetchAssays, - fetchDatasets, - fetchMessages, - fetchUser, setNode -} from './actions.js' -import {EPIVAR_NODES} from "./config"; - const initialState = {}; @@ -48,20 +40,6 @@ render( document.querySelector('#root') ); -store.dispatch(fetchDatasets()).then(() => { - const { datasetsByNode } = store.getState().datasets; - const firstNode = EPIVAR_NODES[0]; - if (firstNode && datasetsByNode[firstNode]) { - store.dispatch(setNode(firstNode)); - store.dispatch(fetchUser()); - store.dispatch(fetchMessages()); // Server-side messages, e.g. auth errors - store.dispatch(fetchAssays()); - } else { - // TODO: report error to users - console.error("Either no nodes are configured, or dataset information was not fetched successfully") - } -}); - /* Re-enable to bring back server-fetched genomic chromosomes. For now, this instead just holds rsID + gene. - David L, 2023-05-25 From 467350bcd6f6aa0cd935038ccbba2914692b8ad0 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 21:35:37 -0500 Subject: [PATCH 09/39] chore(prod): re-disable hg38 [no ci] --- epivar-prod/portal.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epivar-prod/portal.env b/epivar-prod/portal.env index 8c26ad7b..164b4a0f 100644 --- a/epivar-prod/portal.env +++ b/epivar-prod/portal.env @@ -1 +1 @@ -EPIVAR_NODES=https://flu-infection.vhost38.genap.ca/aracena-hg19;https://flu-infection.vhost38.genap.ca/aracena-hg38 +EPIVAR_NODES=https://flu-infection.vhost38.genap.ca/aracena-hg19 From d0e6eb382b018e1cf5c4c6b1314c5e58c7a8ff91 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 22:09:55 -0500 Subject: [PATCH 10/39] chore(prod): link to main about page from node about pages [no ci] --- epivar-prod/node1/about.md | 2 +- epivar-prod/node2/about.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/epivar-prod/node1/about.md b/epivar-prod/node1/about.md index 993ed41a..f08555a5 100644 --- a/epivar-prod/node1/about.md +++ b/epivar-prod/node1/about.md @@ -24,7 +24,7 @@ All the datasets that have been generated as part of this project are summarized [made available here](#datasets). Access to some of the raw datasets require a formal data access request but all resources are available freely. Please cite the appropriate paper(s) if you use the data. -We have also built a [browser tool](https://flu-infection.vhost38.genap.ca/dataset/overview) that allows navigation of +We have also built a [browser tool](https://flu-infection.vhost38.genap.ca/about) that allows navigation of these rich datasets by condition, ancestry and, importantly, haplotype. This allows an in-depth exploration of the quantitative trait loci and other key results reported in the studies above. Using this tool requires a terms-of-use agreement to ensure data privacy conditions are respected. diff --git a/epivar-prod/node2/about.md b/epivar-prod/node2/about.md index a6455077..c00b0233 100644 --- a/epivar-prod/node2/about.md +++ b/epivar-prod/node2/about.md @@ -26,7 +26,7 @@ All the datasets that have been generated as part of this project are summarized [made available here](#datasets). Access to some of the raw datasets require a formal data access request but all resources are available freely. Please cite the appropriate paper(s) if you use the data. -We have also built a [browser tool](https://flu-infection.vhost38.genap.ca/dataset/overview) that allows navigation of +We have also built a [browser tool](https://flu-infection.vhost38.genap.ca/about) that allows navigation of these rich datasets by condition, ancestry and, importantly, haplotype. This allows an in-depth exploration of the quantitative trait loci and other key results reported in the studies above. Using this tool requires a terms-of-use agreement to ensure data privacy conditions are respected. From c880fc2ed95d09f8d0431aa3883a9e3a64155237 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 17 Jan 2024 22:47:29 -0500 Subject: [PATCH 11/39] feat(client): include node in page urls --- client/src/components/App.js | 41 ++++++++------- client/src/components/Controls.js | 11 ++-- client/src/components/Footer.js | 58 +++++++++++---------- client/src/components/Header.js | 6 +-- client/src/components/PeakResults.js | 10 ++-- client/src/components/pages/DatasetPage.js | 23 ++++++++ client/src/components/pages/OverviewPage.js | 7 +-- client/src/hooks.js | 5 ++ 8 files changed, 100 insertions(+), 61 deletions(-) create mode 100644 client/src/components/pages/DatasetPage.js diff --git a/client/src/components/App.js b/client/src/components/App.js index ad80e4a5..9ae2b269 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -14,13 +14,14 @@ import ProtectedPageContainer from "./pages/ProtectedPageContainer"; import DatasetAboutPage from "./pages/DatasetAboutPage"; import OverviewPage from "./pages/OverviewPage"; import ExplorePage from "./pages/ExplorePage"; -import DatasetsPage from "./pages/DatasetsPage"; +// import DatasetsPage from "./pages/DatasetsPage"; import FAQPage from "./pages/FAQPage"; import {setDevMode, saveUser, fetchDatasets, setNode, fetchUser, fetchMessages, fetchAssays} from "../actions"; import {SITE_SUBTITLE, SITE_TITLE} from "../constants/app"; import {EPIVAR_NODES} from "../config"; -import {useNode} from "../hooks"; +import {useNode, useUrlEncodedNode} from "../hooks"; +import DatasetPage from "./pages/DatasetPage"; const RoutedApp = () => { @@ -34,6 +35,9 @@ const RoutedApp = () => { const [contactModal, setContactModal] = useState(false); const [termsModal, setTermsModal] = useState(false); + const node = useNode(); + const urlEncodedNode = useUrlEncodedNode(); + const chrom = useSelector(state => state.ui.chrom); const position = useSelector(state => state.ui.position); @@ -44,14 +48,16 @@ const RoutedApp = () => { const navigateAbout = useCallback(() => navigate("/about"), [navigate]); const navigateDatasets = useCallback(() => navigate("/datasets"), [navigate]); // TODO: remember chrom and assay: - const navigateDatasetAbout = useCallback(() => navigate("/dataset/about"), [navigate]); - const navigateOverview = useCallback(() => navigate("/dataset/overview"), [navigate]); + const navigateDatasetAbout = useCallback(() => navigate(`/datasets/${urlEncodedNode}/about`), + [navigate, urlEncodedNode]); + const navigateOverview = useCallback(() => navigate(`/datasets/${urlEncodedNode}/overview`), + [navigate, urlEncodedNode]); const navigateExplore = useCallback(() => { - if (location.pathname.startsWith("/dataset/explore")) return; + if (location.pathname.startsWith(`/datasets/${urlEncodedNode}/explore`)) return; if (chrom && position) { - navigate(`/dataset/explore/locus/${chrom}/${position}`); + navigate(`/datasets/${urlEncodedNode}/explore/locus/${chrom}/${position}`); } else { - navigate("/dataset/explore"); + navigate(`/datasets/${urlEncodedNode}/explore`); } }, [location.pathname, chrom, position, navigate]); const navigateFAQ = () => navigate("/faq"); @@ -69,7 +75,6 @@ const RoutedApp = () => { dispatch(fetchDatasets()); }, [dispatch]); - const node = useNode(); const datasetsByNode = useSelector((state) => state.datasets.datasetsByNode); useEffect(() => { @@ -144,17 +149,15 @@ const App = () => ( }> } /> } /> - } /> - } /> - - - } /> - - - }> - } /> - } /> - } /> + {/*} />*/} + }> + } /> + } /> + }> + } /> + } /> + } /> + } /> } /> diff --git a/client/src/components/Controls.js b/client/src/components/Controls.js index 96cd7315..d159a3ae 100644 --- a/client/src/components/Controls.js +++ b/client/src/components/Controls.js @@ -21,6 +21,7 @@ import { fetchPositions, } from '../actions.js' import {useNavigate, useParams} from "react-router-dom"; +import {useDatasetIndex, useUrlEncodedNode} from "../hooks"; const defaultChrom = "rsID"; @@ -54,7 +55,7 @@ const Controls = ({toggleHelp}) => { const dispatch = useDispatch(); - const {chrom: paramsChrom, position: paramsPosition} = params; + const {node: urlEncodedNode, chrom: paramsChrom, position: paramsPosition} = params; const {isLoading, list} = positions; const [didFirstSearch, setDidFirstSearch] = useState(false); @@ -141,11 +142,11 @@ const Controls = ({toggleHelp}) => { // The item assay is the tab with the most significant result - which will be // selected first by nature of ordering, thus leading the user to the most interesting // detail from the autocomplete. - navigate(`/dataset/explore/locus/${chrom}/${position}/${item.assay}`, {replace: true}); + navigate(`/datasets/${urlEncodedNode}/explore/locus/${chrom}/${position}/${item.assay}`, {replace: true}); changePosition(position); dispatch(doSearch()); setDidFirstSearch(true); - }, [list, dispatch, navigate, changePosition]); + }, [urlEncodedNode, list, dispatch, navigate, changePosition]); const moveSelection = useCallback(n => { const {length} = list; @@ -192,10 +193,10 @@ const Controls = ({toggleHelp}) => { const onClickSearch = useCallback(() => { if (!chrom || !position) return; - navigate(`/dataset/explore/locus/${chrom}/${position}`, {replace: true}); + navigate(`/datasets/${urlEncodedNode}/explore/locus/${chrom}/${position}`, {replace: true}); dispatch(doSearch()); setDidFirstSearch(true); - }, [navigate, chrom, position]); + }, [navigate, urlEncodedNode, chrom, position]); return
diff --git a/client/src/components/Footer.js b/client/src/components/Footer.js index d5400d41..900be7bd 100644 --- a/client/src/components/Footer.js +++ b/client/src/components/Footer.js @@ -3,37 +3,41 @@ import {Container} from "reactstrap"; import {Link} from "react-router-dom"; import packageJson from "../../package.json"; +import {useUrlEncodedNode} from "../hooks"; -const Footer = ({/*onContact, */onTerms}) => ( - -
-
-
- - Canadian Centre for Computational Genomics - -
+const Footer = ({/*onContact, */onTerms}) => { + const urlEncodedNode = useUrlEncodedNode(); + return ( + +
+
+
+ + Canadian Centre for Computational Genomics + +
Developed by - C3G at McGill University © 2017-2023
- - Version {packageJson.version} •{" "} - source code -
+ C3G at McGill University © 2017-2024
+ + Version {packageJson.version} •{" "} + source code +
+
+
- -
- -); + + ); +} export default Footer; diff --git a/client/src/components/Header.js b/client/src/components/Header.js index 4bfc5bc3..6edf4250 100644 --- a/client/src/components/Header.js +++ b/client/src/components/Header.js @@ -86,16 +86,16 @@ export default function Header({children, onAbout, /*onDatasets, */onDatasetAbou