From 543642f3b9f3bc868ff73b1c8bc5bed98065217d Mon Sep 17 00:00:00 2001 From: kaloster Date: Thu, 18 Jul 2024 17:05:27 -0400 Subject: [PATCH 01/24] feat: cell type info and info panel refactor --- client/src/actions/index.ts | 16 ++ client/src/common/types/entities.ts | 27 ++++ .../components/categorical/value/index.tsx | 53 ++++++- client/src/components/diffexNotice/index.tsx | 2 +- client/src/components/geneExpression/gene.tsx | 12 +- .../infoPanel/cellTypeInfo/index.tsx | 36 +++++ .../infoPanel/cellTypeInfo/types.ts | 9 ++ .../infoPanel/common/containerInfo/index.tsx | 60 ++++++++ .../infoPanel/common/loadingInfo/index.tsx | 137 ++++++++++++++++++ .../infoPanel/{geneInfo => common}/style.ts | 10 +- .../geneExpression/infoPanel/common/types.ts | 16 ++ .../infoPanel/geneInfo/connect.ts | 11 -- .../infoPanel/geneInfo/index.tsx | 119 +++------------ .../infoPanel/geneInfo/types.ts | 16 +- .../geneExpression/infoPanel/index.tsx | 8 +- client/src/reducers/controls.ts | 135 ++++++++++------- server/app/api/v3.py | 7 + server/common/constants.py | 2 + server/common/rest.py | 40 +++++ server/common/utils/cell_type_info.py | 21 +++ 20 files changed, 548 insertions(+), 189 deletions(-) create mode 100644 client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx create mode 100644 client/src/components/geneExpression/infoPanel/cellTypeInfo/types.ts create mode 100644 client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx create mode 100644 client/src/components/geneExpression/infoPanel/common/loadingInfo/index.tsx rename client/src/components/geneExpression/infoPanel/{geneInfo => common}/style.ts (92%) create mode 100644 client/src/components/geneExpression/infoPanel/common/types.ts delete mode 100644 client/src/components/geneExpression/infoPanel/geneInfo/connect.ts create mode 100644 server/common/utils/cell_type_info.py diff --git a/client/src/actions/index.ts b/client/src/actions/index.ts index f1cf25a70..078b4227e 100644 --- a/client/src/actions/index.ts +++ b/client/src/actions/index.ts @@ -179,6 +179,21 @@ async function fetchGeneInfo( return response; } +interface CellTypeInfoAPI { + name: string; + summary: string; +} +/** + * Fetch cell type summary information + * @param cell human-readable name of gene + */ +async function fetchCellTypeInfo( + cell: string +): Promise { + const response = await fetchJson(`cellinfo?cell=${cell}`); + return response; +} + function prefetchEmbeddings(annoMatrix: AnnoMatrix) { /* prefetch requests for all embeddings @@ -539,6 +554,7 @@ export default { checkExplainNewTab, navigateCheckUserState, selectDataset, + fetchCellTypeInfo, fetchGeneInfo, selectContinuousMetadataAction: selnActions.selectContinuousMetadataAction, selectCategoricalMetadataAction: selnActions.selectCategoricalMetadataAction, diff --git a/client/src/common/types/entities.ts b/client/src/common/types/entities.ts index 379af6932..94ae07094 100644 --- a/client/src/common/types/entities.ts +++ b/client/src/common/types/entities.ts @@ -143,3 +143,30 @@ export interface PublisherMetadata { published_month: number; published_year: number; } + +export interface CellInfo { + cellId: string; + cellName: string; + cellDescription: string; + synonyms: string[]; + references: string[]; + error: string | null; + loading: boolean; +} + +export interface GeneInfo { + gene: string | null; + geneName: string; + geneSummary: string; + geneSynonyms: string[]; + geneUrl: string; + showWarningBanner: boolean; + infoError: string | null; + loading: boolean; +} + +export enum ActiveTab { + Gene = "Gene", + Dataset = "Dataset", + CellType = "CellType", +} diff --git a/client/src/components/categorical/value/index.tsx b/client/src/components/categorical/value/index.tsx index b760daafc..b03a02e35 100644 --- a/client/src/components/categorical/value/index.tsx +++ b/client/src/components/categorical/value/index.tsx @@ -1,8 +1,9 @@ import { connect } from "react-redux"; import React from "react"; import * as d3 from "d3"; +import { Icon as InfoCircle, IconButton } from "czifui"; -import { Classes } from "@blueprintjs/core"; +import { AnchorButton, Classes } from "@blueprintjs/core"; import * as globals from "../../../globals"; // @ts-expect-error ts-migrate(2307) FIXME: Cannot find module '../categorical.css' or its cor... Remove this comment to see the full error message import styles from "../categorical.css"; @@ -21,6 +22,7 @@ import { Schema, Category } from "../../../common/types/schema"; import { isDataframeDictEncodedColumn } from "../../../util/dataframe/types"; import { CategorySummary } from "../../../util/stateManager/controlsHelpers"; import { ColorTable } from "../../../util/stateManager/colorHelpers"; +import { ActiveTab } from "../../../common/types/entities"; const STACKED_BAR_HEIGHT = 11; const STACKED_BAR_WIDTH = 100; @@ -356,6 +358,32 @@ class CategoryValue extends React.Component { return null; }; + handleDisplayCellTypeInfo = async (cellName: string): Promise => { + const { dispatch } = this.props; + + track(EVENTS.EXPLORER_GENE_INFO_BUTTON_CLICKED, { + cellName, + }); + + dispatch({ type: "request cell info start", cellName }); + + dispatch({ + type: "toggle active info panel", + activeTab: ActiveTab.CellType, + }); + + const info = await actions.fetchCellTypeInfo(cellName); + + if (!info) { + return; + } + + dispatch({ + type: "open cell info", + cellInfo: info, + }); + }; + // If coloring by and this isn't the colorAccessor and it isn't being edited shouldRenderStackedBarOrHistogram() { const { colorAccessor, isColorBy } = this.props; @@ -545,6 +573,7 @@ class CategoryValue extends React.Component { CHART_MARGIN : globals.leftSidebarWidth - otherElementsWidth; + const isCellInfo = metadataField === "CellType"; return (
{ {displayString} + this.handleDisplayCellTypeInfo(displayString)} + > + +
+ +
+
+
{this.renderMiniStackedBar()} diff --git a/client/src/components/diffexNotice/index.tsx b/client/src/components/diffexNotice/index.tsx index 2fc590628..e2cac2924 100644 --- a/client/src/components/diffexNotice/index.tsx +++ b/client/src/components/diffexNotice/index.tsx @@ -1,7 +1,7 @@ /* Core dependencies */ import React, { useState, useEffect } from "react"; import { noop } from "lodash"; -import { Link } from "../geneExpression/infoPanel/geneInfo/style"; +import { Link } from "../geneExpression/infoPanel/common/style"; import { StyledSnackbar, StyledAlert } from "./style"; interface Props { diff --git a/client/src/components/geneExpression/gene.tsx b/client/src/components/geneExpression/gene.tsx index 17fc5bb7c..61a122984 100644 --- a/client/src/components/geneExpression/gene.tsx +++ b/client/src/components/geneExpression/gene.tsx @@ -11,7 +11,7 @@ import actions from "../../actions"; import { track } from "../../analytics"; import { EVENTS } from "../../analytics/events"; -import { ActiveTab } from "../../reducers/controls"; +import { ActiveTab } from "../../common/types/entities"; import { DataframeValue } from "../../util/dataframe"; const MINI_HISTOGRAM_WIDTH = 110; @@ -94,17 +94,15 @@ class Gene extends React.Component { gene, }); - dispatch({ - type: "load gene info", - gene, - }); - + dispatch({ type: "request gene info start", gene }); dispatch({ type: "toggle active info panel", activeTab: ActiveTab.Gene }); const info = await actions.fetchGeneInfo(geneId, gene); + if (!info) { return; } + dispatch({ type: "open gene info", gene, @@ -293,6 +291,6 @@ function mapStateToProps(state: RootState, ownProps: OwnProps): StateProps { state.colors.colorMode !== "color by categorical metadata", isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === gene, isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === gene, - isGeneInfo: state.controls.gene === gene, + isGeneInfo: state.controls.geneInfo.gene === gene, }; } diff --git a/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx b/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx new file mode 100644 index 000000000..6eff39c81 --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { connect } from "react-redux"; +import { Props, mapStateToProps } from "./types"; +import ContainerInfo from "../common/containerInfo"; + +const CELLGUIDE_URL = "https://cellxgene.cziscience.com/cellguide/"; + +function CellTypeInfo(props: Props) { + const { cellInfo } = props; + + const { + cellId, + cellName, + cellDescription, + synonyms, + references, + error, + loading, + } = cellInfo; + + return ( + + ); +} + +export default connect(mapStateToProps)(CellTypeInfo); diff --git a/client/src/components/geneExpression/infoPanel/cellTypeInfo/types.ts b/client/src/components/geneExpression/infoPanel/cellTypeInfo/types.ts new file mode 100644 index 000000000..f6c5cbeb9 --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/cellTypeInfo/types.ts @@ -0,0 +1,9 @@ +import { RootState } from "../../../../reducers"; + +export interface Props { + cellInfo: RootState["controls"]["cellInfo"]; +} + +export const mapStateToProps = (state: RootState): Props => ({ + cellInfo: state.controls.cellInfo, +}); diff --git a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx b/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx new file mode 100644 index 000000000..33e47fc0f --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { kebabCase } from "lodash"; +import { InfoContainer, InfoWrapper } from "../style"; +import { ErrorInfo, LoadingInfo, NoneSelected, ShowInfo } from "../loadingInfo"; +import { ExtendedInfoProps } from "../types"; + +function ContainerInfo(props: ExtendedInfoProps) { + const { + id, + name, + symbol, + description, + synonyms, + references, + error, + loading, + entity, + url, + showWarningBanner = false, + } = props; + + const entityTag = kebabCase(entity); + + return ( + + + {/* Loading */} + {name && !error && loading && ( + + )} + + {/* None Selected */} + {name === "" && error === null && } + + {/* Error */} + {error && } + + {/* Show Info */} + {name && !error && !loading && ( + + )} + + + ); +} + +export default ContainerInfo; diff --git a/client/src/components/geneExpression/infoPanel/common/loadingInfo/index.tsx b/client/src/components/geneExpression/infoPanel/common/loadingInfo/index.tsx new file mode 100644 index 000000000..e0184f767 --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/common/loadingInfo/index.tsx @@ -0,0 +1,137 @@ +import React from "react"; +import { kebabCase } from "lodash"; +import { Icon } from "czifui"; + +import { + Content, + CustomIcon, + InfoDiv, + InfoSymbol, + Link, + MessageDiv, + NoGeneSelectedDiv, + SynHeader, + Synonyms, + WarningBanner, +} from "../style"; +import { BaseInfoProps, ExtendedInfoProps } from "../types"; + +export function LoadingInfo(props: BaseInfoProps) { + const { name, entity } = props; + return ( + + {name} + Loading {entity}... + + ); +} + +export function NoneSelected({ entity }: { entity: BaseInfoProps["entity"] }) { + return ( + + + + No {entity} Selected + + Choose a gene above or search the Cell Guide database. + + + + ); +} + +export function ShowWarningBanner() { + return ( + + + NCBI didn't return an exact match for this gene. + + ); +} + +export function ErrorInfo(props: BaseInfoProps) { + const { name, entity } = props; + return ( + + {name} + Sorry, this {entity} could not be found. + + Search on Google + + + ); +} + +export function ShowInfo(props: ExtendedInfoProps) { + const { + name, + entity, + description, + id, + synonyms, + references, + url, + symbol, + showWarningBanner, + } = props; + const externalUrl = id ? url + id : url; + const entityTag = kebabCase(entity); + + return ( + + {showWarningBanner ?? } +
+ {symbol ?? name} + +
+ {entity === "Gene" && ( + {name} + )} + +

{description}

+ {entity === "Cell Type" ?? ( +

+ Ontology ID: + + {id} + +

+ )} + {synonyms.length > 0 && ( +

+ Synonyms + + {synonyms.map((syn) => ( + {syn}, + ))} + +

+ )} + {entity === "Cell Type" && references && references?.length > 0 && ( +

+ References + {references.map((ref, index) => ( + + [{index + 1}] + + ))} +

+ )} +
+
+ ); +} diff --git a/client/src/components/geneExpression/infoPanel/geneInfo/style.ts b/client/src/components/geneExpression/infoPanel/common/style.ts similarity index 92% rename from client/src/components/geneExpression/infoPanel/geneInfo/style.ts rename to client/src/components/geneExpression/infoPanel/common/style.ts index f72b580dc..65dc4430d 100644 --- a/client/src/components/geneExpression/infoPanel/geneInfo/style.ts +++ b/client/src/components/geneExpression/infoPanel/common/style.ts @@ -13,22 +13,22 @@ import * as globals from "../../../../globals"; import * as styles from "../../util"; import { gray100, gray500, spacesM } from "../../../theme"; -export const GeneInfoWrapper = styled.div` +export const InfoWrapper = styled.div` display: flex; bottom: ${globals.bottomToolbarGutter}px; left: ${globals.leftSidebarWidth + globals.scatterplotMarginLeft}px; `; -export const GeneInfoContainer = styled.div` +export const InfoContainer = styled.div` width: ${styles.width + styles.margin.left + styles.margin.right}px; height: "50%"; `; -export const GeneInfoEmpty = styled.div` +export const InfoDiv = styled.div` margin: ${spacesM}px; `; -export const GeneSymbol = styled.h1` +export const InfoSymbol = styled.h1` color: black; ${fontHeaderL} ${(props) => { @@ -40,7 +40,7 @@ export const GeneSymbol = styled.h1` }} `; -export const Content = styled.p` +export const Content = styled.div` font-weight: 500; color: black; ${fontBodyXs} diff --git a/client/src/components/geneExpression/infoPanel/common/types.ts b/client/src/components/geneExpression/infoPanel/common/types.ts new file mode 100644 index 000000000..9ed9c47d4 --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/common/types.ts @@ -0,0 +1,16 @@ +export interface BaseInfoProps { + name: string; + entity: "Gene" | "Cell Type"; +} + +export interface ExtendedInfoProps extends BaseInfoProps { + description: string; + id: string | null; + synonyms: string[]; + references: string[]; + url: string; + symbol?: string; + showWarningBanner?: boolean; + error?: string | null; + loading?: boolean; +} diff --git a/client/src/components/geneExpression/infoPanel/geneInfo/connect.ts b/client/src/components/geneExpression/infoPanel/geneInfo/connect.ts deleted file mode 100644 index 2554dcd92..000000000 --- a/client/src/components/geneExpression/infoPanel/geneInfo/connect.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function useConnect({ geneSynonyms }: { geneSynonyms: string[] }) { - let synonymList; - if (geneSynonyms.length > 1) { - synonymList = geneSynonyms.join(", "); - } else if (geneSynonyms.length === 1) { - synonymList = geneSynonyms[0]; - } else { - synonymList = null; - } - return { synonymList }; -} diff --git a/client/src/components/geneExpression/infoPanel/geneInfo/index.tsx b/client/src/components/geneExpression/infoPanel/geneInfo/index.tsx index 33051ab07..d4627ffaa 100644 --- a/client/src/components/geneExpression/infoPanel/geneInfo/index.tsx +++ b/client/src/components/geneExpression/infoPanel/geneInfo/index.tsx @@ -1,115 +1,36 @@ import React from "react"; import { connect } from "react-redux"; -import { Icon } from "czifui"; -import { - SynHeader, - Synonyms, - Link, - Content, - GeneSymbol, - GeneInfoContainer, - GeneInfoEmpty, - GeneInfoWrapper, - WarningBanner, - NoGeneSelectedDiv, - MessageDiv, - CustomIcon, -} from "./style"; import { Props, mapStateToProps } from "./types"; -import { useConnect } from "./connect"; +import ContainerInfo from "../common/containerInfo"; function GeneInfo(props: Props) { + const { geneInfo } = props; + const { - geneSummary, geneName, + geneSummary, gene, geneUrl, geneSynonyms, - infoError, showWarningBanner, - } = props; - - const { synonymList } = useConnect({ geneSynonyms }); + infoError, + loading, + } = geneInfo; return ( - - - {geneName === "" && infoError === null && gene !== null ? ( - - {gene} - loading... - - ) : null} - {gene === null && infoError === null ? ( - - - - - No Gene Selected - - Choose a gene above or search the NCBI database. - - - - ) : null} - {infoError !== null ? ( - - {gene} - Sorry, this gene could not be found on NCBI. - - Search on Google - - - ) : null} - {geneName !== "" && infoError === null ? ( - - {showWarningBanner ? ( - - - - NCBI didn't return an exact match for this gene. - - - ) : null} - {gene} - {geneName} - {geneSummary === "" ? ( - - This gene does not currently have a summary in NCBI. - - ) : ( - {geneSummary} - )} - {synonymList ? ( -

- Synonyms - - {synonymList} - -

- ) : null} - {geneUrl !== "" ? ( - - View on NCBI - - ) : null} -
- ) : null} -
-
+ ); } diff --git a/client/src/components/geneExpression/infoPanel/geneInfo/types.ts b/client/src/components/geneExpression/infoPanel/geneInfo/types.ts index b23705c1b..b14e6de39 100644 --- a/client/src/components/geneExpression/infoPanel/geneInfo/types.ts +++ b/client/src/components/geneExpression/infoPanel/geneInfo/types.ts @@ -1,21 +1,9 @@ import { RootState } from "../../../../reducers"; export interface Props { - geneSummary: RootState["controls"]["geneSummary"]; - geneName: RootState["controls"]["geneName"]; - gene: RootState["controls"]["gene"]; - geneUrl: RootState["controls"]["geneUrl"]; - geneSynonyms: RootState["controls"]["geneSynonyms"]; - showWarningBanner: RootState["controls"]["showWarningBanner"]; - infoError: RootState["controls"]["infoError"]; + geneInfo: RootState["controls"]["geneInfo"]; } export const mapStateToProps = (state: RootState): Props => ({ - geneSummary: state.controls.geneSummary, - geneName: state.controls.geneName, - gene: state.controls.gene, - geneUrl: state.controls.geneUrl, - geneSynonyms: state.controls.geneSynonyms, - showWarningBanner: state.controls.showWarningBanner, - infoError: state.controls.infoError, + geneInfo: state.controls.geneInfo, }); diff --git a/client/src/components/geneExpression/infoPanel/index.tsx b/client/src/components/geneExpression/infoPanel/index.tsx index 0cbe6cc6c..e8db91198 100644 --- a/client/src/components/geneExpression/infoPanel/index.tsx +++ b/client/src/components/geneExpression/infoPanel/index.tsx @@ -9,6 +9,7 @@ import { InfoPanelHeader, InfoPanelWrapper, } from "./style"; +import CellTypeInfo from "./cellTypeInfo"; import GeneInfo from "./geneInfo"; import DatasetInfo from "./datasetInfo"; import { Props, mapStateToProps } from "./types"; @@ -24,12 +25,13 @@ function InfoPanel(props: Props) { setValue(tabsValue as number); dispatch({ type: "toggle active info panel", - activeTab: tabsValue === 0 ? "Gene" : "Dataset", + activeTab: + tabsValue === 0 ? "Gene" : tabsValue === 1 ? "CellType" : "Dataset", }); }; useEffect(() => { - setValue(activeTab === "Gene" ? 0 : 1); + setValue(activeTab === "Gene" ? 0 : activeTab === "CellType" ? 1 : 2); }, [activeTab]); return ( @@ -43,6 +45,7 @@ function InfoPanel(props: Props) { > + @@ -84,6 +87,7 @@ function InfoPanel(props: Props) { isMinimized={infoPanelMinimized} > {activeTab === "Gene" && } + {activeTab === "CellType" && } {activeTab === "Dataset" && } diff --git a/client/src/reducers/controls.ts b/client/src/reducers/controls.ts index 2ab89f5da..ed97ed5ec 100644 --- a/client/src/reducers/controls.ts +++ b/client/src/reducers/controls.ts @@ -1,10 +1,11 @@ import { AnyAction } from "redux"; -import { DatasetUnsMetadata } from "../common/types/entities"; +import { + ActiveTab, + CellInfo, + DatasetUnsMetadata, + GeneInfo, +} from "../common/types/entities"; -export enum ActiveTab { - Gene = "Gene", - Dataset = "Dataset", -} interface ControlsState { loading: boolean; error: Error | string | null; @@ -14,26 +15,21 @@ interface ControlsState { scatterplotYYaccessor: string | false; scatterplotIsMinimized: boolean; scatterplotIsOpen: boolean; - gene: string | null; - infoError: string | null; graphRenderCounter: number; colorLoading: boolean; datasetDrawer: boolean; - geneUrl: string; - geneSummary: string; - geneName: string; - geneSynonyms: string[]; isCellGuideCxg: boolean; screenCap: boolean; mountCapture: boolean; - showWarningBanner: boolean; imageUnderlay: boolean; activeTab: ActiveTab; infoPanelHidden: boolean; infoPanelMinimized: boolean; - unsMetadata: DatasetUnsMetadata; imageOpacity: number; dotOpacity: number; + geneInfo: GeneInfo; + unsMetadata: DatasetUnsMetadata; + cellInfo: CellInfo; } const Controls = ( state: ControlsState = { @@ -46,24 +42,19 @@ const Controls = ( scatterplotXXaccessor: false, // just easier to read scatterplotYYaccessor: false, scatterplotIsMinimized: false, - geneUrl: "", - geneSummary: "", - geneSynonyms: [""], - geneName: "", scatterplotIsOpen: false, - gene: null, - infoError: null, graphRenderCounter: 0 /* integer as { @@ -145,33 +153,6 @@ const Controls = ( graphRenderCounter: c, }; } - - /******************************* - Gene Info - *******************************/ - case "open gene info": { - return { - ...state, - gene: action.gene, - geneUrl: action.url, - geneSummary: action.summary, - geneSynonyms: action.synonyms, - geneName: action.name, - infoError: action.infoError, - showWarningBanner: action.showWarningBanner, - }; - } - case "load gene info": { - return { - ...state, - gene: action.gene, - geneUrl: "", - geneSummary: "", - geneSynonyms: [""], - geneName: "", - infoError: null, - }; - } /******************************* Scatterplot *******************************/ @@ -241,6 +222,62 @@ const Controls = ( infoPanelMinimized: !state.infoPanelMinimized, }; } + /************************** + Cell Info + **************************/ + case "request cell info start": { + return { + ...state, + cellInfo: { + ...state.cellInfo, + cellName: action.cellName, + loading: true, + }, + }; + } + case "open cell info": { + return { + ...state, + cellInfo: { + ...state.cellInfo, + cellId: action.cellInfo.cell_id, + cellDescription: action.cellInfo.description, + synonyms: action.cellInfo.synonyms, + references: action.cellInfo.references, + error: action.cellInfo.error, + loading: false, + }, + }; + } + /******************************* + Gene Info + *******************************/ + case "request gene info start": { + return { + ...state, + geneInfo: { + ...state.geneInfo, + gene: action.gene, + loading: true, + }, + }; + } + case "open gene info": { + return { + ...state, + geneInfo: { + gene: action.gene, + geneName: action.name ?? state.geneInfo.geneName, + geneSummary: action.summary ?? state.geneInfo.geneSummary, + geneSynonyms: action.synonyms ?? state.geneInfo.geneSynonyms, + geneUrl: action.url ?? state.geneInfo.geneUrl, + showWarningBanner: + action.showWarningBanner ?? state.geneInfo.showWarningBanner, + infoError: action.infoError ?? state.geneInfo.infoError, + loading: false, + }, + }; + } /************************** Screen Capture **************************/ diff --git a/server/app/api/v3.py b/server/app/api/v3.py index ac3852618..6d49cdc57 100644 --- a/server/app/api/v3.py +++ b/server/app/api/v3.py @@ -155,6 +155,12 @@ def get(self, data_adaptor): return common_rest.gene_info_get(request) +class CellTypeInfoAPI(S3URIResource): + @rest_get_s3uri_data_adaptor + def get(self, data_adaptor): + return common_rest.cell_type_info_get(request) + + class VersionAPI(Resource): def get(self): return common_rest.get_deployed_version(request) @@ -226,6 +232,7 @@ def add_resource(resource, url): add_resource(AnnotationsObsAPI, "/annotations/obs") add_resource(AnnotationsVarAPI, "/annotations/var") add_resource(DataVarAPI, "/data/var") + add_resource(CellTypeInfoAPI, "/cellinfo") add_resource(GeneInfoAPI, "/geneinfo") add_resource(SummarizeVarAPI, "/summarize/var") # Display routes diff --git a/server/common/constants.py b/server/common/constants.py index ebbaf8fc8..6b0ef147a 100644 --- a/server/common/constants.py +++ b/server/common/constants.py @@ -35,3 +35,5 @@ class XApproximateDistribution(AugmentedEnum): MAX_LAYOUTS = 30 CELLGUIDE_CXG_KEY_NAME = "cellguide-cxgs" + +CELLGUIDE_BASE_URL = "https://cellguide.cellxgene.cziscience.com" diff --git a/server/common/rest.py b/server/common/rest.py index 0c7cb773b..d8986f209 100644 --- a/server/common/rest.py +++ b/server/common/rest.py @@ -31,6 +31,11 @@ TombstoneError, UnsupportedSummaryMethod, ) +from server.common.utils.cell_type_info import ( + get_cell_description, + get_celltype_metadata, + get_latest_snapshot_identifier, +) from server.common.utils.uns import spatial_metadata_get from server.dataset import dataset_metadata @@ -301,6 +306,41 @@ def gene_info_get(request): return abort_and_log(HTTPStatus.BAD_REQUEST, str(e), include_exc_info=True) +def cell_type_info_get(request): + """ + Request information about cell type from cell guide + """ + try: + latest_snapshot_identifier = get_latest_snapshot_identifier() + celltype_metadata = get_celltype_metadata(latest_snapshot_identifier) + + cell_name = request.args.get("cell") + if not cell_name: + return make_response(jsonify({"error": "Cell name is required"}), HTTPStatus.BAD_REQUEST) + + cell_info = next((info for _, info in celltype_metadata.items() if info.get("name") == cell_name), None) + if not cell_info: + return make_response(jsonify({"error": "Cell type not found"}), HTTPStatus.OK) + + cell_id = cell_info.get("id", "") + synonyms = cell_info.get("synonyms", []) + + cell_description = {} + + try: + cell_description = get_cell_description(cell_id.replace(":", "_")) + except Exception as e: + cell_description["description"] = cell_info.get("clDescription", "") + + cell_description["cell_id"] = cell_id + cell_description["cell_name"] = cell_name + cell_description["synonyms"] = synonyms + + return make_response(jsonify(cell_description), HTTPStatus.OK) + except Exception as e: + raise e + + def get_deployed_version(request): """ Returns the deployed version diff --git a/server/common/utils/cell_type_info.py b/server/common/utils/cell_type_info.py new file mode 100644 index 000000000..d8ed8621d --- /dev/null +++ b/server/common/utils/cell_type_info.py @@ -0,0 +1,21 @@ +import requests + +from server.common.constants import CELLGUIDE_BASE_URL + + +def get_latest_snapshot_identifier() -> str: + response = requests.get(url=f"{CELLGUIDE_BASE_URL}/latest_snapshot_identifier") + response.raise_for_status() + return response.text.strip() + + +def get_celltype_metadata(snapshot_identifier: str) -> dict: + response = requests.get(url=f"{CELLGUIDE_BASE_URL}/{snapshot_identifier}/celltype_metadata.json") + response.raise_for_status() + return response.json() + + +def get_cell_description(cell_id: str) -> dict: + response = requests.get(url=f"{CELLGUIDE_BASE_URL}/validated_descriptions/{cell_id}.json") + response.raise_for_status() + return response.json() From 1fd5909997f0781d529ba818809df5556948744d Mon Sep 17 00:00:00 2001 From: kaloster Date: Thu, 18 Jul 2024 21:17:17 +0000 Subject: [PATCH 02/24] chore: Updated [rdev] values.yaml image tags to sha-543642f3 --- .infra/rdev/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index 8bfd13d4e..a98b48ab5 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-01db94ac + tag: sha-543642f3 replicaCount: 1 env: # env vars common to all deployment stages From 6548442ee6a2ddaca7139e590987e9ded0b74546 Mon Sep 17 00:00:00 2001 From: kaloster Date: Fri, 19 Jul 2024 15:19:25 -0400 Subject: [PATCH 03/24] chore: style cleanup and constants --- .../components/categorical/value/index.tsx | 40 ++++----- .../infoPanel/common/constants.ts | 14 +++ .../infoPanel/common/containerInfo/index.tsx | 4 +- .../infoPanel/common/loadingInfo/index.tsx | 88 ++++++++++++------- .../geneExpression/infoPanel/common/style.ts | 19 +++- 5 files changed, 106 insertions(+), 59 deletions(-) create mode 100644 client/src/components/geneExpression/infoPanel/common/constants.ts diff --git a/client/src/components/categorical/value/index.tsx b/client/src/components/categorical/value/index.tsx index b03a02e35..7dfacc72b 100644 --- a/client/src/components/categorical/value/index.tsx +++ b/client/src/components/categorical/value/index.tsx @@ -646,28 +646,26 @@ class CategoryValue extends React.Component { {displayString}
- this.handleDisplayCellTypeInfo(displayString)} - > - + this.handleDisplayCellTypeInfo(displayString)} > -
- -
-
-
+ +
+ +
+
+ + {this.renderMiniStackedBar()} diff --git a/client/src/components/geneExpression/infoPanel/common/constants.ts b/client/src/components/geneExpression/infoPanel/common/constants.ts new file mode 100644 index 000000000..ea6ffa048 --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/common/constants.ts @@ -0,0 +1,14 @@ +export const LOADING_STRING = (entity: string) => `Loading ${entity}...`; +export const NO_ENTITY_SELECTED = (entity: string) => `No ${entity} Selected`; +export const ENTITY_NOT_FOUND = (entity: string) => + `Sorry, this ${entity} could not be found.`; +export const OPEN_IN = (entity: string) => `Open in ${entity}`; +export const SELECT_GENE_OR_CELL_TYPE = + "Select a gene or cell type or search the Cell Guide database"; +export const NCBI_WARNING = "NCBI didn't return an exact match for this gene."; +export const SEARCH_ON_GOOGLE = "Search on Google"; +export const LABELS = { + ontologyID: "Ontology ID: ", + Synonyms: "Synonyms: ", + References: "References: ", +}; diff --git a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx b/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx index 33e47fc0f..fd0a1cc62 100644 --- a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx +++ b/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx @@ -28,7 +28,7 @@ function ContainerInfo(props: ExtendedInfoProps) { > {/* Loading */} - {name && !error && loading && ( + {(name || symbol) && !error && loading && ( )} @@ -38,7 +38,7 @@ function ContainerInfo(props: ExtendedInfoProps) { {/* Error */} {error && } - {/* Show Info */} + {/* Show Info Card for Gen or Cell Type*/} {name && !error && !loading && ( - {name} - Loading {entity}... + + {name} + + {LOADING_STRING(entity)} ); } @@ -31,10 +46,8 @@ export function NoneSelected({ entity }: { entity: BaseInfoProps["entity"] }) { - No {entity} Selected - - Choose a gene above or search the Cell Guide database. - + {NO_ENTITY_SELECTED(entity)} + {SELECT_GENE_OR_CELL_TYPE} ); @@ -44,7 +57,7 @@ export function ShowWarningBanner() { return ( - NCBI didn't return an exact match for this gene. + {NCBI_WARNING} ); } @@ -53,14 +66,18 @@ export function ErrorInfo(props: BaseInfoProps) { const { name, entity } = props; return ( - {name} - Sorry, this {entity} could not be found. + + {name} + + + {ENTITY_NOT_FOUND(entity)} + - Search on Google + {SEARCH_ON_GOOGLE} ); @@ -84,40 +101,43 @@ export function ShowInfo(props: ExtendedInfoProps) { return ( {showWarningBanner ?? } -
+ {symbol ?? name} - -
+ + + {OPEN_IN(entity === "Cell Type" ? "Cell Guide" : "NCBI")} + + + {entity === "Gene" && ( - {name} + {name} )} -

{description}

+ {description} {entity === "Cell Type" ?? ( -

- Ontology ID: + + {LABELS.ontologyID} {id} -

+ )} {synonyms.length > 0 && ( -

- Synonyms - - {synonyms.map((syn) => ( - {syn}, + + {LABELS.Synonyms} + + {synonyms.map((syn, index) => ( + + {syn} + {index < synonyms.length - 1 && ", "} + ))} - -

+ + )} {entity === "Cell Type" && references && references?.length > 0 && ( -

- References + + {LABELS.References} {references.map((ref, index) => ( ))} -

+ )}
diff --git a/client/src/components/geneExpression/infoPanel/common/style.ts b/client/src/components/geneExpression/infoPanel/common/style.ts index 65dc4430d..5d5a7d127 100644 --- a/client/src/components/geneExpression/infoPanel/common/style.ts +++ b/client/src/components/geneExpression/infoPanel/common/style.ts @@ -28,7 +28,13 @@ export const InfoDiv = styled.div` margin: ${spacesM}px; `; +export const InfoOpenIn = styled.div` + padding-top: 15px; +`; + export const InfoSymbol = styled.h1` + width: 60%; + text-overflow: ellipsis; color: black; ${fontHeaderL} ${(props) => { @@ -40,6 +46,11 @@ export const InfoSymbol = styled.h1` }} `; +export const InfoTitle = styled.div` + display: flex; + justify-content: space-between; +`; + export const Content = styled.div` font-weight: 500; color: black; @@ -50,7 +61,11 @@ export const Content = styled.div` overflow: hidden; `; -export const SynHeader = styled.span` +export const ContentRow = styled.p` + padding-bottom: 4px; +`; + +export const InfoLabel = styled.span` ${fontBodyXs} ${(props) => { const colors = getColors(props); @@ -63,7 +78,7 @@ export const SynHeader = styled.span` }} `; -export const Synonyms = styled.span` +export const Items = styled.span` padding: 4px; color: black; From 3ea8d5d13f2ee38f0718fb0f20fd9d7d17ad781a Mon Sep 17 00:00:00 2001 From: kaloster Date: Fri, 19 Jul 2024 19:32:34 +0000 Subject: [PATCH 04/24] chore: Updated [rdev] values.yaml image tags to sha-6548442e --- .infra/rdev/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index a98b48ab5..1712d36cd 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-543642f3 + tag: sha-6548442e replicaCount: 1 env: # env vars common to all deployment stages From ddc0fec47fc4a9b48f1ac79e2ff72c84e6283260 Mon Sep 17 00:00:00 2001 From: kaloster Date: Fri, 19 Jul 2024 15:42:47 -0400 Subject: [PATCH 05/24] fix: mypy typing --- .infra/rdev/values.yaml | 2 +- .../infoPanel/cellTypeInfo/index.tsx | 3 +-- .../infoPanel/common/constants.ts | 2 ++ .../infoPanel/common/containerInfo/index.tsx | 2 +- server/common/rest.py | 2 +- server/common/utils/cell_type_info.py | 21 +++++++++++++++---- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index 1712d36cd..b335f2fd5 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-6548442e + tag: sha-44567a92 replicaCount: 1 env: # env vars common to all deployment stages diff --git a/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx b/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx index 6eff39c81..271382bd9 100644 --- a/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx +++ b/client/src/components/geneExpression/infoPanel/cellTypeInfo/index.tsx @@ -2,8 +2,7 @@ import React from "react"; import { connect } from "react-redux"; import { Props, mapStateToProps } from "./types"; import ContainerInfo from "../common/containerInfo"; - -const CELLGUIDE_URL = "https://cellxgene.cziscience.com/cellguide/"; +import { CELLGUIDE_URL } from "../common/constants"; function CellTypeInfo(props: Props) { const { cellInfo } = props; diff --git a/client/src/components/geneExpression/infoPanel/common/constants.ts b/client/src/components/geneExpression/infoPanel/common/constants.ts index ea6ffa048..bd375c666 100644 --- a/client/src/components/geneExpression/infoPanel/common/constants.ts +++ b/client/src/components/geneExpression/infoPanel/common/constants.ts @@ -12,3 +12,5 @@ export const LABELS = { Synonyms: "Synonyms: ", References: "References: ", }; + +export const CELLGUIDE_URL = "https://cellxgene.cziscience.com/cellguide/"; diff --git a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx b/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx index fd0a1cc62..6f6e759b9 100644 --- a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx +++ b/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx @@ -33,7 +33,7 @@ function ContainerInfo(props: ExtendedInfoProps) { )} {/* None Selected */} - {name === "" && error === null && } + {!entity && error === null && } {/* Error */} {error && } diff --git a/server/common/rest.py b/server/common/rest.py index d8986f209..60470c4e0 100644 --- a/server/common/rest.py +++ b/server/common/rest.py @@ -329,7 +329,7 @@ def cell_type_info_get(request): try: cell_description = get_cell_description(cell_id.replace(":", "_")) - except Exception as e: + except Exception: cell_description["description"] = cell_info.get("clDescription", "") cell_description["cell_id"] = cell_id diff --git a/server/common/utils/cell_type_info.py b/server/common/utils/cell_type_info.py index d8ed8621d..7232fd4ea 100644 --- a/server/common/utils/cell_type_info.py +++ b/server/common/utils/cell_type_info.py @@ -1,21 +1,34 @@ import requests +from typing import Dict, List, TypedDict, cast from server.common.constants import CELLGUIDE_BASE_URL +class CellTypeMetadata(TypedDict, total=False): + name: str + id: str + clDescription: str + synonyms: List[str] + + +class CellDescription(TypedDict): + description: str + references: List[str] + + def get_latest_snapshot_identifier() -> str: response = requests.get(url=f"{CELLGUIDE_BASE_URL}/latest_snapshot_identifier") response.raise_for_status() return response.text.strip() -def get_celltype_metadata(snapshot_identifier: str) -> dict: +def get_celltype_metadata(snapshot_identifier: str) -> Dict[str, CellTypeMetadata]: response = requests.get(url=f"{CELLGUIDE_BASE_URL}/{snapshot_identifier}/celltype_metadata.json") response.raise_for_status() - return response.json() + return cast(Dict[str, CellTypeMetadata], response.json()) -def get_cell_description(cell_id: str) -> dict: +def get_cell_description(cell_id: str) -> CellDescription: response = requests.get(url=f"{CELLGUIDE_BASE_URL}/validated_descriptions/{cell_id}.json") response.raise_for_status() - return response.json() + return cast(CellDescription, response.json()) From 50dae4efe80887f79440a8f53f841773c011a541 Mon Sep 17 00:00:00 2001 From: kaloster Date: Fri, 19 Jul 2024 17:06:40 -0400 Subject: [PATCH 06/24] fix: existing tests after refactor --- .infra/rdev/values.yaml | 2 +- .../index.tsx | 18 +++++++---- .../{loadingInfo => infoPanelParts}/index.tsx | 0 .../geneExpression/infoPanel/connect.ts | 32 +++++++++++++++++++ .../geneExpression/infoPanel/index.tsx | 27 ++++------------ .../index.tsx | 2 +- .../types.ts | 0 .../connect.ts | 0 .../datasetInfoFormat.tsx | 0 .../index.tsx | 0 .../types.ts | 0 .../{geneInfo => infoPanelGene}/index.tsx | 2 +- .../{geneInfo => infoPanelGene}/types.ts | 0 client/src/reducers/controls.ts | 3 +- server/common/utils/cell_type_info.py | 3 +- 15 files changed, 56 insertions(+), 33 deletions(-) rename client/src/components/geneExpression/infoPanel/common/{containerInfo => infoPanelContainer}/index.tsx (77%) rename client/src/components/geneExpression/infoPanel/common/{loadingInfo => infoPanelParts}/index.tsx (100%) create mode 100644 client/src/components/geneExpression/infoPanel/connect.ts rename client/src/components/geneExpression/infoPanel/{cellTypeInfo => infoPanelCellType}/index.tsx (92%) rename client/src/components/geneExpression/infoPanel/{cellTypeInfo => infoPanelCellType}/types.ts (100%) rename client/src/components/geneExpression/infoPanel/{datasetInfo => infoPanelDataset}/connect.ts (100%) rename client/src/components/geneExpression/infoPanel/{datasetInfo => infoPanelDataset}/datasetInfoFormat.tsx (100%) rename client/src/components/geneExpression/infoPanel/{datasetInfo => infoPanelDataset}/index.tsx (100%) rename client/src/components/geneExpression/infoPanel/{datasetInfo => infoPanelDataset}/types.ts (100%) rename client/src/components/geneExpression/infoPanel/{geneInfo => infoPanelGene}/index.tsx (92%) rename client/src/components/geneExpression/infoPanel/{geneInfo => infoPanelGene}/types.ts (100%) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index b335f2fd5..e180f9925 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-44567a92 + tag: sha-419fde2c replicaCount: 1 env: # env vars common to all deployment stages diff --git a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx b/client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx similarity index 77% rename from client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx rename to client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx index 6f6e759b9..3358208a0 100644 --- a/client/src/components/geneExpression/infoPanel/common/containerInfo/index.tsx +++ b/client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx @@ -1,7 +1,12 @@ import React from "react"; import { kebabCase } from "lodash"; import { InfoContainer, InfoWrapper } from "../style"; -import { ErrorInfo, LoadingInfo, NoneSelected, ShowInfo } from "../loadingInfo"; +import { + ErrorInfo, + LoadingInfo, + NoneSelected, + ShowInfo, +} from "../infoPanelParts"; import { ExtendedInfoProps } from "../types"; function ContainerInfo(props: ExtendedInfoProps) { @@ -21,11 +26,10 @@ function ContainerInfo(props: ExtendedInfoProps) { const entityTag = kebabCase(entity); + const wrapperTestId = `${entity === "Gene" ? symbol : id}:${entityTag}-info`; + return ( - + {/* Loading */} {(name || symbol) && !error && loading && ( @@ -33,7 +37,9 @@ function ContainerInfo(props: ExtendedInfoProps) { )} {/* None Selected */} - {!entity && error === null && } + {name === "" && error === null && !loading && ( + + )} {/* Error */} {error && } diff --git a/client/src/components/geneExpression/infoPanel/common/loadingInfo/index.tsx b/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx similarity index 100% rename from client/src/components/geneExpression/infoPanel/common/loadingInfo/index.tsx rename to client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx diff --git a/client/src/components/geneExpression/infoPanel/connect.ts b/client/src/components/geneExpression/infoPanel/connect.ts new file mode 100644 index 000000000..64dbb076e --- /dev/null +++ b/client/src/components/geneExpression/infoPanel/connect.ts @@ -0,0 +1,32 @@ +import { ChangeEvent, useEffect, useState } from "react"; +import { AppDispatch } from "../../../reducers"; + +function useConnect({ + dispatch, + activeTab, +}: { + dispatch: AppDispatch; + activeTab: string; +}) { + const [value, setValue] = useState(1); + + const handleTabsChange = ( + _: ChangeEvent>, + tabsValue: unknown + ) => { + setValue(tabsValue as number); + dispatch({ + type: "toggle active info panel", + activeTab: + tabsValue === 0 ? "Gene" : tabsValue === 1 ? "CellType" : "Dataset", + }); + }; + + useEffect(() => { + setValue(activeTab === "Gene" ? 0 : activeTab === "CellType" ? 1 : 2); + }, [activeTab]); + + return { value, handleTabsChange }; +} + +export default useConnect; diff --git a/client/src/components/geneExpression/infoPanel/index.tsx b/client/src/components/geneExpression/infoPanel/index.tsx index e8db91198..51d1bd095 100644 --- a/client/src/components/geneExpression/infoPanel/index.tsx +++ b/client/src/components/geneExpression/infoPanel/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, ChangeEvent, useEffect } from "react"; +import React from "react"; import { connect } from "react-redux"; import { AnchorButton, ButtonGroup } from "@blueprintjs/core"; import { Tabs, Tab } from "czifui"; @@ -9,30 +9,15 @@ import { InfoPanelHeader, InfoPanelWrapper, } from "./style"; -import CellTypeInfo from "./cellTypeInfo"; -import GeneInfo from "./geneInfo"; -import DatasetInfo from "./datasetInfo"; +import CellTypeInfo from "./infoPanelCellType"; +import GeneInfo from "./infoPanelGene"; +import DatasetInfo from "./infoPanelDataset"; import { Props, mapStateToProps } from "./types"; +import useConnect from "./connect"; function InfoPanel(props: Props) { const { activeTab, dispatch, infoPanelMinimized, infoPanelHidden } = props; - const [value, setValue] = useState(1); - - const handleTabsChange = ( - _: ChangeEvent>, - tabsValue: unknown - ) => { - setValue(tabsValue as number); - dispatch({ - type: "toggle active info panel", - activeTab: - tabsValue === 0 ? "Gene" : tabsValue === 1 ? "CellType" : "Dataset", - }); - }; - - useEffect(() => { - setValue(activeTab === "Gene" ? 0 : activeTab === "CellType" ? 1 : 2); - }, [activeTab]); + const { value, handleTabsChange } = useConnect({ dispatch, activeTab }); return ( Date: Sat, 20 Jul 2024 00:37:49 +0000 Subject: [PATCH 07/24] chore: Updated [rdev] values.yaml image tags to sha-50dae4ef --- .infra/rdev/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index e180f9925..7bb0b87af 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-419fde2c + tag: sha-50dae4ef replicaCount: 1 env: # env vars common to all deployment stages From c36b9bf889ee2615177d348932b88c8e50b74d7b Mon Sep 17 00:00:00 2001 From: kaloster Date: Mon, 22 Jul 2024 17:24:33 -0400 Subject: [PATCH 08/24] chore: e2e and unit tests --- client/__tests__/e2e/cellxgeneActions.ts | 41 +++++---- client/__tests__/e2e/e2e.test.ts | 52 ++++++++++- client/__tests__/util/helpers.ts | 7 ++ client/src/analytics/events.ts | 1 + .../components/categorical/value/index.tsx | 48 +++++----- .../infoPanel/common/constants.ts | 1 - .../common/infoPanelContainer/index.tsx | 4 +- .../infoPanel/common/infoPanelParts/index.tsx | 13 +-- server/tests/unit/common/apis/test_api_v3.py | 89 ++++++++++++++++++- 9 files changed, 203 insertions(+), 53 deletions(-) diff --git a/client/__tests__/e2e/cellxgeneActions.ts b/client/__tests__/e2e/cellxgeneActions.ts index 61ebb0817..fdb0a0ada 100644 --- a/client/__tests__/e2e/cellxgeneActions.ts +++ b/client/__tests__/e2e/cellxgeneActions.ts @@ -602,22 +602,27 @@ export async function expandGene( export async function requestGeneInfo(gene: string, page: Page): Promise { await page.getByTestId(`get-info-${gene}`).click(); - await expect(page.getByTestId(`${gene}:gene-info`)).toBeTruthy(); +} + +export async function requestCellTypeInfo(cell: string, page: Page) { + await page.getByTestId(`cell_type:category-expand`).click(); + await page.getByTestId(`get-info-cell_type-${cell}`).click(); } export async function assertInfoPanelExists( - gene: string, + id: string, + entity: string, page: Page ): Promise { - await expect(page.getByTestId(`${gene}:gene-info`)).toBeTruthy(); - await expect(page.getByTestId(`info-panel-header`)).toBeTruthy(); - await expect(page.getByTestId(`min-info-panel`)).toBeTruthy(); + await expect(page.getByTestId(`${id}:${entity}-info-wrapper`)).toBeVisible(); + await expect(page.getByTestId(`info-panel-header`)).toBeVisible(); + await expect(page.getByTestId(`min-info-panel`)).toBeVisible(); - await expect(page.getByTestId(`gene-info-synonyms`).innerText).not.toEqual( - "" - ); + await expect( + page.getByTestId(`${entity}-info-synonyms`).innerText + ).not.toEqual(""); - await expect(page.getByTestId(`gene-info-link`)).toBeTruthy(); + await expect(page.getByTestId(`${entity}-info-link`)).toBeTruthy(); } export async function minimizeInfoPanel(page: Page): Promise { @@ -625,11 +630,12 @@ export async function minimizeInfoPanel(page: Page): Promise { } export async function assertInfoPanelIsMinimized( - gene: string, + id: string, + entity: string, page: Page ): Promise { const testIds = [ - `${gene}:gene-info`, + `${id}:${entity}-info-wrapper`, "info-panel-header", "max-info-panel", "close-info-panel", @@ -637,8 +643,8 @@ export async function assertInfoPanelIsMinimized( await tryUntil( async () => { - for (const id of testIds) { - const result = await page.getByTestId(id).isVisible(); + for (const testId of testIds) { + const result = await page.getByTestId(testId).isVisible(); await expect(result).toBe(true); } }, @@ -651,19 +657,20 @@ export async function closeInfoPanel(page: Page): Promise { } export async function assertInfoPanelClosed( - gene: string, + id: string, + entity: string, page: Page ): Promise { const testIds = [ - `${gene}:gene-info`, + `${id}:${entity}-info-wrapper`, "info-panel-header", "min-info-panel", "close-info-panel", ]; await tryUntil( async () => { - for (const id of testIds) { - const result = await page.getByTestId(id).isVisible(); + for (const testId of testIds) { + const result = await page.getByTestId(testId).isVisible(); await expect(result).toBe(false); } }, diff --git a/client/__tests__/e2e/e2e.test.ts b/client/__tests__/e2e/e2e.test.ts index f8881eaf4..190c97748 100644 --- a/client/__tests__/e2e/e2e.test.ts +++ b/client/__tests__/e2e/e2e.test.ts @@ -52,6 +52,7 @@ import { snapshotTestGraph, getAllCategories, selectLayout, + requestCellTypeInfo, } from "./cellxgeneActions"; import { datasets } from "./data"; @@ -66,6 +67,7 @@ import { conditionallyToggleSidePanel, goToPage, shouldSkipTests, + skipIfPbmcDataset, skipIfSidePanel, toggleSidePanel, } from "../util/helpers"; @@ -99,6 +101,7 @@ const brushThisGeneGeneset = "brush_this_gene"; // open gene info card const geneToRequestInfo = "SIK1"; +const cellToRequestInfo = "monocyte"; const genesetDescriptionString = "fourth_gene_set: fourth description"; const genesetToCheckForDescription = "fourth_gene_set"; @@ -1382,6 +1385,9 @@ for (const testDataset of testDatasets) { test("open info panel and hide/remove", async ({ page, }, testInfo) => { + skipIfSidePanel(graphTestId, MAIN_PANEL); + skipIfPbmcDataset(testDataset, DATASET); + await setup({ option, page, url, testInfo }); await addGeneToSearch(geneToRequestInfo, page); @@ -1390,7 +1396,11 @@ for (const testDataset of testDatasets) { await tryUntil( async () => { await requestGeneInfo(geneToRequestInfo, page); - await assertInfoPanelExists(geneToRequestInfo, page); + await assertInfoPanelExists( + geneToRequestInfo, + "gene", + page + ); }, { page } ); @@ -1400,7 +1410,11 @@ for (const testDataset of testDatasets) { await tryUntil( async () => { await minimizeInfoPanel(page); - await assertInfoPanelIsMinimized(geneToRequestInfo, page); + await assertInfoPanelIsMinimized( + geneToRequestInfo, + "gene", + page + ); }, { page } ); @@ -1410,7 +1424,39 @@ for (const testDataset of testDatasets) { await tryUntil( async () => { await closeInfoPanel(page); - await assertInfoPanelClosed(geneToRequestInfo, page); + await assertInfoPanelClosed( + geneToRequestInfo, + "gene", + page + ); + }, + { page } + ); + + await snapshotTestGraph(page, testInfo); + + await tryUntil( + async () => { + await requestCellTypeInfo(cellToRequestInfo, page); + await assertInfoPanelExists( + cellToRequestInfo, + "cell-type", + page + ); + }, + { page } + ); + + await snapshotTestGraph(page, testInfo); + + await tryUntil( + async () => { + await minimizeInfoPanel(page); + await assertInfoPanelIsMinimized( + cellToRequestInfo, + "cell-type", + page + ); }, { page } ); diff --git a/client/__tests__/util/helpers.ts b/client/__tests__/util/helpers.ts index e6e7cb101..1ce673cdd 100644 --- a/client/__tests__/util/helpers.ts +++ b/client/__tests__/util/helpers.ts @@ -291,6 +291,13 @@ export function skipIfSidePanel(graphTestId: string, MAIN_PANEL: string) { skip(graphTestId !== MAIN_PANEL, "This test is only for the main panel"); } +export function skipIfPbmcDataset(graphTestId: string, PBMC_DATASET: string) { + skip( + graphTestId === PBMC_DATASET, + "This test is only for the spatial dataset, since there's not cell_type data in pbmc3k.cxg dataset" + ); +} + export function shouldSkipTests( graphTestId: string, SIDE_PANEL: string diff --git a/client/src/analytics/events.ts b/client/src/analytics/events.ts index 3c968c1a5..283aa9586 100644 --- a/client/src/analytics/events.ts +++ b/client/src/analytics/events.ts @@ -51,6 +51,7 @@ export enum EVENTS { EXPLORER_ADD_GENE_AND_SELECT_HISTOGRAM = "EXPLORER_ADD_GENE_AND_SELECT_HISTOGRAM", EXPLORER_SELECT_HISTOGRAM = "EXPLORER_SELECT_HISTOGRAM", EXPLORER_GENE_INFO_BUTTON_CLICKED = "EXPLORER_GENE_INFO_BUTTON_CLICKED", + EXPLORER_CELLTYPE_INFO_BUTTON_CLICKED = "EXPLORER_CELLTYPE_INFO_BUTTON_CLICKED", EXPLORER_IMAGE_SELECT = "EXPLORER_IMAGE_SELECT", EXPLORER_IMAGE_DESELECT = "EXPLORER_IMAGE_DESELECT", EXPLORER_RE_CENTER_EMBEDDING = "EXPLORER_RE_CENTER_EMBEDDING", diff --git a/client/src/components/categorical/value/index.tsx b/client/src/components/categorical/value/index.tsx index effa2e840..5eb25e9e5 100644 --- a/client/src/components/categorical/value/index.tsx +++ b/client/src/components/categorical/value/index.tsx @@ -361,9 +361,7 @@ class CategoryValue extends React.Component { handleDisplayCellTypeInfo = async (cellName: string): Promise => { const { dispatch } = this.props; - track(EVENTS.EXPLORER_GENE_INFO_BUTTON_CLICKED, { - cellName, - }); + track(EVENTS.EXPLORER_CELLTYPE_INFO_BUTTON_CLICKED, { cellName }); dispatch({ type: "request cell info start", cellName }); @@ -573,7 +571,8 @@ class CategoryValue extends React.Component { CHART_MARGIN : globals.leftSidebarWidth - otherElementsWidth; - const isCellInfo = metadataField === "CellType"; + const isCellInfo = metadataField === "cell_type"; + return (
{ {displayString} -
- this.handleDisplayCellTypeInfo(displayString)} - > - -
- -
-
-
-
+ {isCellInfo && ( +
+ this.handleDisplayCellTypeInfo(displayString)} + > + +
+ +
+
+
+
+ )}
{this.renderMiniStackedBar()} diff --git a/client/src/components/geneExpression/infoPanel/common/constants.ts b/client/src/components/geneExpression/infoPanel/common/constants.ts index bd375c666..ea61e6ae8 100644 --- a/client/src/components/geneExpression/infoPanel/common/constants.ts +++ b/client/src/components/geneExpression/infoPanel/common/constants.ts @@ -1,4 +1,3 @@ -export const LOADING_STRING = (entity: string) => `Loading ${entity}...`; export const NO_ENTITY_SELECTED = (entity: string) => `No ${entity} Selected`; export const ENTITY_NOT_FOUND = (entity: string) => `Sorry, this ${entity} could not be found.`; diff --git a/client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx b/client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx index 3358208a0..084d471a1 100644 --- a/client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx +++ b/client/src/components/geneExpression/infoPanel/common/infoPanelContainer/index.tsx @@ -26,7 +26,9 @@ function ContainerInfo(props: ExtendedInfoProps) { const entityTag = kebabCase(entity); - const wrapperTestId = `${entity === "Gene" ? symbol : id}:${entityTag}-info`; + const wrapperTestId = `${ + entity === "Gene" ? symbol : name + }:${entityTag}-info-wrapper`; return ( diff --git a/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx b/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx index dbb1a87ae..c3c6d7548 100644 --- a/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx +++ b/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx @@ -1,6 +1,6 @@ import React from "react"; import { kebabCase } from "lodash"; -import { Icon } from "czifui"; +import { Icon, LoadingIndicator } from "czifui"; import { Content, @@ -20,7 +20,6 @@ import { import { BaseInfoProps, ExtendedInfoProps } from "../types"; import { ENTITY_NOT_FOUND, - LOADING_STRING, NCBI_WARNING, NO_ENTITY_SELECTED, OPEN_IN, @@ -30,13 +29,15 @@ import { } from "../constants"; export function LoadingInfo(props: BaseInfoProps) { - const { name, entity } = props; + const { name } = props; return ( {name} - {LOADING_STRING(entity)} + + + ); } @@ -114,9 +115,9 @@ export function ShowInfo(props: ExtendedInfoProps) { )} {description} - {entity === "Cell Type" ?? ( + {entity === "Cell Type" && ( - {LABELS.ontologyID} + {LABELS.ontologyID} {id} diff --git a/server/tests/unit/common/apis/test_api_v3.py b/server/tests/unit/common/apis/test_api_v3.py index 3ae30eaa2..0521d6a70 100644 --- a/server/tests/unit/common/apis/test_api_v3.py +++ b/server/tests/unit/common/apis/test_api_v3.py @@ -20,6 +20,27 @@ BAD_FILTER = {"filter": {"obs": {"annotation_value": [{"name": "xyz"}]}}} +# Test data for cell type info endpoint +TEST_SNAPSHOT_IDENTIFIER = "snapshot123" +TEST_CELL_TYPE_METADATA = { + "1": { + "name": "monocyte", + "id": "CL:0000576", + "clDescription": "Monocytes are a type of white blood cell...", + "synonyms": [], + } +} +TEST_CELL_DESCRIPTION = { + "description": "Monocytes are a type of white blood cell...", + "references": ["ref1", "ref2"], +} +TEST_CELL_DESCRIPTION_RESPONSE = ( + '{"description": "Monocytes are a type of white blood cell", "references": ["ref1", "ref2"]}' +) +TEST_CELL_METADATA_RESPONSE = ( + '{"1": {"name": "monocyte", "id": "CL:0000540", "clDescription": "A neuron", "synonyms": []}}' +) + class BaseTest(_BaseTest): @classmethod @@ -554,6 +575,67 @@ def test_gene_info_failure(self, mock_request): self.assertEqual(result.status_code, HTTPStatus.BAD_REQUEST) self.assertEqual(result.headers["Content-Type"], "application/json") + @patch("server.common.rest.requests.get") + @patch("server.common.utils.cell_type_info.get_latest_snapshot_identifier") + @patch("server.common.utils.cell_type_info.get_celltype_metadata") + @patch("server.common.utils.cell_type_info.get_cell_description") + def test_cell_type_info_success( + self, mock_get_cell_description, mock_get_celltype_metadata, mock_get_latest_snapshot_identifier, mock_request + ): + endpoint = "cellinfo?cell=monocyte" + mock_get_latest_snapshot_identifier.return_value = TEST_SNAPSHOT_IDENTIFIER + mock_get_celltype_metadata.return_value = TEST_CELL_TYPE_METADATA + mock_get_cell_description.return_value = TEST_CELL_DESCRIPTION + + def mock_requests_get(url, *args, **kwargs): + if "latest_snapshot_identifier" in url: + return MockResponse(TEST_SNAPSHOT_IDENTIFIER, 200) + elif "celltype_metadata.json" in url: + return MockResponse(TEST_CELL_METADATA_RESPONSE, 200) + else: + return MockResponse(TEST_CELL_DESCRIPTION_RESPONSE, 200) + + mock_request.side_effect = mock_requests_get + + for url_base in [self.TEST_UNS_URL_BASE]: + with self.subTest(url_base=url_base): + url = f"{url_base}{endpoint}" + result = self.client.get(url) + self.assertEqual(result.status_code, HTTPStatus.OK) + self.assertIn("description", result.json) + self.assertIn("cell_id", result.json) + self.assertIn("cell_name", result.json) + self.assertIn("synonyms", result.json) + + @patch("server.common.rest.requests.get") + @patch("server.common.utils.cell_type_info.get_latest_snapshot_identifier") + @patch("server.common.utils.cell_type_info.get_celltype_metadata") + @patch("server.common.utils.cell_type_info.get_cell_description") + def test_cell_type_info_not_found( + self, _, mock_get_celltype_metadata, mock_get_latest_snapshot_identifier, mock_request + ): + endpoint = "cellinfo?cell=unknowncell" + mock_get_latest_snapshot_identifier.return_value = TEST_SNAPSHOT_IDENTIFIER + mock_get_celltype_metadata.return_value = TEST_CELL_TYPE_METADATA + + def mock_requests_get(url): + if "latest_snapshot_identifier" in url: + return MockResponse(TEST_SNAPSHOT_IDENTIFIER, 200) + elif "celltype_metadata.json" in url: + return MockResponse(TEST_CELL_METADATA_RESPONSE, 200) + else: + return MockResponse(TEST_CELL_DESCRIPTION_RESPONSE, 200) + + mock_request.side_effect = mock_requests_get + + for url_base in [self.TEST_UNS_URL_BASE]: + with self.subTest(url_base=url_base): + url = f"{url_base}{endpoint}" + result = self.client.get(url) + self.assertEqual(result.status_code, HTTPStatus.OK) + self.assertIn("error", result.json) + self.assertEqual(result.json["error"], "Cell type not found") + @unittest.skipIf(lambda x: os.getenv("SKIP_STATIC"), "Skip static test when running locally") def test_static(self): endpoint = "static" @@ -1074,10 +1156,15 @@ def test_get_deployed_version(self): class MockResponse: - def __init__(self, body, status_code, ok=True): + def __init__(self, body, status_code, text="monocyte", ok=True): + self.text = text self.content = body self.status_code = status_code self.ok = ok def json(self): return json.loads(self.content) + + def raise_for_status(self): + if self.status_code >= 400: + raise From dc6815ede917f38c0232e46abba51942bb19c38a Mon Sep 17 00:00:00 2001 From: kaloster Date: Mon, 22 Jul 2024 21:39:12 +0000 Subject: [PATCH 09/24] fix: error handling and tweaks --- .infra/rdev/values.yaml | 2 +- client/src/actions/index.ts | 2 +- .../infoPanel/common/infoPanelParts/index.tsx | 2 +- client/src/reducers/controls.ts | 13 ++++++------- server/common/rest.py | 15 ++++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index 7bb0b87af..34312dbb8 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-50dae4ef + tag: sha-da9947f7 replicaCount: 1 env: # env vars common to all deployment stages diff --git a/client/src/actions/index.ts b/client/src/actions/index.ts index 078b4227e..ab60cc902 100644 --- a/client/src/actions/index.ts +++ b/client/src/actions/index.ts @@ -185,7 +185,7 @@ interface CellTypeInfoAPI { } /** * Fetch cell type summary information - * @param cell human-readable name of gene + * @param cell human-readable name of cell type */ async function fetchCellTypeInfo( cell: string diff --git a/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx b/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx index c3c6d7548..de503f6cb 100644 --- a/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx +++ b/client/src/components/geneExpression/infoPanel/common/infoPanelParts/index.tsx @@ -101,7 +101,7 @@ export function ShowInfo(props: ExtendedInfoProps) { return ( - {showWarningBanner ?? } + {showWarningBanner && } {symbol ?? name} diff --git a/client/src/reducers/controls.ts b/client/src/reducers/controls.ts index c2e7e07fb..733110ba7 100644 --- a/client/src/reducers/controls.ts +++ b/client/src/reducers/controls.ts @@ -270,13 +270,12 @@ const Controls = ( ...state, geneInfo: { gene: action.gene, - geneName: action.name ?? state.geneInfo.geneName, - geneSummary: action.summary ?? state.geneInfo.geneSummary, - geneSynonyms: action.synonyms ?? state.geneInfo.geneSynonyms, - geneUrl: action.url ?? state.geneInfo.geneUrl, - showWarningBanner: - action.showWarningBanner ?? state.geneInfo.showWarningBanner, - infoError: action.infoError ?? state.geneInfo.infoError, + geneName: action.name, + geneSummary: action.summary, + geneSynonyms: action.synonyms, + geneUrl: action.url, + showWarningBanner: action.showWarningBanner, + infoError: action.infoError, loading: false, }, }; diff --git a/server/common/rest.py b/server/common/rest.py index 60470c4e0..c4e3408be 100644 --- a/server/common/rest.py +++ b/server/common/rest.py @@ -330,15 +330,16 @@ def cell_type_info_get(request): try: cell_description = get_cell_description(cell_id.replace(":", "_")) except Exception: - cell_description["description"] = cell_info.get("clDescription", "") + current_app.logger.warning("Extended cell description not available, using default description instead.") + cell_description = {"description": cell_info.get("clDescription", "")} - cell_description["cell_id"] = cell_id - cell_description["cell_name"] = cell_name - cell_description["synonyms"] = synonyms + response_data = {"cell_id": cell_id, "cell_name": cell_name, "synonyms": synonyms, **cell_description} - return make_response(jsonify(cell_description), HTTPStatus.OK) - except Exception as e: - raise e + return make_response(jsonify(response_data), HTTPStatus.OK) + + except Exception: + current_app.logger.error("Error fetching cell type info") + raise def get_deployed_version(request): From 3882bc0ac31b594cdc047bfdc8b7547b7a5486ab Mon Sep 17 00:00:00 2001 From: kaloster Date: Mon, 22 Jul 2024 22:16:08 +0000 Subject: [PATCH 10/24] chore: Updated [rdev] values.yaml image tags to sha-dc6815ed --- .infra/rdev/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index 34312dbb8..d1b3bd423 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-da9947f7 + tag: sha-dc6815ed replicaCount: 1 env: # env vars common to all deployment stages From 4f1d534ab307757a54c893352ae5f88fea6d4eb3 Mon Sep 17 00:00:00 2001 From: kaloster Date: Tue, 23 Jul 2024 01:35:22 -0400 Subject: [PATCH 11/24] fix: some visual tweaks --- client/__tests__/e2e/cellxgeneActions.ts | 18 +++--- client/index.html | 7 ++- client/index_template.html | 2 +- .../components/categorical/value/index.tsx | 42 +++++++------- .../infoPanel/common/constants.ts | 9 +-- .../common/infoPanelContainer/index.tsx | 20 +++---- .../infoPanel/common/infoPanelParts/index.tsx | 56 ++++++++++++++----- .../geneExpression/infoPanel/common/style.ts | 5 ++ .../geneExpression/infoPanel/common/types.ts | 2 +- .../infoPanel/infoPanelCellType/index.tsx | 2 +- .../infoPanel/infoPanelGene/index.tsx | 2 +- 11 files changed, 104 insertions(+), 61 deletions(-) diff --git a/client/__tests__/e2e/cellxgeneActions.ts b/client/__tests__/e2e/cellxgeneActions.ts index fdb0a0ada..cc96feb61 100644 --- a/client/__tests__/e2e/cellxgeneActions.ts +++ b/client/__tests__/e2e/cellxgeneActions.ts @@ -611,18 +611,20 @@ export async function requestCellTypeInfo(cell: string, page: Page) { export async function assertInfoPanelExists( id: string, - entity: string, + infoType: string, page: Page ): Promise { - await expect(page.getByTestId(`${id}:${entity}-info-wrapper`)).toBeVisible(); + await expect( + page.getByTestId(`${id}:${infoType}-info-wrapper`) + ).toBeVisible(); await expect(page.getByTestId(`info-panel-header`)).toBeVisible(); await expect(page.getByTestId(`min-info-panel`)).toBeVisible(); await expect( - page.getByTestId(`${entity}-info-synonyms`).innerText + page.getByTestId(`${infoType}-info-synonyms`).innerText ).not.toEqual(""); - await expect(page.getByTestId(`${entity}-info-link`)).toBeTruthy(); + await expect(page.getByTestId(`${infoType}-info-link`)).toBeTruthy(); } export async function minimizeInfoPanel(page: Page): Promise { @@ -631,11 +633,11 @@ export async function minimizeInfoPanel(page: Page): Promise { export async function assertInfoPanelIsMinimized( id: string, - entity: string, + infoType: string, page: Page ): Promise { const testIds = [ - `${id}:${entity}-info-wrapper`, + `${id}:${infoType}-info-wrapper`, "info-panel-header", "max-info-panel", "close-info-panel", @@ -658,11 +660,11 @@ export async function closeInfoPanel(page: Page): Promise { export async function assertInfoPanelClosed( id: string, - entity: string, + infoType: string, page: Page ): Promise { const testIds = [ - `${id}:${entity}-info-wrapper`, + `${id}:${infoType}-info-wrapper`, "info-panel-header", "min-info-panel", "close-info-panel", diff --git a/client/index.html b/client/index.html index b4be28603..3e129b0b8 100644 --- a/client/index.html +++ b/client/index.html @@ -21,7 +21,7 @@ text, div { font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", "Arial", - sans-serif; + "Open Sans", sans-serif; font-size: 14px; letter-spacing: -0.006em; } @@ -42,6 +42,11 @@ rel="stylesheet" crossorigin="anonymous" /> +