Skip to content

Commit

Permalink
add single dataset description (#116)
Browse files Browse the repository at this point in the history
Implemented as in #30. Adds various dataset info including its
description, authors, and places of publication.


https://github.com/chanzuckerberg/cryoet-data-portal/assets/29165011/f9538568-d715-41ea-a27a-32734cc220d1
  • Loading branch information
kne42 authored Nov 2, 2023
1 parent b6bb3cf commit 2c23a32
Show file tree
Hide file tree
Showing 17 changed files with 2,758 additions and 6,822 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ComponentProps, ReactNode } from 'react'
import { GetDatasetsDataQuery } from 'app/__generated__/graphql'
import { Link } from 'app/components/Link'
import { TableCell } from 'app/components/TableCell'
import { EMPIAR_ID } from 'app/constants/external-dbs'
import { MAX_PER_PAGE } from 'app/constants/pagination'
import { useIsLoading } from 'app/hooks/useIsLoading'
import { i18n } from 'app/i18n'
Expand Down Expand Up @@ -151,7 +152,7 @@ export function DatasetTable() {

<tbody>
{datasets.map((dataset) => {
const empiarIDMatch = /EMPIAR-([\d]+)/.exec(
const empiarIDMatch = EMPIAR_ID.exec(
dataset.dataset_publications ?? '',
)
const empiarID = empiarIDMatch?.[1]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { Button } from '@czi-sds/components'
import clsx from 'clsx'
import { useState } from 'react'

import { EnvelopeIcon } from 'app/components/icons'
import { Link } from 'app/components/Link'
import {
DatabaseType,
DOI_ID,
LABEL_MAP,
REGEX_MAP,
URL_MAP,
} from 'app/constants/external-dbs'
import { useDatasetById } from 'app/hooks/useDatasetById'
import { i18n } from 'app/i18n'

// use clsx here instead of cns since it erroneously merges text-sds-gray-500 and text-sds-caps-xxxs
const sectionHeaderStyles = clsx(
'font-semibold uppercase',
'text-sds-gray-500',
'text-sds-caps-xxxs leading-sds-caps-xxxs tracking-sds-caps',
)

interface DatabaseEntryProps {
entry: string
}

function DatabaseEntry(props: DatabaseEntryProps) {
const { entry } = props
let dbtype: DatabaseType | undefined
let id: string = ''

for (const [dbt, pattern] of REGEX_MAP) {
const match = pattern.exec(entry)
if (match !== null) {
dbtype = dbt
// eslint-disable-next-line prefer-destructuring
id = match[1]
break
}
}

if (dbtype === undefined) {
return <p>{entry}</p>
}

return (
<p className="text-sds-body-xxs leading-sds-body-xxs flex flex-row gap-sds-xs">
<span className="text-sds-gray-black font-semibold">
{LABEL_MAP.get(dbtype)}:
</span>
<Link className="text-sds-primary-400" to={URL_MAP.get(dbtype) + id}>
{entry}
</Link>
</p>
)
}

interface DatabaseListProps {
title: string
entries?: string[]
className?: string
collapseAfter?: number
}

function DatabaseList(props: DatabaseListProps) {
const { title, entries, className, collapseAfter } = props
const collapsible =
collapseAfter !== undefined &&
collapseAfter >= 0 &&
entries !== undefined &&
entries.length > collapseAfter
const [collapsed, setCollapsed] = useState(true)

return (
<div className={clsx(className, 'flex flex-col gap-sds-xs')}>
<h3 className={sectionHeaderStyles}>{title}</h3>
{entries ? (
<ul
className={clsx(
'flex flex-col gap-sds-xxs',
collapsible && 'transition-[max-height_0.2s_ease-out]',
)}
>
{entries.map(
(e, i) =>
!(collapsible && collapsed && i + 1 > collapseAfter) && (
<li key={e}>
<DatabaseEntry entry={e} />
</li>
),
)}
{collapsible && (
<div>
<Button
sdsType="primary"
sdsStyle="minimal"
onClick={() => setCollapsed(!collapsed)}
// remove whitespace
style={{ minWidth: 0, padding: 0 }}
>
{collapsed
? i18n.plusMore(entries.length - collapseAfter)
: i18n.showLess}
</Button>
</div>
)}
</ul>
) : (
<p className="text-sds-body-xxs leading-sds-body-xxs text-sds-gray-400">
{i18n.notSubmitted}
</p>
)}
</div>
)
}

export function DatasetDescription() {
const { dataset } = useDatasetById()

// TODO: make the below grouping more efficient and/or use GraphQL ordering
const authorsPrimary = dataset.authors.filter(
(author) => author.primary_author_status,
)
const authorsCorresponding = dataset.authors.filter(
(author) => author.corresponding_author_status,
)
const authorsOther = dataset.authors.filter(
(author) =>
!(author.primary_author_status || author.corresponding_author_status),
)

// clean up entries into lists
const publicationEntries = dataset.dataset_publications
?.split(',')
.map((e) => e.trim())
.filter((e) => DOI_ID.exec(e)) // only show DOI links

const relatedDatabaseEntries = dataset.related_database_entries
?.split(',')
.map((e) => e.trim())

const envelopeIcon = (
<EnvelopeIcon className="text-sds-gray-400 mx-sds-xxxs align-top inline-block h-sds-icon-xs w-sds-icon-xs" />
)

return (
<div className="flex flex-col w-full gap-sds-xl">
<p className="text-sds-body-m leading-sds-body-m">
{dataset.description}
</p>
<div className="flex flex-col gap-sds-xs">
<h3 className={sectionHeaderStyles}>{i18n.authors}</h3>
{/* TODO: let's find a better way of doing this */}
<p className="text-sds-body-xxs leading-sds-body-xxs">
<span className="font-semibold">
{authorsPrimary.map((author, i, arr) => (
<>
{author.name}
{!(
authorsOther.length + authorsCorresponding.length === 0 &&
arr.length - 1 === i
) && '; '}
</>
))}
</span>
<span className="text-sds-gray-600">
{authorsOther.map((author, i, arr) => (
<>
{author.name}
{!(authorsCorresponding.length === 0 && arr.length - 1 === i) &&
'; '}
</>
))}
{authorsCorresponding.map((author, i, arr) => (
<>
{author.name}
{author.email ? (
<Link to={`mailto:${author.email}`}>{envelopeIcon}</Link>
) : (
envelopeIcon
)}
{!(arr.length - 1 === i) && '; '}
</>
))}
</span>
</p>
</div>
<div className="flex flex-row gap-sds-xxl">
<DatabaseList
title={i18n.publications}
entries={publicationEntries}
className="flex-1 max-w-[260px]"
/>
<DatabaseList
title={i18n.relatedDatabases}
entries={relatedDatabaseEntries}
collapseAfter={1}
className="flex-1 max-w-[260px]"
/>
{/* extra div to turn it into 3 columns */}
<div className="flex-1" />
</div>
</div>
)
}
125 changes: 94 additions & 31 deletions frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,111 @@
import { Icon } from '@czi-sds/components'
import { Button, Icon } from '@czi-sds/components'
import { useSearchParams } from '@remix-run/react'

import { DatasetDescription } from 'app/components/Dataset/DatasetDescription'
import { Link } from 'app/components/Link'
import { useDatasetById } from 'app/hooks/useDatasetById'
import { i18n } from 'app/i18n'
import { useDatasetDrawer } from 'app/state/drawer'
import { cns } from 'app/utils/cns'

export function DatasetHeader() {
const [params] = useSearchParams()
const previousUrl = params.get('prev')
const dataset = useDatasetById()
const { dataset } = useDatasetById()
const drawer = useDatasetDrawer()

return (
<header className="flex flex-col items-center justify-center w-full min-h-[48px]">
<div
className={cns(
'flex items-center',
'px-sds-xl py-sds-l',
'w-full max-w-content',
previousUrl ? 'justify-between' : 'justify-end',
)}
>
{previousUrl && (
<Link className="flex items-center gap-1" to={previousUrl}>
<Icon
sdsIcon="chevronLeft"
sdsSize="xs"
sdsType="iconButton"
className="!w-[10px] !h-[10px] !fill-sds-primary-400"
/>
<span className="text-sds-primary-400 font-semibold text-sm">
Back to results
</span>
</Link>
)}
<header className="flex flex-col items-center justify-center w-full min-h-[48px] pb-sds-xxl">
<div className="flex flex-col justify-start gap-sds-xxl">
<div
className={cns(
// create grid with fit content 1st row / 2nd col
'grid grid-cols-[1fr_auto] grid-rows-[1fr_auto]',
'w-full',
'justify-between',
'gap-x-sds-xxl',
)}
>
{/* back button */}
{previousUrl && (
<div className="flex items-center">
<Link className="flex items-center gap-sds-xxs" to={previousUrl}>
<Icon
sdsIcon="chevronLeft"
sdsSize="xs"
sdsType="iconButton"
className="!w-[10px] !h-[10px] !fill-sds-primary-400"
/>
<span className="text-sds-primary-400 font-semibold text-sds-header-s leading-sds-header-s">
Back to Results
</span>
</Link>
</div>
)}
<div className="col-start-1 row-start-2 flex flex-col gap-sds-xxs">
{/* dataset title */}
<h1
className={cns(
'font-semibold',
'text-sds-header-xxl leading-sds-header-xxl',
'max-w-[1000px]',
)}
>
{dataset.title}
</h1>
{/* portal ID */}
<div className="flex flex-row items-center justify-left gap-sds-xxs text-sds-gray-500">
<p className="font-semibold uppercase text-sds-caps-xxs leading-sds-caps-xxs tracking-sds-caps">
{i18n.portalIdBlank}
</p>
<p className="text-sds-body-s leading-sds-body-s">{dataset.id}</p>
</div>
</div>

<div className="flex items-center gap-sds-xs text-xs text-sds-gray-600">
<p>{i18n.releaseDate(dataset.release_date)}</p>
<div className="h-3 w-px bg-sds-gray-400" />
<p>
{i18n.lastModified(
dataset.last_modified_date ?? dataset.deposition_date,
{/* dates */}
<div
className={cns(
'row-start-1 col-start-2',
'flex items-center justify-end gap-sds-xs',
'text-xs text-sds-gray-600',
'my-sds-l',
)}
</p>
>
<p>{i18n.releaseDate(dataset.release_date)}</p>
<div className="h-3 w-px bg-sds-gray-400" />
<p>
{i18n.lastModified(
dataset.last_modified_date ?? dataset.deposition_date,
)}
</p>
</div>

{/* actions */}
<div className="flex flex-row row-start-2 col-start-2 gap-sds-m justify-between min-w-[315px]">
<Button
startIcon={
<Icon sdsIcon="download" sdsType="button" sdsSize="l" />
}
sdsType="primary"
sdsStyle="rounded"
>
Download Dataset
</Button>
<Button
startIcon={
<Icon sdsIcon="infoCircle" sdsType="button" sdsSize="l" />
}
sdsType="secondary"
sdsStyle="rounded"
onClick={drawer.toggle}
>
More Info
</Button>
</div>
</div>
<div className="flex flex-row">
<DatasetDescription />
{/* add key photo here */}
</div>
</div>
</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ACTIVE_TAB_PARAM = 'tab'

export function DatasetMetadataDrawer() {
const drawer = useDatasetDrawer()
const dataset = useDatasetById()
const { dataset } = useDatasetById()

const [searchParams, setSearchParams] = useSearchParams()
const activeTab = (searchParams.get(ACTIVE_TAB_PARAM) ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { AccordionMetadataTable } from './AccordionMetadataTable'
import { getTableData } from './utils'

export function DatasetMetadataTable() {
const dataset = useDatasetById()
const { dataset } = useDatasetById()
const datasetMetadata = getTableData(
{
label: i18n.depositionDate,
values: [dataset.deposition_date],
},
{
label: i18n.affiliationName,
values: dataset.authors
values: dataset.authors_with_affiliation
.map((author) => author.affiliation_name)
.filter((value): value is string => !!value),
},
Expand Down
Loading

0 comments on commit 2c23a32

Please sign in to comment.