diff --git a/data-catalog/app/apis/catalog/brc-analytics-catalog/common/entities.ts b/data-catalog/app/apis/catalog/brc-analytics-catalog/common/entities.ts index 33e58c7..78a67fb 100644 --- a/data-catalog/app/apis/catalog/brc-analytics-catalog/common/entities.ts +++ b/data-catalog/app/apis/catalog/brc-analytics-catalog/common/entities.ts @@ -1,3 +1,5 @@ +export type BRCCatalog = BRCDataCatalogGenome; + export interface BRCDataCatalogGenome { chromosomes: number; contigs: number; @@ -9,3 +11,16 @@ export interface BRCDataCatalogGenome { ucscBrowserUrl: string; vEuPathDbProject: string; } + +export interface EntitiesResponse { + hits: R[]; + pagination: EntitiesResponsePagination; + termFacets: Record; +} + +export interface EntitiesResponsePagination { + count: number; + pages: number; + size: number; + total: number; +} diff --git a/data-catalog/app/apis/catalog/brc-analytics-catalog/common/utils.ts b/data-catalog/app/apis/catalog/brc-analytics-catalog/common/utils.ts index 962db94..73363b8 100644 --- a/data-catalog/app/apis/catalog/brc-analytics-catalog/common/utils.ts +++ b/data-catalog/app/apis/catalog/brc-analytics-catalog/common/utils.ts @@ -1,5 +1,10 @@ import { BRCDataCatalogGenome } from "./entities"; export function getGenomeId(genome: BRCDataCatalogGenome): string { - return genome.organism; + return genome.genomeVersionAssemblyId; +} + +export function getGenomeTitle(genome?: BRCDataCatalogGenome): string { + if (!genome) return ""; + return `${genome.species} - ${genome.strain}`; } diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/analysisMethod.styles.ts b/data-catalog/app/components/Entity/components/AnalysisMethod/analysisMethod.styles.ts new file mode 100644 index 0000000..7bf6fce --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/analysisMethod.styles.ts @@ -0,0 +1,13 @@ +import { ButtonPrimary } from "@databiosphere/findable-ui/lib/components/common/Button/components/ButtonPrimary/buttonPrimary"; +import { CardContent } from "@databiosphere/findable-ui/lib/components/common/Card/card.styles"; +import styled from "@emotion/styled"; + +export const StyledCardContent = styled(CardContent)` + gap: 4px; +`; + +export const StyledButtonPrimary = styled(ButtonPrimary)` + justify-self: flex-start; + padding-bottom: 8px; + padding-top: 8px; +`; diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/analysisMethod.tsx b/data-catalog/app/components/Entity/components/AnalysisMethod/analysisMethod.tsx new file mode 100644 index 0000000..4c7de8c --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/analysisMethod.tsx @@ -0,0 +1,48 @@ +import { CardProps } from "@databiosphere/findable-ui/lib/components/common/Card/card"; +import { CardSection } from "@databiosphere/findable-ui/lib/components/common/Card/card.styles"; +import { CardText } from "@databiosphere/findable-ui/lib/components/common/Card/components/CardText/cardText"; +import { CardTitle } from "@databiosphere/findable-ui/lib/components/common/Card/components/CardTitle/cardTitle"; +import { FluidPaper } from "@databiosphere/findable-ui/lib/components/common/Paper/paper.styles"; +import { + ANCHOR_TARGET, + REL_ATTRIBUTE, +} from "@databiosphere/findable-ui/lib/components/Links/common/entities"; +import { Card } from "@mui/material"; +import { + StyledButtonPrimary, + StyledCardContent, +} from "./analysisMethod.styles"; + +export interface AnalysisMethodProps extends CardProps { + url: string; +} + +export const AnalysisMethod = ({ + Paper = FluidPaper, + text, + title, + url, +}: AnalysisMethodProps): JSX.Element => { + return ( + + + + {title} + {text} + + { + window.open( + url, + ANCHOR_TARGET.BLANK, + REL_ATTRIBUTE.NO_OPENER_NO_REFERRER + ); + }} + > + Analyze + + + + ); +}; diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/assembly.mdx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/assembly.mdx new file mode 100644 index 0000000..498cf07 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/assembly.mdx @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/genomeComparisons.mdx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/genomeComparisons.mdx new file mode 100644 index 0000000..498cf07 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/genomeComparisons.mdx @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/index.tsx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/index.tsx new file mode 100644 index 0000000..d60cd0b --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/index.tsx @@ -0,0 +1,6 @@ +export { default as Assembly } from "./assembly.mdx"; +export { default as GenomeComparisons } from "./genomeComparisons.mdx"; +export { default as ProteinFolding } from "./proteinFolding.mdx"; +export { default as Regulation } from "./regulation.mdx"; +export { default as Transcriptomics } from "./transcriptomics.mdx"; +export { default as VariantCalling } from "./variantCalling.mdx"; diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/proteinFolding.mdx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/proteinFolding.mdx new file mode 100644 index 0000000..498cf07 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/proteinFolding.mdx @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/regulation.mdx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/regulation.mdx new file mode 100644 index 0000000..498cf07 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/regulation.mdx @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/transcriptomics.mdx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/transcriptomics.mdx new file mode 100644 index 0000000..498cf07 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/transcriptomics.mdx @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. diff --git a/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/variantCalling.mdx b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/variantCalling.mdx new file mode 100644 index 0000000..498cf07 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisMethod/mdx/variantCalling.mdx @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. diff --git a/data-catalog/app/components/Entity/components/AnalysisPortals/analysisPortals.styles.ts b/data-catalog/app/components/Entity/components/AnalysisPortals/analysisPortals.styles.ts new file mode 100644 index 0000000..0ce285c --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisPortals/analysisPortals.styles.ts @@ -0,0 +1,9 @@ +import styled from "@emotion/styled"; +import { ButtonBase } from "@mui/material"; + +export const StyledButtonBase = styled(ButtonBase)` + display: grid; + gap: 8px; + grid-template-columns: auto 1fr; + justify-items: flex-start; +`; diff --git a/data-catalog/app/components/Entity/components/AnalysisPortals/analysisPortals.tsx b/data-catalog/app/components/Entity/components/AnalysisPortals/analysisPortals.tsx new file mode 100644 index 0000000..09f0af5 --- /dev/null +++ b/data-catalog/app/components/Entity/components/AnalysisPortals/analysisPortals.tsx @@ -0,0 +1,47 @@ +import { + StaticImage, + StaticImageProps, +} from "@databiosphere/findable-ui/lib/components/common/StaticImage/staticImage"; +import { + ANCHOR_TARGET, + REL_ATTRIBUTE, +} from "@databiosphere/findable-ui/lib/components/Links/common/entities"; +import { TEXT_BODY_400 } from "@databiosphere/findable-ui/lib/theme/common/typography"; +import { Typography } from "@mui/material"; +import { Fragment } from "react"; +import { StyledButtonBase } from "./analysisPortals.styles"; + +export interface AnalysisPortals { + imageProps: StaticImageProps; + label: string; + url: string; +} + +interface AnalysisPortalsProps { + portals: AnalysisPortals[]; +} + +export const AnalysisPortals = ({ + portals, +}: AnalysisPortalsProps): JSX.Element => { + if (portals.length === 0) return None; + return ( + + {portals.map(({ imageProps, label, url }) => ( + { + window.open( + url, + ANCHOR_TARGET.BLANK, + REL_ATTRIBUTE.NO_OPENER_NO_REFERRER + ); + }} + > + + {label} + + ))} + + ); +}; diff --git a/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/components/BackButton/backButton.styles.ts b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/components/BackButton/backButton.styles.ts new file mode 100644 index 0000000..8d60439 --- /dev/null +++ b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/components/BackButton/backButton.styles.ts @@ -0,0 +1,10 @@ +import { mediaDesktopSmallUp } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; +import styled from "@emotion/styled"; +import { HeroActions as DetailViewActions } from "../../detailViewHero.styles"; + +export const HeroActions = styled(DetailViewActions)` + ${mediaDesktopSmallUp} { + align-self: flex-start; + margin: 8px 0; + } +`; diff --git a/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/components/BackButton/backButton.tsx b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/components/BackButton/backButton.tsx new file mode 100644 index 0000000..4adab81 --- /dev/null +++ b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/components/BackButton/backButton.tsx @@ -0,0 +1,32 @@ +import { SouthIcon } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/components/SouthIcon/southIcon"; +import { IconButton } from "@mui/material"; +import { useRouter } from "next/router"; +import { useCallback } from "react"; +import { HeroActions } from "./backButton.styles"; + +export const BackButton = (): JSX.Element => { + const { asPath, push } = useRouter(); + const onNavigate = useCallback( + () => push(getNextPath(asPath)), + [asPath, push] + ); + return ( + + + + + + ); +}; + +/** + * Returns the next path to navigate to when the back button is clicked. + * The back button will navigate to the parent path of the current path. + * @param asPath - Current path. + * @returns next path. + */ +function getNextPath(asPath: string): string { + const path = asPath.split("/"); + path.pop(); + return path.join("/"); +} diff --git a/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/detailViewHero.styles.ts b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/detailViewHero.styles.ts new file mode 100644 index 0000000..41126ce --- /dev/null +++ b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/detailViewHero.styles.ts @@ -0,0 +1,60 @@ +import { + mediaDesktopSmallUp, + mediaTabletUp, +} from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; +import styled from "@emotion/styled"; + +export const DetailViewHero = styled.div` + display: grid; + gap: 16px; + grid-column: 1 / -1; + margin-bottom: 8px; +`; + +export const DetailViewHeroHeadline = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + + ${mediaDesktopSmallUp} { + flex-direction: row; + } +`; + +export const HeroHeader = styled.div` + display: flex; + flex: 1; + flex-direction: column; + gap: 4px; +`; + +export const HeroTitle = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + + ${mediaTabletUp} { + align-items: center; + flex-direction: row; + } + + .MuiTypography-text-heading { + max-width: 726px; + } +`; + +export const Titles = styled.div` + display: grid; + gap: 4px; +`; + +export const HeroActions = styled.div` + align-items: center; + align-self: flex-start; + display: flex; + gap: 8px; + + ${mediaDesktopSmallUp} { + align-self: center; + } +`; diff --git a/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/detailViewHero.tsx b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/detailViewHero.tsx new file mode 100644 index 0000000..db1fe88 --- /dev/null +++ b/data-catalog/app/components/Layout/components/Detail/components/DetailViewHero/detailViewHero.tsx @@ -0,0 +1,51 @@ +import { BackPageTabs } from "@databiosphere/findable-ui/lib/components/Layout/components/BackPage/backPageView.styles"; +import { SubTitle } from "@databiosphere/findable-ui/lib/components/Layout/components/BackPage/components/BackPageHero/components/SubTitle/subTitle"; +import { TEXT_HEADING } from "@databiosphere/findable-ui/lib/theme/common/typography"; +import { HeroTitle as Typography } from "@databiosphere/findable-ui/src/components/common/Title/title.styles"; +import { ReactNode } from "react"; +import { BackButton } from "./components/BackButton/backButton"; +import { + DetailViewHero as DetailViewHeroLayout, + DetailViewHeroHeadline, + HeroHeader, + HeroTitle, + Titles, +} from "./detailViewHero.styles"; + +export interface DetailViewHeroProps { + breadcrumbs?: ReactNode; + subTitle?: ReactNode; + tabs?: ReactNode; + title?: ReactNode; +} + +export const DetailViewHero = ({ + breadcrumbs, + subTitle, + tabs, + title, +}: DetailViewHeroProps): JSX.Element => { + return ( + + {(breadcrumbs || title) && ( + + + + {breadcrumbs} + + + {title && ( + + {title} + + )} + {subTitle && } + + + + + )} + {tabs && {tabs}} + + ); +}; diff --git a/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/analyzeGenome.styles.ts b/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/analyzeGenome.styles.ts new file mode 100644 index 0000000..6df771f --- /dev/null +++ b/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/analyzeGenome.styles.ts @@ -0,0 +1,8 @@ +import { ButtonGroup } from "@databiosphere/findable-ui/lib/components/common/ButtonGroup/buttonGroup"; +import styled from "@emotion/styled"; + +export const StyledButtonGroup = styled(ButtonGroup)` + .MuiButton-sizeSmall { + padding: 8px; + } +`; diff --git a/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/analyzeGenome.tsx b/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/analyzeGenome.tsx new file mode 100644 index 0000000..6f79885 --- /dev/null +++ b/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/analyzeGenome.tsx @@ -0,0 +1,57 @@ +import { + ANCHOR_TARGET, + REL_ATTRIBUTE, +} from "@databiosphere/findable-ui/lib/components/Links/common/entities"; +import { Button } from "@mui/material"; +import Router from "next/router"; +import { ROUTES } from "../../../../../../../routes/contants"; +import { BRCDataCatalogGenome } from "../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; +import { AnalyzeGenomeIcon } from "../../../../../common/CustomIcon/components/AnalyzeGenomeIcon/analyzeGenomeIcon"; +import { ViewGenomeIcon } from "../../../../../common/CustomIcon/components/ViewGenomeIcon/viewGenomeIcon"; +import { StyledButtonGroup } from "./analyzeGenome.styles"; +import { + BUTTON_GROUP_PROPS, + BUTTON_PROPS, + ICON_PROPS, +} from "./common/constants"; + +export interface AnalyzeGenomeProps { + genome: BRCDataCatalogGenome; +} + +export const AnalyzeGenome = ({ genome }: AnalyzeGenomeProps): JSX.Element => { + const { genomeVersionAssemblyId, ucscBrowserUrl } = genome; + + const onAnalyze = (entityId: string): void => { + Router.push(`${ROUTES.GENOMES}/${entityId}`); + }; + + const onView = (url: string | null): void => { + if (!url) return; + window.open(url, ANCHOR_TARGET.BLANK, REL_ATTRIBUTE.NO_OPENER_NO_REFERRER); + }; + + return ( + onAnalyze(genomeVersionAssemblyId)} + > + + , + , + ]} + /> + ); +}; diff --git a/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/common/constants.ts b/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/common/constants.ts new file mode 100644 index 0000000..b8fa568 --- /dev/null +++ b/data-catalog/app/components/Table/components/TableCell/components/AnalyzeGenome/common/constants.ts @@ -0,0 +1,20 @@ +import { CustomSVGIconProps } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/common/entities"; +import { + ButtonGroupProps as MButtonGroupProps, + ButtonProps, +} from "@mui/material"; + +export const BUTTON_PROPS: Partial = { + color: "secondary", + variant: "contained", +}; + +export const BUTTON_GROUP_PROPS: Partial = { + color: "secondary", + variant: "outlined", +}; + +export const ICON_PROPS: Partial = { + color: "inkLight", + fontSize: "small", +}; diff --git a/data-catalog/app/components/Table/table.styles.ts b/data-catalog/app/components/Table/table.styles.ts new file mode 100644 index 0000000..971764b --- /dev/null +++ b/data-catalog/app/components/Table/table.styles.ts @@ -0,0 +1,40 @@ +import { Table as DXTable } from "@databiosphere/findable-ui/lib/components/Detail/components/Table/table"; +import { mediaTabletUp } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; +import { smokeLightest } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors"; +import { ThemeProps } from "@databiosphere/findable-ui/lib/theme/theme"; +import { css } from "@emotion/react"; +import styled from "@emotion/styled"; + +export const tableStyles = (props: ThemeProps) => css` + .MuiTable-root { + .MuiTableHead-root { + .MuiTableRow-root { + .MuiTableCell-root { + background-color: ${smokeLightest(props)}; + } + } + } + + ${mediaTabletUp(props)} { + .MuiTableHead-root, + .MuiTableBody-root { + .MuiTableRow-root { + .MuiTableCell-root { + min-height: 56px; + padding: 10px 16px; + + &.MuiTableCell-paddingCheckbox { + padding-right: 0; + } + } + } + } + } + } +`; + +export const Table = styled(DXTable)` + &.MuiTableContainer-root { + ${tableStyles} + } +` as typeof DXTable; diff --git a/data-catalog/app/components/common/CustomIcon/components/AnalyzeGenomeIcon/analyzeGenomeIcon.tsx b/data-catalog/app/components/common/CustomIcon/components/AnalyzeGenomeIcon/analyzeGenomeIcon.tsx new file mode 100644 index 0000000..e06b060 --- /dev/null +++ b/data-catalog/app/components/common/CustomIcon/components/AnalyzeGenomeIcon/analyzeGenomeIcon.tsx @@ -0,0 +1,21 @@ +import { CustomSVGIconProps } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/common/entities"; +import { SvgIcon } from "@mui/material"; + +/** + * Custom analyze genome icon. + */ + +export const AnalyzeGenomeIcon = ({ + fontSize = "xsmall", + viewBox = "0 0 18 18", + ...props /* Spread props to allow for Mui SvgIconProps specific prop overrides e.g. "htmlColor". */ +}: CustomSVGIconProps): JSX.Element => { + return ( + + + + ); +}; diff --git a/data-catalog/app/components/common/CustomIcon/components/ViewGenomeIcon/viewGenomeIcon.tsx b/data-catalog/app/components/common/CustomIcon/components/ViewGenomeIcon/viewGenomeIcon.tsx new file mode 100644 index 0000000..4e1d466 --- /dev/null +++ b/data-catalog/app/components/common/CustomIcon/components/ViewGenomeIcon/viewGenomeIcon.tsx @@ -0,0 +1,23 @@ +import { CustomSVGIconProps } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/common/entities"; +import { SvgIcon } from "@mui/material"; + +/** + * Custom view genome icon. + */ + +export const ViewGenomeIcon = ({ + fontSize = "xsmall", + viewBox = "0 0 18 18", + ...props /* Spread props to allow for Mui SvgIconProps specific prop overrides e.g. "htmlColor". */ +}: CustomSVGIconProps): JSX.Element => { + return ( + + + + ); +}; diff --git a/data-catalog/app/components/index.ts b/data-catalog/app/components/index.ts index 7bf33c0..2bac2f2 100644 --- a/data-catalog/app/components/index.ts +++ b/data-catalog/app/components/index.ts @@ -1,2 +1,25 @@ +export { Breadcrumbs } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; +export { Grid } from "@databiosphere/findable-ui/lib/components/common/Grid/grid"; +export { KeyElType } from "@databiosphere/findable-ui/lib/components/common/KeyValuePairs/components/KeyElType/keyElType"; +export { KeyValueElType } from "@databiosphere/findable-ui/lib/components/common/KeyValuePairs/components/KeyValueElType/keyValueElType"; +export { ValueElType } from "@databiosphere/findable-ui/lib/components/common/KeyValuePairs/components/ValueElType/valueElType"; +export { KeyValuePairs } from "@databiosphere/findable-ui/lib/components/common/KeyValuePairs/keyValuePairs"; +export { + FluidPaper, + GridPaper, +} from "@databiosphere/findable-ui/lib/components/common/Paper/paper.styles"; +export { SectionTitle } from "@databiosphere/findable-ui/lib/components/common/Section/components/SectionTitle/sectionTitle"; +export { GridPaperSection } from "@databiosphere/findable-ui/lib/components/common/Section/section.styles"; +export { Stack } from "@databiosphere/findable-ui/lib/components/common/Stack/stack"; +export { StaticImage } from "@databiosphere/findable-ui/lib/components/common/StaticImage/staticImage"; +export { + BackPageContentMainColumn, + BackPageContentSideColumn, + BackPageContentSingleColumn, +} from "@databiosphere/findable-ui/lib/components/Layout/components/BackPage/backPageView.styles"; export { Link } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link"; export { BasicCell } from "@databiosphere/findable-ui/lib/components/Table/components/TableCell/components/BasicCell/basicCell"; +export { AnalysisMethod } from "./Entity/components/AnalysisMethod/analysisMethod"; +export { AnalysisPortals } from "./Entity/components/AnalysisPortals/analysisPortals"; +export { DetailViewHero } from "./Layout/components/Detail/components/DetailViewHero/detailViewHero"; +export { AnalyzeGenome } from "./Table/components/TableCell/components/AnalyzeGenome/analyzeGenome"; diff --git a/data-catalog/app/config/config.ts b/data-catalog/app/config/config.ts index 66608ea..b10f30f 100644 --- a/data-catalog/app/config/config.ts +++ b/data-catalog/app/config/config.ts @@ -1,6 +1,7 @@ import { setConfig } from "@databiosphere/findable-ui/lib/config/config"; import { SiteConfig } from "@databiosphere/findable-ui/lib/config/entities"; import brcAnalyticsCatalogLocal from "../../site-config/brc-analytics-catalog/local/config"; + const CONFIGS: { [k: string]: SiteConfig } = { "brc-analytics-catalog-local": brcAnalyticsCatalogLocal, }; diff --git a/data-catalog/app/utils/seedDatabase.ts b/data-catalog/app/utils/seedDatabase.ts new file mode 100644 index 0000000..29c739e --- /dev/null +++ b/data-catalog/app/utils/seedDatabase.ts @@ -0,0 +1,36 @@ +import { EntityConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import { database } from "@databiosphere/findable-ui/lib/utils/database"; +import fsp from "fs/promises"; + +/** + * Seed database. + * @param entityListType - Entity list type. + * @param entityConfig - Entity config. + * @returns Promise. + */ +export const seedDatabase = async function seedDatabase( + entityListType: string, + entityConfig: EntityConfig +): Promise { + const { entityMapper, label, staticLoadFile } = entityConfig; + + if (!staticLoadFile) { + throw new Error(`staticLoadFile not found for entity ${label}`); + } + + // Build database from configured JSON, if any. + let jsonText; + try { + jsonText = await fsp.readFile(staticLoadFile, "utf8"); + } catch (e) { + throw new Error(`File ${staticLoadFile} not found for entity ${label}`); + } + + const object = JSON.parse(jsonText); + const entities = entityMapper + ? Object.values(object).map(entityMapper) + : Object.values(object); // Client-side fetched entities are mapped prior to dispatch to explore state. + + // Seed entities. + database.get().seed(entityListType, entities); +}; diff --git a/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/constants.ts b/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/constants.ts new file mode 100644 index 0000000..bdfc20f --- /dev/null +++ b/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/constants.ts @@ -0,0 +1 @@ +export const GENOME_BROWSER = "Genome Browser"; diff --git a/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders.ts b/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders.ts index 730f465..884066b 100644 --- a/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders.ts +++ b/data-catalog/app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders.ts @@ -1,5 +1,27 @@ +import { Breadcrumb } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; +import { CardProps } from "@databiosphere/findable-ui/lib/components/common/Card/card"; +import { + Key, + Value, +} from "@databiosphere/findable-ui/lib/components/common/KeyValuePairs/keyValuePairs"; +import { ComponentProps } from "react"; +import { ROUTES } from "../../../../../routes/contants"; import { BRCDataCatalogGenome } from "../../../../apis/catalog/brc-analytics-catalog/common/entities"; import * as C from "../../../../components/index"; +import { GENOME_BROWSER } from "./constants"; + +/** + * Build props for the genome analysis cell. + * @param genome - Genome entity. + * @returns Props to be used for the AnalyzeGenome component. + */ +export const buildAnalyzeGenome = ( + genome: BRCDataCatalogGenome +): ComponentProps => { + return { + genome, + }; +}; /** * Build props for the chromosomes cell. @@ -8,7 +30,7 @@ import * as C from "../../../../components/index"; */ export const buildChromosomes = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { value: genome.chromosomes, }; @@ -21,12 +43,94 @@ export const buildChromosomes = ( */ export const buildContigs = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { value: genome.contigs, }; }; +/** + * Build props for the genome AnalysisMethod component. + * @param genome - Genome entity. + * @param cardProps - Card properties. + * @param cardProps.text - Card text. + * @param cardProps.title - Card title. + * @returns Props to be used for the AnalysisMethod component. + */ +export const buildGenomeAnalysisMethod = ( + genome: BRCDataCatalogGenome, + { text, title }: Partial +): ComponentProps => { + return { + text, + title, + url: "", + }; +}; + +/** + * Build props for the genome AnalysisPortals component. + * @param genome - Genome entity. + * @returns Props to be used for the AnalysisPortals component. + */ +export const buildGenomeAnalysisPortals = ( + genome: BRCDataCatalogGenome +): ComponentProps => { + return { + portals: [ + { + imageProps: { + alt: GENOME_BROWSER, + src: "/analysis-portals/ucsc-genome.svg", + width: 20, + }, + label: GENOME_BROWSER, + url: genome.ucscBrowserUrl, + }, + ], + }; +}; + +/** + * Build props for the genome DetailViewHero component. + * @param genome - Genome entity. + * @returns Props to be used for the DetailViewHero component. + */ +export const buildGenomeChooseAnalysisMethodDetailViewHero = ( + genome: BRCDataCatalogGenome +): ComponentProps => { + return { + breadcrumbs: C.Breadcrumbs({ + breadcrumbs: getGenomeEntityChooseAnalysisMethodBreadcrumbs(genome), + }), + title: "Choose Analysis Methods", + }; +}; + +/** + * Build props for the genome detail KeyValuePairs component. + * @param genome - Genome entity. + * @returns Props to be used for the KeyValuePairs component. + */ +export const buildGenomeDetails = ( + genome: BRCDataCatalogGenome +): ComponentProps => { + const keyValuePairs = new Map(); + keyValuePairs.set("Species", genome.species); + keyValuePairs.set("Strain", genome.strain); + keyValuePairs.set("Genome Version", genome.genomeVersionAssemblyId); + keyValuePairs.set("VeUPathDB Project", genome.vEuPathDbProject); + keyValuePairs.set("Contigs", genome.contigs); + keyValuePairs.set("Super Contigs", genome.supercontigs); + keyValuePairs.set("Chromosomes", genome.chromosomes); + return { + KeyElType: C.KeyElType, + KeyValuesElType: (props) => C.Stack({ gap: 4, ...props }), + ValueElType: C.ValueElType, + keyValuePairs, + }; +}; + /** * Build props for the genome version/assembly ID cell. * @param genome - Genome entity. @@ -34,7 +138,7 @@ export const buildContigs = ( */ export const buildGenomeVersionAssemblyId = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { value: genome.genomeVersionAssemblyId, }; @@ -47,7 +151,7 @@ export const buildGenomeVersionAssemblyId = ( */ export const buildSpecies = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { value: genome.species, }; @@ -60,7 +164,7 @@ export const buildSpecies = ( */ export const buildStrain = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { value: genome.strain, }; @@ -73,35 +177,36 @@ export const buildStrain = ( */ export const buildSupercontigs = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { value: genome.supercontigs, }; }; /** - * Build props for the UCSC browser URL cell. + * Build props for the VEuPathDB project cell. * @param genome - Genome entity. * @returns Props to be used for the cell. */ -export const buildUcscBrowserUrl = ( +export const buildVEuPathDbProject = ( genome: BRCDataCatalogGenome -): React.ComponentProps => { +): ComponentProps => { return { - label: "UCSC Browser", - url: genome.ucscBrowserUrl, + value: genome.vEuPathDbProject, }; }; /** - * Build props for the VEuPathDB project cell. + * Get the genome entity breadcrumbs. * @param genome - Genome entity. - * @returns Props to be used for the cell. + * @returns Breadcrumbs. */ -export const buildVEuPathDbProject = ( +function getGenomeEntityChooseAnalysisMethodBreadcrumbs( genome: BRCDataCatalogGenome -): React.ComponentProps => { - return { - value: genome.vEuPathDbProject, - }; -}; +): Breadcrumb[] { + return [ + { path: ROUTES.GENOMES, text: "Genomes" }, + { path: "", text: `${genome.species} - ${genome.strain}` }, + { path: "", text: "Choose Analysis Methods" }, + ]; +} diff --git a/data-catalog/app/views/EntityView/entityView.tsx b/data-catalog/app/views/EntityView/entityView.tsx new file mode 100644 index 0000000..36595c1 --- /dev/null +++ b/data-catalog/app/views/EntityView/entityView.tsx @@ -0,0 +1,54 @@ +import { ComponentCreator } from "@databiosphere/findable-ui/lib/components/ComponentCreator/ComponentCreator"; +import { Detail } from "@databiosphere/findable-ui/lib/components/Detail/detail"; +import { useCurrentDetailTab } from "@databiosphere/findable-ui/lib/hooks/useCurrentDetailTab"; +import { useEntityHeadTitle } from "@databiosphere/findable-ui/lib/hooks/useEntityHeadTitle"; +import { useFetchEntity } from "@databiosphere/findable-ui/lib/hooks/useFetchEntity"; +import { ComponentsConfig } from "@databiosphere/findable-ui/src/config/entities"; +import Head from "next/head"; +import { Fragment } from "react"; +import { BRCBackPageTabConfig } from "../../../site-config/common/entities"; + +export interface EntityViewProps { + data?: R; + entityListType: string; +} + +export const EntityDetailView = ( + props: EntityViewProps +): JSX.Element => { + const { currentTab } = useCurrentDetailTab(); + const { response } = useFetchEntity(props); + const { mainColumn, sideColumn, top } = currentTab as BRCBackPageTabConfig; + const title = useEntityHeadTitle(response); + return ( + + {title && ( + + {title} + + )} + + + ); +}; + +/** + * Renders components with given response data. + * @param components - Components configuration. + * @param response - Response. + * @returns rendered components. + */ +function renderComponents( + components?: ComponentsConfig, + response?: R +): JSX.Element | null { + if (!response) return null; + if (!components) return null; + return ; +} diff --git a/data-catalog/app/views/ExploreView/exploreView.styles.ts b/data-catalog/app/views/ExploreView/exploreView.styles.ts new file mode 100644 index 0000000..9db5ae4 --- /dev/null +++ b/data-catalog/app/views/ExploreView/exploreView.styles.ts @@ -0,0 +1,25 @@ +import { mediaTabletDown } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; +import { ExploreView as DXExploreView } from "@databiosphere/findable-ui/lib/views/ExploreView/exploreView"; +import styled from "@emotion/styled"; +import { tableStyles } from "../../components/Table/table.styles"; + +export const StyledExploreView = styled(DXExploreView)` + & .MuiToolbar-root.MuiToolbar-table { + padding: 16px; + + .MuiButton-containedSecondary { + padding-bottom: 8px; + padding-top: 8px; + } + } + + & .MuiTableContainer-root { + ${tableStyles}; + } + + ${mediaTabletDown} { + .MuiPaper-table { + min-height: 64px; + } + } +`; diff --git a/data-catalog/files/build-genomes-files.py b/data-catalog/files/build-genomes-files.py index 43ccf86..7cf4d0f 100644 --- a/data-catalog/files/build-genomes-files.py +++ b/data-catalog/files/build-genomes-files.py @@ -7,14 +7,23 @@ OUTPUT_PATH = "files/source/genomes.tsv" +def get_duplicate_ids(genomes_df): + counts = genomes_df["Genome Version/Assembly ID"].value_counts() + return list(counts.index.to_series().loc[counts > 1]) + def build_genomes_files(): print("Building files") genomes_source_df = pd.read_csv(GENOMES_SOURCE_URL, keep_default_na=False, usecols=lambda name: re.fullmatch(r"Unnamed: \d+", name) is None) assemblies_df = pd.DataFrame(requests.get(ASSEMBLIES_URL).json()["data"]) + + duplicate_ids = get_duplicate_ids(genomes_source_df) + print(f"Removing rows with duplicate Genome Version/Assembly ID values of: {", ".join(duplicate_ids)}") + + deduped_genomes_df = genomes_source_df.drop_duplicates(subset=["Genome Version/Assembly ID"]) - gen_bank_merge_df = genomes_source_df.merge(assemblies_df, how="left", left_on="Genome Version/Assembly ID", right_on="genBank") - ref_seq_merge_df = genomes_source_df.merge(assemblies_df, how="left", left_on="Genome Version/Assembly ID", right_on="refSeq") + gen_bank_merge_df = deduped_genomes_df.merge(assemblies_df, how="left", left_on="Genome Version/Assembly ID", right_on="genBank") + ref_seq_merge_df = deduped_genomes_df.merge(assemblies_df, how="left", left_on="Genome Version/Assembly ID", right_on="refSeq") result_df = gen_bank_merge_df.combine_first(ref_seq_merge_df).dropna(subset=["ucscBrowser"]) diff --git a/data-catalog/files/out/genomes.json b/data-catalog/files/out/genomes.json index b6136db..f39d0a6 100644 --- a/data-catalog/files/out/genomes.json +++ b/data-catalog/files/out/genomes.json @@ -1187,28 +1187,6 @@ "ucscBrowserUrl": "https://genome.ucsc.edu/h/GCF_000209065.1", "vEuPathDbProject": "TriTrypDB" }, - { - "chromosomes": 41, - "contigs": 0, - "genomeVersionAssemblyId": "GCA_000209065.1", - "organism": "Trypanosoma cruzi CL Brener Esmeraldo-like", - "species": "Trypanosoma cruzi", - "strain": "CL Brener Esmeraldo-like", - "supercontigs": 0, - "ucscBrowserUrl": "https://genome.ucsc.edu/h/GCF_000209065.1", - "vEuPathDbProject": "TriTrypDB" - }, - { - "chromosomes": 41, - "contigs": 0, - "genomeVersionAssemblyId": "GCA_000209065.1", - "organism": "Trypanosoma cruzi CL Brener Non-Esmeraldo-like", - "species": "Trypanosoma cruzi", - "strain": "CL Brener Non-Esmeraldo-like", - "supercontigs": 0, - "ucscBrowserUrl": "https://genome.ucsc.edu/h/GCF_000209065.1", - "vEuPathDbProject": "TriTrypDB" - }, { "chromosomes": 0, "contigs": 0, diff --git a/data-catalog/files/source/genomes.tsv b/data-catalog/files/source/genomes.tsv index 23923d4..0365711 100644 --- a/data-catalog/files/source/genomes.tsv +++ b/data-catalog/files/source/genomes.tsv @@ -522,8 +522,6 @@ Trypanosoma cruzi Berenice INSDC GCA_013358655.1 no 0 923 0 Trypanosoma cruzi Be Trypanosoma cruzi Brazil A4 GenBank GCA_015033625.1 no 359 0 43 Trypanosoma cruzi Brazil A4 TriTrypDB 5693.0 GCA_015033625.1_ASM1503362v1 GCA_015033625.1 False Trypanosoma cruzi Trypanosoma cruzi (Brazil clone A4 2020) https://genome.ucsc.edu/h/GCA_015033625.1 Trypanosoma cruzi Bug2148 GenBank GCA_002749415.1 no 929 0 0 Trypanosoma cruzi Bug2148 TriTrypDB 5693.0 GCA_002749415.1_ASM274941v1 GCA_002749415.1 False Trypanosoma cruzi Trypanosoma cruzi (Bug2148 2017) https://genome.ucsc.edu/h/GCA_002749415.1 Trypanosoma cruzi strain CL INSDC GCA_003719155.1 no 0 7764 0 Trypanosoma cruzi strain CL TriTrypDB 5693.0 GCA_003719155.1_ASM371915v1 GCA_003719155.1 False Trypanosoma cruzi Trypanosoma cruzi (CL 2018) https://genome.ucsc.edu/h/GCA_003719155.1 -Trypanosoma cruzi CL Brener Esmeraldo-like GenBank GCA_000209065.1 yes 0 0 41 Trypanosoma cruzi CL Brener Esmeraldo-like TriTrypDB 5693.0 GCF_000209065.1_ASM20906v1 GCA_000209065.1 GCF_000209065.1 True Trypanosoma cruzi Trypanosoma cruzi (CL Brener 2005 kinetoplastids) https://genome.ucsc.edu/h/GCF_000209065.1 -Trypanosoma cruzi CL Brener Non-Esmeraldo-like GenBank GCA_000209065.1 no 0 0 41 Trypanosoma cruzi CL Brener Non-Esmeraldo-like TriTrypDB 5693.0 GCF_000209065.1_ASM20906v1 GCA_000209065.1 GCF_000209065.1 True Trypanosoma cruzi Trypanosoma cruzi (CL Brener 2005 kinetoplastids) https://genome.ucsc.edu/h/GCF_000209065.1 Trypanosoma cruzi Dm28c 2017 GenBank GCA_002219105.2 no 0 1029 0 Trypanosoma cruzi Dm28c 2017 TriTrypDB 85057.0 GCA_002219105.2_TcruziDm28cPB1 GCA_002219105.2 False Trypanosoma cruzi cruzi Trypanosoma cruzi cruzi (Dm28c 2017) https://genome.ucsc.edu/h/GCA_002219105.2 Trypanosoma cruzi Dm28c 2018 GenBank GCA_003177105.1 no 636 0 0 Trypanosoma cruzi Dm28c 2018 TriTrypDB 5693.0 GCA_003177105.1_ASM317710v1 GCA_003177105.1 False Trypanosoma cruzi Trypanosoma cruzi (Dm28c 2018) https://genome.ucsc.edu/h/GCA_003177105.1 Trypanosoma cruzi strain G INSDC GCA_003719455.1 no 0 1450 0 Trypanosoma cruzi strain G TriTrypDB 5693.0 GCA_003719455.1_ASM371945v1 GCA_003719455.1 False Trypanosoma cruzi Trypanosoma cruzi (G 2018) https://genome.ucsc.edu/h/GCA_003719455.1 diff --git a/data-catalog/pages/[entityListType]/[...params].tsx b/data-catalog/pages/[entityListType]/[...params].tsx new file mode 100644 index 0000000..65108d3 --- /dev/null +++ b/data-catalog/pages/[entityListType]/[...params].tsx @@ -0,0 +1,221 @@ +import { + PARAMS_INDEX_TAB, + PARAMS_INDEX_UUID, +} from "@databiosphere/findable-ui/lib/common/constants"; +import { + BackPageTabConfig, + EntityConfig, +} from "@databiosphere/findable-ui/lib/config/entities"; +import { getEntityConfig } from "@databiosphere/findable-ui/lib/config/utils"; +import { getEntityService } from "@databiosphere/findable-ui/lib/hooks/useEntityService"; +import { EXPLORE_MODE } from "@databiosphere/findable-ui/lib/hooks/useExploreMode"; +import { config } from "app/config/config"; +import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next"; +import { ParsedUrlQuery } from "querystring"; +import { + BRCCatalog, + EntitiesResponse, +} from "../../app/apis/catalog/brc-analytics-catalog/common/entities"; +import { seedDatabase } from "../../app/utils/seedDatabase"; +import { EntityDetailView } from "../../app/views/EntityView/entityView"; + +const setOfProcessedIds = new Set(); + +interface StaticPath { + params: PageUrl; +} + +interface PageUrl extends ParsedUrlQuery { + entityListType: string; + params: string[]; +} + +export interface EntityPageProps { + data?: R; + entityListType: string; +} + +/** + * Entity detail view page. + * @param props - Entity detail view page props. + * @returns Entity detail view component. + */ +const EntityDetailPage = (props: EntityPageProps): JSX.Element => { + if (!props.entityListType) return <>; + return ; +}; + +/** + * getStaticPaths - return the list of paths to prerender for each entity type and its tabs. + * @returns Promise>. + */ +export const getStaticPaths: GetStaticPaths = async () => { + const appConfig = config(); + const { entities } = appConfig; + + const paths: StaticPath[] = []; + + for (const entityConfig of entities) { + const { exploreMode, route: entityListType } = entityConfig; + // Process static paths. + // Client-side fetch, client-side filtering. + if (exploreMode === EXPLORE_MODE.CS_FETCH_CS_FILTERING) { + await seedDatabase(entityListType, entityConfig); + const entitiesResponse: EntitiesResponse = await getEntities( + entityConfig + ); + processEntityPaths(entityConfig, entitiesResponse, paths); + } + } + + return { + fallback: false, + paths, + }; +}; + +export const getStaticProps: GetStaticProps< + EntityPageProps +> = async ({ params }: GetStaticPropsContext) => { + const appConfig = config(); + const { entities } = appConfig; + const { entityListType, params: slug } = params || {}; + + if (typeof entityListType !== "string") return { notFound: true }; + if (!Array.isArray(slug)) return { notFound: true }; + + const entityConfig = getEntityConfig(entities, entityListType); + const entityTab = getSlugPath(slug, PARAMS_INDEX_TAB); + const entityId = getSlugPath(slug, PARAMS_INDEX_UUID); + + if (!entityConfig || !entityId) return { notFound: true }; + + const props: EntityPageProps = { entityListType }; + + // Process entity props. + await processEntityProps(entityConfig, entityTab, entityId, props); + + return { + props, + }; +}; + +export default EntityDetailPage; + +/** + * Fetches entities response for the given entity config. + * @param entityConfig - Entity config. + * @returns entities response. + */ +async function getEntities( + entityConfig: EntityConfig +): Promise> { + const { fetchAllEntities, path } = getEntityService(entityConfig, undefined); + return await fetchAllEntities(path, undefined, undefined, undefined); +} + +/** + * Fetches the entity for the given entity ID. + * @param entityConfig - Entity config. + * @param entityId - Entity ID. + * @returns entity response. + */ +async function getEntity( + entityConfig: EntityConfig, + entityId: string +): Promise { + const { fetchEntityDetail, path } = getEntityService(entityConfig, undefined); + return await fetchEntityDetail( + entityId, + path, + undefined, + undefined, + undefined, + true + ); +} + +/** + * Returns the slug path for the given slug and slug index. + * @param slug - Slug. + * @param slugIndex - Slug index. + * @returns path. + */ +function getSlugPath(slug: string[], slugIndex: number): string | undefined { + return slug[slugIndex]; +} + +/** + * Returns the list of tab routes for the given tab config. + * @param tabs - Tab config. + * @returns tab routes. + */ +function getTabRoutes(tabs: BackPageTabConfig[]): string[] { + return tabs.map(({ route }) => route) ?? []; +} + +/** + * Processes the static paths for the given entity response. + * @param entityConfig - Entity config. + * @param entitiesResponse - Entities response. + * @param paths - Static paths. + */ +function processEntityPaths( + entityConfig: EntityConfig, + entitiesResponse: EntitiesResponse, + paths: StaticPath[] +): void { + const { detail, route: entityListType } = entityConfig; + const { tabs } = detail; + const { hits: entities } = entitiesResponse; + const tabRoutes = getTabRoutes(tabs); + for (const entity of entities) { + const entityId = entityConfig.getId?.(entity); + if (!entityId) continue; + // Skip the entity if it has already been processed. + if (setOfProcessedIds.has(entityId)) continue; + setOfProcessedIds.add(entityId); + // Generate a path for each entity and each tab. + for (const tabRoute of tabRoutes) { + const params = [entityId, tabRoute]; + paths.push({ + params: { + entityListType, + params, + }, + }); + } + } +} + +/** + * Processes the entity props for the given entity page. + * @param entityConfig - Entity config. + * @param entityTab - Entity tab. + * @param entityId - Entity ID. + * @param props - Entity detail page props. + */ +async function processEntityProps( + entityConfig: EntityConfig, + entityTab = "", + entityId: string, + props: EntityPageProps +): Promise { + const { + detail: { staticLoad }, + exploreMode, + } = entityConfig; + // Early exit; return if the entity is not to be statically loaded. + if (!staticLoad) return; + // When the entity detail is to be fetched from API, we only do so for the first tab. + if (exploreMode === EXPLORE_MODE.SS_FETCH_SS_FILTERING && entityTab) return; + if (exploreMode === EXPLORE_MODE.CS_FETCH_CS_FILTERING) { + // Seed database. + await seedDatabase(entityConfig.route, entityConfig); + } + // Fetch entity detail, either from database or API. + const entityResponse = (await getEntity(entityConfig, entityId)) as R; + if (entityResponse) { + props.data = entityResponse; + } +} diff --git a/data-catalog/pages/[entityListType]/index.tsx b/data-catalog/pages/[entityListType]/index.tsx index 2ba18e6..5ddac9c 100644 --- a/data-catalog/pages/[entityListType]/index.tsx +++ b/data-catalog/pages/[entityListType]/index.tsx @@ -1,67 +1,38 @@ -import { AzulEntitiesStaticResponse } from "@databiosphere/findable-ui/lib/apis/azul/common/entities"; import { Main as DXMain } from "@databiosphere/findable-ui/lib/components/Layout/components/Main/main.styles"; -import { EntityConfig } from "@databiosphere/findable-ui/lib/config/entities"; import { getEntityConfig } from "@databiosphere/findable-ui/lib/config/utils"; import { getEntityService } from "@databiosphere/findable-ui/lib/hooks/useEntityService"; import { EXPLORE_MODE } from "@databiosphere/findable-ui/lib/hooks/useExploreMode"; -import { database } from "@databiosphere/findable-ui/lib/utils/database"; -import { ExploreView } from "@databiosphere/findable-ui/lib/views/ExploreView/exploreView"; import { config } from "app/config/config"; -import fsp from "fs/promises"; import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next"; import { ParsedUrlQuery } from "querystring"; +import { + BRCCatalog, + EntitiesResponse, +} from "../../app/apis/catalog/brc-analytics-catalog/common/entities"; +import { seedDatabase } from "../../app/utils/seedDatabase"; +import { StyledExploreView } from "../../app/views/ExploreView/exploreView.styles"; interface PageUrl extends ParsedUrlQuery { entityListType: string; } -interface ListPageProps extends AzulEntitiesStaticResponse { +interface EntitiesPageProps { + data?: EntitiesResponse; entityListType: string; } -/** - * Seed database. - * @param entityListType - Entity list type. - * @param entityConfig - Entity config. - * @returns Promise. - */ -const seedDatabase = async function seedDatabase( // TODO get rid of this duplicated code - entityListType: string, - entityConfig: EntityConfig -): Promise { - const { label, staticLoadFile } = entityConfig; - - if (!staticLoadFile) { - throw new Error(`staticLoadFile not found for entity entity ${label}`); - } - - // Build database from configured JSON, if any. - let jsonText; - try { - jsonText = await fsp.readFile(staticLoadFile, "utf8"); - } catch (e) { - throw new Error(`File ${staticLoadFile} not found for entity ${label}`); - } - - const object = JSON.parse(jsonText); - const entities = Object.values(object); // Client-side fetched entities are mapped prior to dispatch to explore state. - - // Seed entities. - database.get().seed(entityListType, entities); -}; - /** * Explore view page. * @param props - Explore view page props. * @param props.entityListType - Entity list type. * @returns ExploreView component. */ -const IndexPage = ({ +const IndexPage = ({ entityListType, ...props -}: ListPageProps): JSX.Element => { +}: EntitiesPageProps): JSX.Element => { if (!entityListType) return <>; - return ; + return ; }; /** @@ -88,7 +59,7 @@ export const getStaticPaths: GetStaticPaths = async () => { * @returns static props. */ export const getStaticProps: GetStaticProps< - AzulEntitiesStaticResponse + EntitiesPageProps > = async (context: GetStaticPropsContext) => { const appConfig = config(); const { entityListType } = context.params as PageUrl; @@ -97,7 +68,7 @@ export const getStaticProps: GetStaticProps< const { exploreMode } = entityConfig; const { fetchAllEntities } = getEntityService(entityConfig, undefined); // Determine the type of fetch, either from an API endpoint or a TSV. - const props: AzulEntitiesStaticResponse = { entityListType }; + const props: EntitiesPageProps = { entityListType }; // Seed database. if (exploreMode === EXPLORE_MODE.CS_FETCH_CS_FILTERING) { diff --git a/data-catalog/public/analysis-portals/ucsc-genome.svg b/data-catalog/public/analysis-portals/ucsc-genome.svg new file mode 100644 index 0000000..6257643 --- /dev/null +++ b/data-catalog/public/analysis-portals/ucsc-genome.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data-catalog/routes/contants.ts b/data-catalog/routes/contants.ts new file mode 100644 index 0000000..62722ce --- /dev/null +++ b/data-catalog/routes/contants.ts @@ -0,0 +1,3 @@ +export const ROUTES = { + GENOMES: "/genomes", +}; diff --git a/data-catalog/site-config/brc-analytics-catalog/category.ts b/data-catalog/site-config/brc-analytics-catalog/category.ts index 4793942..3221b0b 100644 --- a/data-catalog/site-config/brc-analytics-catalog/category.ts +++ b/data-catalog/site-config/brc-analytics-catalog/category.ts @@ -1,4 +1,5 @@ export const BRC_DATA_CATALOG_CATEGORY_KEY = { + ANALYZE_GENOME: "analyzeGenome", CHROMOSOMES: "chromosomes", CONTIGS: "contigs", GENOME_VERSION_ASSEMBLY_ID: "genomeVersionAssemblyId", @@ -10,6 +11,7 @@ export const BRC_DATA_CATALOG_CATEGORY_KEY = { }; export const BRC_DATA_CATALOG_CATEGORY_LABEL = { + ANALYZE_GENOME: "Action", CHROMOSOMES: "Chromosomes", CONTIGS: "Contigs", GENOME_VERSION_ASSEMBLY_ID: "Genome Version/Assembly ID", diff --git a/data-catalog/site-config/brc-analytics-catalog/local/config.ts b/data-catalog/site-config/brc-analytics-catalog/local/config.ts index 1eaace4..b5b1868 100644 --- a/data-catalog/site-config/brc-analytics-catalog/local/config.ts +++ b/data-catalog/site-config/brc-analytics-catalog/local/config.ts @@ -1,11 +1,27 @@ import { SiteConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import { EntityConfig } from "@databiosphere/findable-ui/src/config/entities"; +import { BRCDataCatalogGenome } from "../../../app/apis/catalog/brc-analytics-catalog/common/entities"; import { genomeEntityConfig } from "./index/genomeEntityConfig"; -// Template constants const LOCALHOST = "http://localhost:3000"; const APP_TITLE = "BRC Analytics Data Catalog"; const BROWSER_URL = LOCALHOST; +/** + * Make site config object. + * @param browserUrl - Browser URL. + * + * @remarks + * The `genomeEntityConfig` is typecast to `EntityConfig` + * because the `SiteConfig` interface from the `@databiosphere/findable-ui` package expects + * an array of entities typed as `EntityConfig`, but we have modified the EntityConfig + * locally with a custom `BRCEntityConfig` entity. To avoid rewriting + * the associated functions and providers across the codebase due to this modification, + * we perform a type cast here. This allows us to retain compatibility with the existing + * `SiteConfig` structure while accommodating the modified entity configuration. + * + * @returns site config. + */ export function makeConfig(browserUrl: string): SiteConfig { return { appTitle: APP_TITLE, @@ -13,7 +29,7 @@ export function makeConfig(browserUrl: string): SiteConfig { dataSource: { url: "", }, - entities: [genomeEntityConfig], + entities: [genomeEntityConfig as EntityConfig], explorerTitle: APP_TITLE, layout: { footer: { diff --git a/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodMainColumn.ts b/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodMainColumn.ts new file mode 100644 index 0000000..03ac283 --- /dev/null +++ b/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodMainColumn.ts @@ -0,0 +1,60 @@ +import { ComponentsConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import * as C from "../../../../../app/components"; +import * as MDX from "../../../../../app/components/Entity/components/AnalysisMethod/mdx"; +import * as V from "../../../../../app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders"; + +export const mainColumn: ComponentsConfig = [ + { + children: [ + { + component: C.AnalysisMethod, + viewBuilder: (r) => + V.buildGenomeAnalysisMethod(r, { + text: MDX.VariantCalling({}), + title: "Variant calling", + }), + }, + { + component: C.AnalysisMethod, + viewBuilder: (r) => + V.buildGenomeAnalysisMethod(r, { + text: MDX.Transcriptomics({}), + title: "Transcriptomics", + }), + }, + { + component: C.AnalysisMethod, + viewBuilder: (r) => + V.buildGenomeAnalysisMethod(r, { + text: MDX.Regulation({}), + title: "Regulation", + }), + }, + { + component: C.AnalysisMethod, + viewBuilder: (r) => + V.buildGenomeAnalysisMethod(r, { + text: MDX.Assembly({}), + title: "Assembly", + }), + }, + { + component: C.AnalysisMethod, + viewBuilder: (r) => + V.buildGenomeAnalysisMethod(r, { + text: MDX.GenomeComparisons({}), + title: "Genome comparisons", + }), + }, + { + component: C.AnalysisMethod, + viewBuilder: (r) => + V.buildGenomeAnalysisMethod(r, { + text: MDX.ProteinFolding({}), + title: "Protein folding", + }), + }, + ], + component: C.BackPageContentMainColumn, + }, +]; diff --git a/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodsSideColumn.ts b/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodsSideColumn.ts new file mode 100644 index 0000000..0615791 --- /dev/null +++ b/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodsSideColumn.ts @@ -0,0 +1,63 @@ +import { + ComponentConfig, + ComponentsConfig, +} from "@databiosphere/findable-ui/lib/config/entities"; +import { BRCDataCatalogGenome } from "../../../../../app/apis/catalog/brc-analytics-catalog/common/entities"; +import * as C from "../../../../../app/components"; +import * as V from "../../../../../app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders"; + +export const sideColumn: ComponentsConfig = [ + { + children: [ + { + children: [ + { + children: [ + { + children: [ + { + component: C.KeyValuePairs, + viewBuilder: V.buildGenomeDetails, + } as ComponentConfig< + typeof C.KeyValuePairs, + BRCDataCatalogGenome + >, + ], + component: C.GridPaperSection, + }, + { + children: [ + { + children: [ + { + component: C.SectionTitle, + props: { + title: "Analysis Portals", + }, + } as ComponentConfig, + { + component: C.AnalysisPortals, + viewBuilder: V.buildGenomeAnalysisPortals, + } as ComponentConfig< + typeof C.AnalysisPortals, + BRCDataCatalogGenome + >, + ], + component: C.Grid, + props: { + gridSx: { gap: 4 }, + }, + } as ComponentConfig, + ], + component: C.GridPaperSection, + }, + ], + component: C.GridPaper, + }, + ], + component: C.FluidPaper, + } as ComponentConfig, + ], + component: C.BackPageContentSideColumn, + }, +]; diff --git a/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodsTop.ts b/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodsTop.ts new file mode 100644 index 0000000..44df50a --- /dev/null +++ b/data-catalog/site-config/brc-analytics-catalog/local/entity/genome/analysisMethodsTop.ts @@ -0,0 +1,14 @@ +import { + ComponentConfig, + ComponentsConfig, +} from "@databiosphere/findable-ui/lib/config/entities"; +import { BRCDataCatalogGenome } from "../../../../../app/apis/catalog/brc-analytics-catalog/common/entities"; +import * as C from "../../../../../app/components"; +import * as V from "../../../../../app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders"; + +export const top: ComponentsConfig = [ + { + component: C.DetailViewHero, + viewBuilder: V.buildGenomeChooseAnalysisMethodDetailViewHero, + } as ComponentConfig, +]; diff --git a/data-catalog/site-config/brc-analytics-catalog/local/index/genomeEntityConfig.ts b/data-catalog/site-config/brc-analytics-catalog/local/index/genomeEntityConfig.ts index 092ec18..bf15ab1 100644 --- a/data-catalog/site-config/brc-analytics-catalog/local/index/genomeEntityConfig.ts +++ b/data-catalog/site-config/brc-analytics-catalog/local/index/genomeEntityConfig.ts @@ -1,23 +1,29 @@ import { ComponentConfig, - EntityConfig, ListConfig, SORT_DIRECTION, } from "@databiosphere/findable-ui/lib/config/entities"; import { EXPLORE_MODE } from "@databiosphere/findable-ui/lib/hooks/useExploreMode"; import { BRCDataCatalogGenome } from "../../../../app/apis/catalog/brc-analytics-catalog/common/entities"; -import { getGenomeId } from "../../../../app/apis/catalog/brc-analytics-catalog/common/utils"; +import { + getGenomeId, + getGenomeTitle, +} from "../../../../app/apis/catalog/brc-analytics-catalog/common/utils"; import * as C from "../../../../app/components/index"; import * as V from "../../../../app/viewModelBuilders/catalog/brc-analytics-catalog/common/viewModelBuilders"; +import { BRCEntityConfig } from "../../../common/entities"; import { BRC_DATA_CATALOG_CATEGORY_KEY, BRC_DATA_CATALOG_CATEGORY_LABEL, } from "../../category"; +import { mainColumn as analysisMethodsMainColumn } from "../entity/genome/analysisMethodMainColumn"; +import { sideColumn as analysisMethodsSideColumn } from "../entity/genome/analysisMethodsSideColumn"; +import { top as analysisMethodsTop } from "../entity/genome/analysisMethodsTop"; /** * Entity config object responsible to config anything related to the /genomes route. */ -export const genomeEntityConfig: EntityConfig = { +export const genomeEntityConfig: BRCEntityConfig = { categoryGroupConfig: { categoryGroups: [ { @@ -45,16 +51,34 @@ export const genomeEntityConfig: EntityConfig = { }, detail: { detailOverviews: [], - staticLoad: false, - tabs: [], - top: [], + staticLoad: true, + tabs: [ + { + label: "Choose Analysis Method", + mainColumn: analysisMethodsMainColumn, + route: "", + sideColumn: analysisMethodsSideColumn, + top: analysisMethodsTop, + }, + ], }, exploreMode: EXPLORE_MODE.CS_FETCH_CS_FILTERING, explorerTitle: "Genomes", getId: getGenomeId, + getTitle: getGenomeTitle, label: "Genomes", list: { columns: [ + { + componentConfig: { + component: C.AnalyzeGenome, + viewBuilder: V.buildAnalyzeGenome, + } as ComponentConfig, + disableSorting: true, + header: BRC_DATA_CATALOG_CATEGORY_LABEL.ANALYZE_GENOME, + id: BRC_DATA_CATALOG_CATEGORY_KEY.ANALYZE_GENOME, + width: "auto", + }, { componentConfig: { component: C.BasicCell, @@ -62,7 +86,7 @@ export const genomeEntityConfig: EntityConfig = { } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.SPECIES, id: BRC_DATA_CATALOG_CATEGORY_KEY.SPECIES, - width: { max: "1.5fr", min: "212px" }, + width: { max: "1fr", min: "284px" }, }, { componentConfig: { @@ -71,16 +95,17 @@ export const genomeEntityConfig: EntityConfig = { } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.STRAIN, id: BRC_DATA_CATALOG_CATEGORY_KEY.STRAIN, - width: { max: "1fr", min: "160px" }, + width: { max: "1fr", min: "124px" }, }, { + columnPinned: true, componentConfig: { component: C.BasicCell, viewBuilder: V.buildGenomeVersionAssemblyId, } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.GENOME_VERSION_ASSEMBLY_ID, id: BRC_DATA_CATALOG_CATEGORY_KEY.GENOME_VERSION_ASSEMBLY_ID, - width: { max: "1fr", min: "160px" }, + width: { max: "1fr", min: "164px" }, }, { componentConfig: { @@ -89,7 +114,7 @@ export const genomeEntityConfig: EntityConfig = { } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.VEUPATHDB_PROJECT, id: BRC_DATA_CATALOG_CATEGORY_KEY.VEUPATHDB_PROJECT, - width: { max: "1fr", min: "160px" }, + width: { max: "1fr", min: "140px" }, }, { componentConfig: { @@ -98,7 +123,7 @@ export const genomeEntityConfig: EntityConfig = { } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.CONTIGS, id: BRC_DATA_CATALOG_CATEGORY_KEY.CONTIGS, - width: { max: "0.5fr", min: "112px" }, + width: { max: "0.5fr", min: "100px" }, }, { componentConfig: { @@ -107,7 +132,7 @@ export const genomeEntityConfig: EntityConfig = { } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.SUPERCONTIGS, id: BRC_DATA_CATALOG_CATEGORY_KEY.SUPERCONTIGS, - width: { max: "0.5fr", min: "112px" }, + width: { max: "0.5fr", min: "140px" }, }, { componentConfig: { @@ -116,16 +141,7 @@ export const genomeEntityConfig: EntityConfig = { } as ComponentConfig, header: BRC_DATA_CATALOG_CATEGORY_LABEL.CHROMOSOMES, id: BRC_DATA_CATALOG_CATEGORY_KEY.CHROMOSOMES, - width: { max: "0.5fr", min: "112px" }, - }, - { - componentConfig: { - component: C.Link, - viewBuilder: V.buildUcscBrowserUrl, - } as ComponentConfig, - header: BRC_DATA_CATALOG_CATEGORY_LABEL.UCSC_BROWSER_URL, - id: BRC_DATA_CATALOG_CATEGORY_KEY.UCSC_BROWSER_URL, - width: { max: "1fr", min: "160px" }, + width: { max: "0.5fr", min: "142px" }, }, ], defaultSort: { diff --git a/data-catalog/site-config/common/entities.ts b/data-catalog/site-config/common/entities.ts new file mode 100644 index 0000000..482707f --- /dev/null +++ b/data-catalog/site-config/common/entities.ts @@ -0,0 +1,20 @@ +import { EntityConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import { + BackPageConfig, + BackPageTabConfig, + ComponentsConfig, +} from "@databiosphere/findable-ui/src/config/entities"; + +export interface BRCBackPageConfig + extends Omit { + tabs: BRCBackPageTabConfig[]; + top?: ComponentsConfig; +} + +export interface BRCBackPageTabConfig extends BackPageTabConfig { + top?: ComponentsConfig; +} + +export interface BRCEntityConfig extends Omit, "detail"> { + detail: BRCBackPageConfig; +}