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

feat: 5673 cell guide tissue specific cell type route #6232

Merged
merged 3 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["<meta charset=\"utf-8\">","<meta id=\"newsletter-signup-meta\" name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">","<meta name=\"description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta name=\"next-head-count\" content=\"17\">","<meta name=\"theme-color\" content=\"#000000\">","<meta property=\"og:creator\" content=\"@cziscience\">","<meta property=\"og:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"og:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"og:site\" content=\"@cziscience\">","<meta property=\"og:site_name\" content=\"Cellxgene Data Portal\">","<meta property=\"og:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"og:type\" content=\"website\">","<meta property=\"og:url\" content=\"https://cellxgene.cziscience.com/\">","<meta property=\"title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"twitter:card\" content=\"summary\">","<meta property=\"twitter:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"twitter:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"twitter:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["<meta charset=\"utf-8\">","<meta id=\"newsletter-signup-meta\" name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">","<meta name=\"description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta name=\"next-head-count\" content=\"17\">","<meta name=\"theme-color\" content=\"#000000\">","<meta property=\"og:creator\" content=\"@cziscience\">","<meta property=\"og:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"og:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"og:site\" content=\"@cziscience\">","<meta property=\"og:site_name\" content=\"Cellxgene Data Portal\">","<meta property=\"og:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"og:type\" content=\"website\">","<meta property=\"og:url\" content=\"https://cellxgene.cziscience.com/\">","<meta property=\"title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">","<meta property=\"twitter:card\" content=\"summary\">","<meta property=\"twitter:description\" content=\"Find comprehensive information about brain specific &quot;neuron&quot; cell types (synonyms: nerve cell). Neurons: Essential cells in the nervous system responsible for receiving, processing, and transmitting information, enabling vital body functions.\">","<meta property=\"twitter:image\" content=\"https://cellxgene.cziscience.com/open-graph.jpg\">","<meta property=\"twitter:title\" content=\"Brain Specific Neuron Cell Types - CZ CELLxGENE CellGuide\">"]
3 changes: 3 additions & 0 deletions frontend/src/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ export enum ROUTES {
WMG_DOCS_DATA_PROCESSING = "/docs/04__Analyze%20Public%20Data/4_2__Gene%20Expression%20Documentation/4_2_3__Gene%20Expression%20Data%20Processing",
SITEMAP = "/sitemap",
CELL_GUIDE = "/cellguide",
CELL_GUIDE_CELL_TYPE = "/cellguide/:cellTypeId",
CELL_GUIDE_TISSUE = "/cellguide/tissues/:tissueId",
CELL_GUIDE_TISSUE_SPECIFIC_CELL_TYPE = "/cellguide/tissues/:tissueId/cell-types/:cellTypeId",
Comment on lines +19 to +21
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add routes for easier way to have a consistent way to create URL and find places that navigate to the same route

DEPLOYED_VERSION = "/api/deployed_version",
}
23 changes: 18 additions & 5 deletions frontend/src/common/queries/cellGuide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,20 +472,33 @@ export const fetchTissueMetadata =
};

/* ========== Lookup tables for organs ========== */
export function useAllOrgansLookupTables(): Map<string, string> {
const { data: allOrgansData } = useTissueMetadata();
export function useAllOrgansLookupTables(): {
data: Map<string, string>;
isSuccess: boolean;
} {
/**
* (thuang): Expose `isSuccess`, so `CellGuide/components/CellGuideCard/connect.ts`
* can use it to determine if the data is ready and determine if the user should
* be redirected to the tissue agnostic cell type page.
*/
const { data: allOrgansData, isSuccess } = useTissueMetadata();

return useMemo(() => {
if (!allOrgansData) {
return new Map<string, string>();
return { data: new Map<string, string>(), isSuccess };
}

const allOrgansLabelToIdMap = new Map<string, string>();
for (const organId in allOrgansData) {
const organData = allOrgansData[organId];
allOrgansLabelToIdMap.set(organData.name, organData.id);
}
return allOrgansLabelToIdMap;
}, [allOrgansData]);

return {
data: allOrgansLabelToIdMap,
isSuccess,
};
}, [allOrgansData, isSuccess]);
}

/* ========== Lookup tables for tissues ========== */
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/pages/cellguide/[cellTypeId].tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention is to use [cellTypeId].tsx to serve both CELL_GUIDE_CELL_TYPE and CELL_GUIDE_TISSUE_SPECIFIC_CELL_TYPE routes, so we have single source of truth and not duplicate code where possible

* IMPORTANT(thuang): `frontend/src/pages/cellguide/tissues/[tissueId]/cell-types/[cellTypeId].tsx`
* imports the exports from this file, since both routes use the same components.
* If we export more things from this file, we need to export them from the other file as well,
* to make sure that /cellguide/tissues/[tissueId]/cell-types/[cellTypeId] route continue
* to work.
*/

import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import CellGuideCard from "src/views/CellGuide/components/CellGuideCard";
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* IMPORTANT(thuang): Make sure the exports from this file is in sync with
* frontend/src/pages/cellguide/[cellTypeId].tsx
*/

import Page, { getServerSideProps } from "src/pages/cellguide/[cellTypeId]";

export default Page;
export { getServerSideProps };
29 changes: 29 additions & 0 deletions frontend/src/views/CellGuide/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,32 @@ export function filterDescendantsOfAncestorTissueId(

return tissueIdList.filter((value) => descendantSet.has(value));
}

import { ROUTES } from "src/common/constants/routes";
import { NO_ORGAN_ID } from "src/views/CellGuide/components/CellGuideCard/components/MarkerGeneTables/constants";

export function getCellTypeLink({
/**
* (thuang): `queryTissueId` can be undefined when a user is on `/cellguide/:cellTypeId` route
* instead of `/cellguide/tissues/:tissueId/cell-types/:cellTypeId` route.
*
* NOTE: `tissue` and `organ` in variable names are interchangeable here.
*/
tissueId = NO_ORGAN_ID,
cellTypeId,
}: {
tissueId: string;
cellTypeId: string;
}) {
const urlCellTypeId = cellTypeId.replace(":", "_") ?? "";
const urlTissueId = tissueId.replace(":", "_") || NO_ORGAN_ID;

if (tissueId === NO_ORGAN_ID) {
return ROUTES.CELL_GUIDE_CELL_TYPE.replace(":cellTypeId", urlCellTypeId);
} else {
return ROUTES.CELL_GUIDE_TISSUE_SPECIFIC_CELL_TYPE.replace(
":tissueId",
urlTissueId
).replace(":cellTypeId", urlCellTypeId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import { ALL_TISSUES, HOMO_SAPIENS, NO_ORGAN_ID } from "../constants";
export function useOrganAndOrganismFilterListForCellType(cellTypeId: string): {
organsMap: Map<string, string>;
organismsList: string[];
isSuccess: boolean;
} {
const { data: computationalMarkers } = useComputationalMarkers(cellTypeId);
const {
data: computationalMarkers,
isSuccess: isComputationalMarkersSuccess,
} = useComputationalMarkers(cellTypeId);

const organLabelToIdMap = useAllOrgansLookupTables();
const { data: organLabelToIdMap, isSuccess } = useAllOrgansLookupTables();

// eslint-disable-next-line sonarjs/cognitive-complexity
return useMemo(() => {
Expand Down Expand Up @@ -66,6 +70,17 @@ export function useOrganAndOrganismFilterListForCellType(cellTypeId: string): {
return {
organsMap: sortedFilteredOrganMap,
organismsList: sortedOrganismList,
/**
* (thuang): Expose `isSuccess`, so `CellGuide/components/CellGuideCard/connect.ts`
* can use it to determine if the data is ready and determine if the user should
* be redirected to the tissue agnostic cell type page.
*/
isSuccess: isComputationalMarkersSuccess && isSuccess,
};
}, [computationalMarkers, organLabelToIdMap]);
}, [
computationalMarkers,
organLabelToIdMap,
isSuccess,
isComputationalMarkersSuccess,
]);
}
166 changes: 166 additions & 0 deletions frontend/src/views/CellGuide/components/CellGuideCard/connect.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracts out stuff to connect.ts

Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { DefaultDropdownMenuOption } from "@czi-sds/components";
import { throttle } from "lodash";
import { useRouter } from "next/router";
import { useState, useRef, useCallback, useMemo, useEffect } from "react";
import { ROUTES } from "src/common/constants/routes";
import {
ALL_TISSUES,
NO_ORGAN_ID,
TISSUE_AGNOSTIC,
} from "src/views/CellGuide/components/CellGuideCard/components/MarkerGeneTables/constants";
import { useOrganAndOrganismFilterListForCellType } from "src/views/CellGuide/components/CellGuideCard/components/MarkerGeneTables/hooks/common";
import { SKINNY_MODE_BREAKPOINT_WIDTH } from "src/views/CellGuide/components/CellGuideCard/constants";
import { LEFT_RIGHT_PADDING_PX_XXL } from "src/views/CellGuide/components/CellGuideCard/style";
import { SDSOrgan } from "src/views/CellGuide/components/CellGuideCard/types";
import { CellType } from "src/views/CellGuide/components/common/OntologyDagView/common/types";
import { Gene } from "src/views/WheresMyGeneV2/common/types";

export function useConnect() {
const router = useRouter();

const [pageNavIsOpen, setPageNavIsOpen] = useState(false);
const [selectedGene, setSelectedGene] = useState<string | undefined>(
undefined
);

const [skinnyMode, setSkinnyMode] = useState<boolean>(false);

// Navigation
const sectionRef0 = useRef<HTMLDivElement>(null);
const sectionRef1 = useRef<HTMLDivElement>(null);
const sectionRef2 = useRef<HTMLDivElement>(null);
const sectionRef3 = useRef<HTMLDivElement>(null);

const selectGene = useCallback(
(gene: string) => {
if (gene === selectedGene) {
setSelectedGene(undefined);
} else {
setSelectedGene(gene);
if (sectionRef1.current) {
window.scrollTo({
top:
sectionRef1.current.getBoundingClientRect().top +
window.scrollY -
50,
behavior: "smooth",
});
}
}
},
[selectedGene]
);

// Set the mobile tooltip view content
const [tooltipContent, setTooltipContent] = useState<{
title: string;
element: JSX.Element;
} | null>(null);

const { cellTypeId: queryCellTypeId, tissueId: queryTissueId } = router.query;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we get tissueId from the router now, I've removed the states that used to track selectedOrgan and selectedOrganId

const cellTypeId = (queryCellTypeId as string)?.replace("_", ":") ?? "";

/**
* (thuang): `queryTissueId` can be undefined when a user is on `/cellguide/:cellTypeId` route
* instead of `/cellguide/tissues/:tissueId/cell-types/:cellTypeId` route.
*
* NOTE: `tissue` and `organ` in variable names are interchangeable here.
*/
const tissueId = (queryTissueId as string)?.replace("_", ":") || NO_ORGAN_ID;

const handleResize = useCallback(() => {
setSkinnyMode(
window.innerWidth <
SKINNY_MODE_BREAKPOINT_WIDTH + 2 * LEFT_RIGHT_PADDING_PX_XXL
);
}, [setSkinnyMode]);

const throttledHandleResize = useMemo(() => {
return throttle(handleResize, 100);
}, [handleResize]);

useEffect(() => {
throttledHandleResize();
window.addEventListener("resize", throttledHandleResize);

return () => window.removeEventListener("resize", throttledHandleResize);
}, [throttledHandleResize]);

const [geneInfoGene, setGeneInfoGene] = useState<Gene["name"] | null>(null);
const [cellInfoCellType, setCellInfoCellType] = useState<CellType | null>(
null
);

const { organismsList, organsMap, isSuccess } =
useOrganAndOrganismFilterListForCellType(cellTypeId);

const sdsOrganismsList = useMemo(
() =>
organismsList.map((organism) => ({
name: organism,
})),
[organismsList]
);

const sdsOrgansList = useMemo<SDSOrgan[]>(
() =>
Array.from(organsMap).map(([name, id]) => ({
name: name === ALL_TISSUES ? TISSUE_AGNOSTIC : name,
id,
})),
[organsMap]
);

const selectedOrgan = sdsOrgansList.find((organ) => organ.id === tissueId);

/**
* (thuang): Push the user to tissue agnostic cell type page if the user is on
* a tissue-specific cell type page and the tissue is not in the filter list.
*/
useEffect(() => {
if (isSuccess && tissueId !== NO_ORGAN_ID && !selectedOrgan) {
router.replace(
ROUTES.CELL_GUIDE_CELL_TYPE.replace(
":cellTypeId",
queryCellTypeId as string
)
);
}
}, [queryCellTypeId, router, selectedOrgan, tissueId, isSuccess]);

const [selectedOrganism, setSelectedOrganism] =
useState<DefaultDropdownMenuOption>(sdsOrganismsList[0]);

useEffect(() => {
setSelectedGene(undefined);
}, [selectedOrgan, selectedOrganism, setSelectedGene]);

return {
router,
pageNavIsOpen,
setPageNavIsOpen,
selectedGene,
sectionRef0,
sectionRef1,
sectionRef2,
sectionRef3,
skinnyMode,
selectGene,
tooltipContent,
setTooltipContent,
queryCellTypeId,
cellTypeId,
geneInfoGene,
setGeneInfoGene,
cellInfoCellType,
setCellInfoCellType,
organismsList,
organsMap,
sdsOrganismsList,
sdsOrgansList,
selectedOrgan,
selectedOrganId: tissueId,
selectedOrganism,
setSelectedOrganism,
};
}
Loading
Loading