From fb63462915684046a1011d124d74e3423912e95a Mon Sep 17 00:00:00 2001 From: Jeremy Asuncion Date: Thu, 25 Apr 2024 11:59:12 -0700 Subject: [PATCH] feat: navigation styling updates (#614) #596 Implements navigation style updates for the the dataset and run pages ## Demos https://dev-nav-changes.cryoet.dev.si.czi.technology/ image image image image --- .../components/BrowseData/DatasetTable.tsx | 39 +++++++--- .../app/components/Dataset/DatasetHeader.tsx | 3 +- .../Dataset/DatasetMetadataTable.tsx | 2 +- .../app/components/Dataset/RunsTable.tsx | 57 ++++++++++---- .../DatasetFilter/NameOrIdFilterSection.tsx | 78 +++++++++++-------- .../data-portal/app/components/KeyPhoto.tsx | 6 +- .../data-portal/app/components/PageHeader.tsx | 2 +- .../app/components/Run/RunHeader.tsx | 33 +------- .../app/components/Table/CellHeader.tsx | 1 + .../app/components/Table/TableCell.tsx | 1 + .../data-portal/app/constants/query.ts | 2 +- .../data-portal/app/constants/table.ts | 11 +++ .../app/graphql/getBrowseDatasets.server.ts | 8 +- .../app/graphql/getRunById.server.ts | 7 -- .../data-portal/app/hooks/useFilter.ts | 2 +- frontend/packages/data-portal/app/i18n.ts | 2 - .../e2e/filters/testDatasetIdsFilter.ts | 4 +- .../packages/data-portal/e2e/filters/utils.ts | 4 +- .../public/locales/en/translation.json | 3 +- website-docs/faq.mdx | 4 +- 20 files changed, 153 insertions(+), 116 deletions(-) diff --git a/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx b/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx index 2bb916b17..0a663682a 100644 --- a/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx +++ b/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx @@ -56,6 +56,33 @@ export function DatasetTable() { try { return [ + columnHelper.accessor('key_photo_thumbnail_url', { + header: () =>

, + + cell({ row: { original: dataset } }) { + const previousUrl = `${location.pathname}${location.search}` + const datasetUrl = `/datasets/${ + dataset.id + }?prev=${encodeURIComponent(previousUrl)}` + + return ( + + + + + + ) + }, + }), + columnHelper.accessor('id', { header: () => ( - {t('dataset')} + {t('datasetName')} ), @@ -95,14 +122,6 @@ export function DatasetTable() { renderLoadingSkeleton={false} width={DatasetTableWidths.id} > - - - -

{isLoadingDebounced ? ( @@ -116,7 +135,7 @@ export function DatasetTable() { {isLoadingDebounced ? ( ) : ( - `${t('portalId')}: ${dataset.id}` + `${t('datasetId')}: ${dataset.id}` )}

diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx b/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx index 6a8e21b1f..a35500e53 100644 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx +++ b/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx @@ -33,9 +33,8 @@ export function DatasetHeader() { lastModifiedDate={dataset.last_modified_date ?? dataset.deposition_date} metadata={[ { - key: t('portalId'), + key: t('datasetId'), value: String(dataset.id), - uppercase: true, }, ]} onMoreInfoClick={() => toggleDrawer(MetadataDrawerId.Dataset)} diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx b/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx index bc1dabacc..9d14b1d8b 100644 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx +++ b/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx @@ -62,7 +62,7 @@ export function DatasetMetadataTable({ }, !!allFields && { - label: t('portalId'), + label: t('datasetId'), values: [`${dataset.id!}`], }, diff --git a/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx b/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx index ce528eee2..ef682f21f 100644 --- a/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx +++ b/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx @@ -43,6 +43,33 @@ export function RunsTable() { const columnHelper = createColumnHelper() return [ + columnHelper.accessor( + (run) => + run.tomogram_voxel_spacings.at(0)?.tomograms.at(0) + ?.key_photo_thumbnail_url, + { + id: 'key-photo', + header: () =>

, + + cell: ({ row: { original: run } }) => ( + + + + ), + }, + ), + columnHelper.accessor('name', { header: () => ( - {t('runs')} + {t('runName')} ), cell({ row: { original: run } }) { @@ -61,25 +88,29 @@ export function RunsTable() { return ( - - -

+
{isLoadingDebounced ? ( ) : ( - {run.name} + + {run.name} + )} + +

+ {isLoadingDebounced ? ( + + ) : ( + `${t('runId')}: ${run.id}` + )} +

) diff --git a/frontend/packages/data-portal/app/components/DatasetFilter/NameOrIdFilterSection.tsx b/frontend/packages/data-portal/app/components/DatasetFilter/NameOrIdFilterSection.tsx index 6829dbe67..bf734a86d 100644 --- a/frontend/packages/data-portal/app/components/DatasetFilter/NameOrIdFilterSection.tsx +++ b/frontend/packages/data-portal/app/components/DatasetFilter/NameOrIdFilterSection.tsx @@ -1,47 +1,57 @@ +import { useMemo } from 'react' + import { FilterSection, InputFilterData, MultiInputFilter, } from 'app/components/Filters' import { QueryParams } from 'app/constants/query' -import { i18n } from 'app/i18n' +import { useI18n } from 'app/hooks/useI18n' + +export function NameOrIdFilterSection() { + const { t } = useI18n() -const DATASET_ID_FILTERS: InputFilterData[] = [ - { - id: 'portal-id-input', - label: `${i18n.portalIdBlank}:`, - queryParam: QueryParams.PortalId, - }, - { - id: 'empiar-id-input', - label: `${i18n.empiarID}:`, - queryParam: QueryParams.EmpiarId, - }, - { - id: 'emdb-id-input', - label: `${i18n.emdb}:`, - queryParam: QueryParams.EmdbId, - }, -] + const datasetIdFilters = useMemo( + () => [ + { + id: 'portal-id-input', + label: `${t('datasetId')}:`, + queryParam: QueryParams.DatasetId, + }, + { + id: 'empiar-id-input', + label: `${t('empiarID')}:`, + queryParam: QueryParams.EmpiarId, + }, + { + id: 'emdb-id-input', + label: `${t('emdb')}:`, + queryParam: QueryParams.EmdbId, + }, + ], + [t], + ) -const AUTHOR_FILTERS: InputFilterData[] = [ - { - id: 'author-name-input', - label: `${i18n.authorName}:`, - queryParam: QueryParams.AuthorName, - }, - { - id: 'author-orcid-input', - label: `${i18n.authorOrcid}:`, - queryParam: QueryParams.AuthorOrcid, - }, -] + const authorFilters = useMemo( + () => [ + { + id: 'author-name-input', + label: `${t('authorName')}:`, + queryParam: QueryParams.AuthorName, + }, + { + id: 'author-orcid-input', + label: `${t('authorOrcid')}:`, + queryParam: QueryParams.AuthorOrcid, + }, + ], + [t], + ) -export function NameOrIdFilterSection() { return ( - - - + + + ) } diff --git a/frontend/packages/data-portal/app/components/KeyPhoto.tsx b/frontend/packages/data-portal/app/components/KeyPhoto.tsx index 2358182cd..fc8ccce16 100644 --- a/frontend/packages/data-portal/app/components/KeyPhoto.tsx +++ b/frontend/packages/data-portal/app/components/KeyPhoto.tsx @@ -5,10 +5,12 @@ import { KeyPhotoFallbackIcon } from 'app/components/icons' import { cns } from 'app/utils/cns' export function KeyPhoto({ + className, loading = false, src, title, }: { + className?: string loading?: boolean src?: string title: string @@ -16,12 +18,14 @@ export function KeyPhoto({ return (
{match([src, loading]) diff --git a/frontend/packages/data-portal/app/components/PageHeader.tsx b/frontend/packages/data-portal/app/components/PageHeader.tsx index 937d16ae7..189e22675 100644 --- a/frontend/packages/data-portal/app/components/PageHeader.tsx +++ b/frontend/packages/data-portal/app/components/PageHeader.tsx @@ -96,7 +96,7 @@ export function PageHeader({ > diff --git a/frontend/packages/data-portal/app/components/Run/RunHeader.tsx b/frontend/packages/data-portal/app/components/Run/RunHeader.tsx index 31ef26c22..289b5f973 100644 --- a/frontend/packages/data-portal/app/components/Run/RunHeader.tsx +++ b/frontend/packages/data-portal/app/components/Run/RunHeader.tsx @@ -1,5 +1,4 @@ import { Button, Icon } from '@czi-sds/components' -import { sum } from 'lodash-es' import { I18n } from 'app/components/I18n' import { KeyPhoto } from 'app/components/KeyPhoto' @@ -69,37 +68,7 @@ export function RunHeader() { } backToResultsLabel={run.dataset.title} lastModifiedDate="2023-12-16" - metadata={[ - // TODO fetch frames from API - { key: i18n.frames, value: i18n.nFiles(0) }, - - { - key: i18n.tiltSeries, - value: i18n.nFiles(run.tiltseries_aggregate.aggregate?.count ?? 0), - }, - - { - key: i18n.tomograms, - value: i18n.nFiles( - sum( - run.tomogram_stats.flatMap( - (stats) => stats.tomograms_aggregate.aggregate?.count ?? 0, - ), - ), - ), - }, - - { - key: i18n.annotations, - value: i18n.nFiles( - sum( - run.tomogram_stats.flatMap( - (stats) => stats.annotations_aggregate.aggregate?.count ?? 0, - ), - ), - ), - }, - ]} + metadata={[{ key: t('runId'), value: `${run.id}` }]} onMoreInfoClick={() => toggleDrawer(MetadataDrawerId.Run)} title={run.name} renderHeader={({ moreInfo }) => ( diff --git a/frontend/packages/data-portal/app/components/Table/CellHeader.tsx b/frontend/packages/data-portal/app/components/Table/CellHeader.tsx index 9cf1446ef..018029f37 100644 --- a/frontend/packages/data-portal/app/components/Table/CellHeader.tsx +++ b/frontend/packages/data-portal/app/components/Table/CellHeader.tsx @@ -47,6 +47,7 @@ export function CellHeader({ style={{ maxWidth: columnWidth?.max, minWidth: columnWidth?.min, + width: columnWidth?.width, }} hideSortIcon={!showSort} > diff --git a/frontend/packages/data-portal/app/components/Table/TableCell.tsx b/frontend/packages/data-portal/app/components/Table/TableCell.tsx index 94bd236f0..2abe31d86 100644 --- a/frontend/packages/data-portal/app/components/Table/TableCell.tsx +++ b/frontend/packages/data-portal/app/components/Table/TableCell.tsx @@ -42,6 +42,7 @@ export function TableCell({ style: { maxWidth: width?.max, minWidth: width?.min, + width: width?.width, }, } diff --git a/frontend/packages/data-portal/app/constants/query.ts b/frontend/packages/data-portal/app/constants/query.ts index d524b805f..7777edef7 100644 --- a/frontend/packages/data-portal/app/constants/query.ts +++ b/frontend/packages/data-portal/app/constants/query.ts @@ -4,6 +4,7 @@ export enum QueryParams { AuthorOrcid = 'author_orcid', AvailableFiles = 'files', CameraManufacturer = 'camera_manufacturer', + DatasetId = 'dataset_id', DownloadConfig = 'download-config', DownloadStep = 'download-step', DownloadTab = 'download-tab', @@ -18,7 +19,6 @@ export enum QueryParams { ObjectShapeType = 'object_shape', Organism = 'organism', Page = 'page', - PortalId = 'portal_id', QualityScore = 'quality_score', ReconstructionMethod = 'reconstruction_method', ReconstructionSoftware = 'reconstruction_software', diff --git a/frontend/packages/data-portal/app/constants/table.ts b/frontend/packages/data-portal/app/constants/table.ts index 2ade3fbf5..6f03cd552 100644 --- a/frontend/packages/data-portal/app/constants/table.ts +++ b/frontend/packages/data-portal/app/constants/table.ts @@ -1,9 +1,19 @@ export type TableColumnWidth = { + // TODO maybe remove this because it seems like only width and min-width are respected max?: number min?: number + // explicit width is sometimes required to ensure the column width does not grow + width?: number +} + +const PHOTO_COLUMN_WIDTH: TableColumnWidth = { + min: 134, + max: 134, + width: 134, } export const DatasetTableWidths = { + photo: PHOTO_COLUMN_WIDTH, id: { min: 450, max: 800 }, empiarId: { min: 120, max: 130 }, organismName: { min: 100, max: 400 }, @@ -22,6 +32,7 @@ export const AnnotationTableWidths = { } export const RunTableWidths = { + photo: PHOTO_COLUMN_WIDTH, name: { min: 400 }, tiltSeriesQuality: { min: 120, max: 210 }, annotatedObjects: { min: 250, max: 400 }, diff --git a/frontend/packages/data-portal/app/graphql/getBrowseDatasets.server.ts b/frontend/packages/data-portal/app/graphql/getBrowseDatasets.server.ts index ee91f3125..ec012764c 100644 --- a/frontend/packages/data-portal/app/graphql/getBrowseDatasets.server.ts +++ b/frontend/packages/data-portal/app/graphql/getBrowseDatasets.server.ts @@ -188,12 +188,12 @@ function getFilter(filterState: FilterState, query: string) { // Id filters const idFilters: Datasets_Bool_Exp[] = [] - // Portal ID filter - const portalId = +(filterState.ids.portal ?? Number.NaN) - if (!Number.isNaN(portalId) && portalId > 0) { + // Dataset ID filter + const datasetId = +(filterState.ids.dataset ?? Number.NaN) + if (!Number.isNaN(datasetId) && datasetId > 0) { idFilters.push({ id: { - _eq: portalId, + _eq: datasetId, }, }) } diff --git a/frontend/packages/data-portal/app/graphql/getRunById.server.ts b/frontend/packages/data-portal/app/graphql/getRunById.server.ts index 68ee66934..043c0874d 100644 --- a/frontend/packages/data-portal/app/graphql/getRunById.server.ts +++ b/frontend/packages/data-portal/app/graphql/getRunById.server.ts @@ -194,17 +194,10 @@ const GET_RUN_BY_ID_QUERY = gql(` size_z voxel_spacing } - - tomograms_aggregate { - aggregate { - count - } - } } tiltseries_aggregate { aggregate { - count avg { tilt_series_quality } diff --git a/frontend/packages/data-portal/app/hooks/useFilter.ts b/frontend/packages/data-portal/app/hooks/useFilter.ts index 1a16ab09f..c1466b174 100644 --- a/frontend/packages/data-portal/app/hooks/useFilter.ts +++ b/frontend/packages/data-portal/app/hooks/useFilter.ts @@ -26,7 +26,7 @@ export function getFilterState(searchParams: URLSearchParams) { }, ids: { - portal: searchParams.get(QueryParams.PortalId), + dataset: searchParams.get(QueryParams.DatasetId), empiar: searchParams.get(QueryParams.EmpiarId), emdb: searchParams.get(QueryParams.EmdbId), }, diff --git a/frontend/packages/data-portal/app/i18n.ts b/frontend/packages/data-portal/app/i18n.ts index 1fc27f70b..623e7d3d9 100644 --- a/frontend/packages/data-portal/app/i18n.ts +++ b/frontend/packages/data-portal/app/i18n.ts @@ -133,8 +133,6 @@ export const i18n = { pixelSpacing: 'Pixel Spacing', plusMore: (count: number) => `+${count} More`, poor: 'Poor', - portalId: (id: number | string) => `Portal ID: ${id}`, - portalIdBlank: 'Portal ID', precision: 'Precision', privacy: 'Privacy', privacyPolicy: 'Privacy Policy', diff --git a/frontend/packages/data-portal/e2e/filters/testDatasetIdsFilter.ts b/frontend/packages/data-portal/e2e/filters/testDatasetIdsFilter.ts index 59ee95817..224824dcb 100644 --- a/frontend/packages/data-portal/e2e/filters/testDatasetIdsFilter.ts +++ b/frontend/packages/data-portal/e2e/filters/testDatasetIdsFilter.ts @@ -110,8 +110,8 @@ export function testDatasetIdsFilter() { testFilter({ client, - queryParam: QueryParams.PortalId, - label: 'Portal ID', + queryParam: QueryParams.DatasetId, + label: 'Dataset ID', valueKey: 'datasetId', }) diff --git a/frontend/packages/data-portal/e2e/filters/utils.ts b/frontend/packages/data-portal/e2e/filters/utils.ts index d733edcb6..74adb1035 100644 --- a/frontend/packages/data-portal/e2e/filters/utils.ts +++ b/frontend/packages/data-portal/e2e/filters/utils.ts @@ -29,9 +29,9 @@ export function getDatasetTableFilterValidator( return async (page: Page) => { const datasetIds = await Promise.all( - (await page.getByText(/Portal ID: [0-9]+/).all()).map(async (node) => { + (await page.getByText(/Dataset ID: [0-9]+/).all()).map(async (node) => { const text = await node.innerText() - return text.replace('Portal ID: ', '') + return text.replace('Dataset ID: ', '') }), ) diff --git a/frontend/packages/data-portal/public/locales/en/translation.json b/frontend/packages/data-portal/public/locales/en/translation.json index 1d6f873b7..1ea9e1ee0 100644 --- a/frontend/packages/data-portal/public/locales/en/translation.json +++ b/frontend/packages/data-portal/public/locales/en/translation.json @@ -73,6 +73,7 @@ "datasetId": "Dataset ID", "datasetIds": "Dataset IDs", "datasetMetadata": "Dataset Metadata", + "datasetName": "Dataset Name", "datasetOverview": "Dataset Overview", "datasetTitle": "Dataset Title", "datasets": "Datasets", @@ -187,7 +188,6 @@ "pixelSpacing": "Pixel Spacing", "plusMore": "+{{count}} More", "poor": "Poor", - "portalId": "Portal ID", "precision": "Precision", "precisionTooltip": "Precision: Percentage of true positives among the total number of positive predictions. A value of 100% means everything found is actually the object of interest. Learn More", "preferToDownloadViaApi": "Prefer to download via our API? Record the IDs below and visit our API documentation to learn how.", @@ -214,6 +214,7 @@ "runDataIncludes": "Run data includes all available movie frames, tilt series image stack, tomograms, annotations, and associated metadata.", "runDetails": "Run Details", "runId": "Run ID", + "runName": "Run Name", "runOverview": "Run Overview", "runs": "Runs", "runsTab": "Runs {{count}}", diff --git a/website-docs/faq.mdx b/website-docs/faq.mdx index d0b01d800..e1f2cb3d1 100644 --- a/website-docs/faq.mdx +++ b/website-docs/faq.mdx @@ -131,9 +131,9 @@ The tilt series quality score/rating is a relative subjective scale meant for co - + -The dataset identifier in the API refers to the Portal ID provided in the Portal. This number is assigned by the Data Portal as a unique identifier for a dataset and is used as the directory name in the data filetree. +The dataset identifier in the API refers to the Dataset ID provided in the Portal. This number is assigned by the Data Portal as a unique identifier for a dataset and is used as the directory name in the data filetree. Descriptions of all terminology and metadata used in the Portal is provided [here](https://chanzuckerberg.github.io/cryoet-data-portal/python-api.html).