diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositedInTableCell.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositedInTableCell.tsx new file mode 100644 index 000000000..113a97b08 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositedInTableCell.tsx @@ -0,0 +1,70 @@ +import { useMemo } from 'react' + +import { Link } from 'app/components/Link' +import { useI18n } from 'app/hooks/useI18n' +import type { I18nKeys } from 'app/types/i18n' +import { DASHED_UNDERLINED_CLASSES } from 'app/utils/classNames' + +interface DepositItem { + label: I18nKeys + url: string + value?: string +} + +export function DepositedInTableCell({ + datasetId, + datasetTitle, + runId, + runName, +}: { + datasetId?: number + datasetTitle?: string + runId?: number + runName?: string +}) { + const depositItems: DepositItem[] = useMemo( + () => [ + ...(datasetId && datasetTitle + ? [ + { + label: 'dataset' as I18nKeys, + value: datasetTitle, + url: `/datasets/${datasetId}`, + }, + ] + : []), + + ...(runId && runName + ? [ + { + label: 'run' as I18nKeys, + value: runName, + url: `/runs/${runId}`, + }, + ] + : []), + ], + [datasetId, datasetTitle, runId, runName], + ) + + const { t } = useI18n() + + if (depositItems.length === 0) { + return '--' + } + + return ( +
+ {depositItems.map(({ label, value, url }) => ( + + {t(label)}: + {value ?? '--'} + + ))} +
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionAnnotationTable.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionAnnotationTable.tsx new file mode 100644 index 000000000..339924b14 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionAnnotationTable.tsx @@ -0,0 +1,105 @@ +import { ColumnDef } from '@tanstack/react-table' +import { useMemo } from 'react' + +import { + Annotation_File_Shape_Type_Enum, + Annotation_Method_Type_Enum, + type GetDepositionAnnotationsQuery, +} from 'app/__generated_v2__/graphql' +import { useAnnotationNameColumn } from 'app/components/AnnotationTable/useAnnotationNameColumn' +import { useShapeTypeColumn } from 'app/components/AnnotationTable/useShapeTypeColumn' +import { PageTable } from 'app/components/Table' +import { DepositionAnnotationTableWidths } from 'app/constants/table' +import { useDepositionById } from 'app/hooks/useDepositionById' +import { useIsLoading } from 'app/hooks/useIsLoading' + +import { useMethodTypeColumn } from '../AnnotationTable/useMethodTypeColumn' +import { useDepositedInColumn } from './useDepositedInColumn' + +export type DepositionAnnotationTableData = + GetDepositionAnnotationsQuery['annotationShapes'][number] + +const LOADING_ANNOTATIONS = Array.from( + { length: 10 }, + (_, index) => + ({ + id: index, + shapeType: Annotation_File_Shape_Type_Enum.Point, + + annotation: { + id: index, + objectName: 'object name', + groundTruthStatus: false, + methodType: Annotation_Method_Type_Enum.Automated, + }, + + annotationFiles: { + edges: [ + { + node: { + s3Path: 's3://example.com/loading', + }, + }, + ], + }, + }) as DepositionAnnotationTableData, +) + +export function DepositionAnnotationTable() { + const { annotations } = useDepositionById() + + const { isLoadingDebounced } = useIsLoading() + + const annotationNameColumn = useAnnotationNameColumn({ + width: DepositionAnnotationTableWidths.name, + }) + + const shapeTypeColumn = useShapeTypeColumn( + DepositionAnnotationTableWidths.objectShapeType, + ) + + const methodTypeColumn = useMethodTypeColumn({ + width: DepositionAnnotationTableWidths.methodType, + }) + + const depositedInColumn = useDepositedInColumn( + { + width: DepositionAnnotationTableWidths.depositedIn, + + getDepositedInData: ({ annotation }) => ({ + datasetId: annotation?.run?.dataset?.id, + datasetTitle: annotation?.run?.dataset?.title, + runId: annotation?.run?.id, + runName: annotation?.run?.name, + }), + }, + ) + + const columns = useMemo( + () => + [ + annotationNameColumn, + shapeTypeColumn, + methodTypeColumn, + depositedInColumn, + ] as ColumnDef[], + [ + annotationNameColumn, + depositedInColumn, + methodTypeColumn, + shapeTypeColumn, + ], + ) + + return ( + + ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionFilters.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionFilters.tsx new file mode 100644 index 000000000..319f42744 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionFilters.tsx @@ -0,0 +1,69 @@ +import { useMemo } from 'react' + +import { useDepositionById } from 'app/hooks/useDepositionById' +import { useI18n } from 'app/hooks/useI18n' +import { cns } from 'app/utils/cns' +import { getDataContents } from 'app/utils/deposition' + +import { DepositionTabs } from './DepositionTabs' + +export function DepositionFilters() { + const { allRuns } = useDepositionById() + const dataContents = getDataContents(allRuns) + const dataContentItems = useMemo( + () => + [ + { + label: 'tiltSeries', + isAvailable: dataContents.tiltSeriesAvailable, + }, + { + label: 'frames', + isAvailable: dataContents.framesAvailable, + }, + { + label: 'ctf', + isAvailable: dataContents.ctfAvailable, + }, + { + label: 'alignment', + isAvailable: dataContents.alignmentAvailable, + }, + ] as const, + [dataContents], + ) + + const { t } = useI18n() + + return ( +
+

+ {t('dataContents')} +

+ + + +
+ {dataContentItems.map(({ label, isAvailable }) => ( +
+ {t(label)} + + {t(isAvailable ? 'available' : 'na')} +
+ ))} +
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionTableRenderer.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionTableRenderer.tsx new file mode 100644 index 000000000..ad4270c5c --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionTableRenderer.tsx @@ -0,0 +1,16 @@ +import { DepositionTab, useDepositionTab } from 'app/hooks/useDepositionTab' + +import { DepositionAnnotationTable } from './DepositionAnnotationTable' +import { DepositionTomogramTable } from './DepositionTomogramTable' + +export function DepositionTableRenderer() { + const [tab] = useDepositionTab() + + switch (tab) { + case DepositionTab.Tomograms: + return + + default: + return + } +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionTabs.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionTabs.tsx new file mode 100644 index 000000000..d70accb07 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionTabs.tsx @@ -0,0 +1,66 @@ +import { Icon } from '@czi-sds/components' +import { useMemo } from 'react' + +import { type TabData, Tabs } from 'app/components/Tabs' +import { useDepositionById } from 'app/hooks/useDepositionById' +import { DepositionTab, useDepositionTab } from 'app/hooks/useDepositionTab' +import { useI18n } from 'app/hooks/useI18n' +import { cns } from 'app/utils/cns' + +export function DepositionTabs() { + const [tab, setTab] = useDepositionTab() + const tabs = useTabs(tab) + + return +} + +function useTabs(activeTab: DepositionTab) { + const { t } = useI18n() + const { annotationsCount, tomogramsCount } = useDepositionById() + + return useMemo[]>(() => { + const tabData = [ + { + tab: DepositionTab.Annotations, + label: 'annotations', + icon: 'Cube', + count: annotationsCount, + }, + { + tab: DepositionTab.Tomograms, + label: 'tomograms', + icon: 'FlagOutline', + count: tomogramsCount, + }, + ] as const + + return tabData.map(({ tab, label, icon, count }) => ({ + value: tab, + label: ( + + + + + {t(label)} + + + {count.toLocaleString()} + + ), + })) + }, [activeTab, annotationsCount, t, tomogramsCount]) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionTomogramTable.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionTomogramTable.tsx new file mode 100644 index 000000000..0980cd669 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionTomogramTable.tsx @@ -0,0 +1,121 @@ +import { ColumnDef } from '@tanstack/react-table' +import { useMemo } from 'react' + +import { + type GetDepositionTomogramsQuery, + Tomogram_Processing_Enum, +} from 'app/__generated_v2__/graphql' +import { useDepositedInColumn } from 'app/components/Deposition/useDepositedInColumn' +import { PageTable } from 'app/components/Table' +import { usePostProcessingColumn } from 'app/components/TomogramsTable/usePostProcessingColumn' +import { useReconstructionMethodColumn } from 'app/components/TomogramsTable/useReconstructionMethodColumn' +import { useTomogramActionsColumn } from 'app/components/TomogramsTable/useTomogramActionsColumn' +import { useTomogramKeyPhotoColumn } from 'app/components/TomogramsTable/useTomogramKeyPhotoColumn' +import { useTomogramNameColumn } from 'app/components/TomogramsTable/useTomogramNameColumn' +import { useVoxelSpacingColumn } from 'app/components/TomogramsTable/useVoxelSpacingColumn' +import { DepositionTomogramTableWidths } from 'app/constants/table' +import { useDepositionById } from 'app/hooks/useDepositionById' +import { useIsLoading } from 'app/hooks/useIsLoading' + +type DepositionTomogramTableData = + GetDepositionTomogramsQuery['tomograms'][number] + +const LOADING_ANNOTATIONS = Array.from( + { length: 10 }, + (_, index) => + ({ + id: index, + name: `Tomogram ${index + 1}`, + processing: Tomogram_Processing_Enum.Raw, + reconstructionMethod: 'Unknown', + voxelSpacing: 1.0, + run: { + id: index, + name: `Run ${index + 1}`, + dataset: { + id: index, + title: `Dataset ${index + 1}`, + }, + }, + }) as DepositionTomogramTableData, +) + +export function DepositionTomogramTable() { + const { tomograms } = useDepositionById() + + const { isLoadingDebounced } = useIsLoading() + + const keyPhotoColumn = useTomogramKeyPhotoColumn( + DepositionTomogramTableWidths.photo, + ) + + const tomogramNameColumn = useTomogramNameColumn({ + width: DepositionTomogramTableWidths.name, + }) + + const voxelSpacingColumn = useVoxelSpacingColumn( + DepositionTomogramTableWidths.voxelSpacing, + ) + + const reconstructionMethodColumn = useReconstructionMethodColumn( + DepositionTomogramTableWidths.reconstructionMethod, + ) + + const postProcessingColumn = usePostProcessingColumn( + DepositionTomogramTableWidths.postProcessing, + ) + + const depositedInColumn = useDepositedInColumn({ + width: DepositionTomogramTableWidths.depositedIn, + + getDepositedInData: ({ run }) => ({ + datasetId: run?.dataset?.id, + datasetTitle: run?.dataset?.title, + runId: run?.id, + runName: run?.name, + }), + }) + + const tomogramActionsColumn = useTomogramActionsColumn({ + forceShowViewTomogramButton: true, + width: DepositionTomogramTableWidths.actions, + + getPlausibleData: (tomogram) => ({ + datasetId: tomogram.run?.dataset?.id ?? 0, + organism: tomogram.run?.dataset?.organismName ?? 'None', + runId: tomogram.run?.id ?? 0, + }), + }) + + const columns = useMemo( + () => + [ + keyPhotoColumn, + tomogramNameColumn, + voxelSpacingColumn, + reconstructionMethodColumn, + postProcessingColumn, + depositedInColumn, + tomogramActionsColumn, + ] as ColumnDef[], + [ + depositedInColumn, + keyPhotoColumn, + postProcessingColumn, + reconstructionMethodColumn, + tomogramActionsColumn, + tomogramNameColumn, + voxelSpacingColumn, + ], + ) + + return ( + + ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/useDepositedInColumn.tsx b/frontend/packages/data-portal/app/components/Deposition/useDepositedInColumn.tsx new file mode 100644 index 000000000..25b4c4353 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/useDepositedInColumn.tsx @@ -0,0 +1,39 @@ +import Skeleton from '@mui/material/Skeleton' +import { createColumnHelper } from '@tanstack/react-table' +import type { ComponentProps } from 'react' + +import { CellHeader, TableCell } from 'app/components/Table' +import type { TableColumnWidth } from 'app/constants/table' +import { useI18n } from 'app/hooks/useI18n' + +import { DepositedInTableCell } from './DepositedInTableCell' + +type DepositedInData = ComponentProps + +export function useDepositedInColumn({ + getDepositedInData, + width, +}: { + getDepositedInData(value: T): DepositedInData + width: TableColumnWidth +}) { + const { t } = useI18n() + const columnHelper = createColumnHelper() + + return columnHelper.display({ + id: 'depositedIn', + + header: () => {t('depositedIn')}, + + cell: ({ row: { original } }) => ( + ( + + )} + width={width} + > + + + ), + }) +} diff --git a/frontend/packages/data-portal/app/constants/query.ts b/frontend/packages/data-portal/app/constants/query.ts index 8c0fa7e06..a33a11bee 100644 --- a/frontend/packages/data-portal/app/constants/query.ts +++ b/frontend/packages/data-portal/app/constants/query.ts @@ -10,6 +10,7 @@ export enum QueryParams { Competition = 'competition', DatasetId = 'dataset_id', DepositionId = 'deposition-id', + DepositionTab = 'deposition-tab', DownloadConfig = 'download-config', DownloadStep = 'download-step', DownloadTab = 'download-tab', diff --git a/frontend/packages/data-portal/app/constants/table.ts b/frontend/packages/data-portal/app/constants/table.ts index a3ff7f914..7e68dc4c8 100644 --- a/frontend/packages/data-portal/app/constants/table.ts +++ b/frontend/packages/data-portal/app/constants/table.ts @@ -101,3 +101,20 @@ export const ExperimentalConditionsTableWidths = { gridPreparation: { width: 120 }, runs: { width: 100 }, } + +export const DepositionAnnotationTableWidths = { + name: { width: 350 }, + objectShapeType: { width: 160 }, + methodType: { width: 160 }, + depositedIn: { width: 340 }, +} + +export const DepositionTomogramTableWidths = { + photo: PHOTO_COLUMN_WIDTH, + name: { width: 200 }, + voxelSpacing: { width: 160 }, + reconstructionMethod: { width: 142 }, + postProcessing: { width: 120 }, + depositedIn: { width: 230 }, + actions: { width: 158 }, +} diff --git a/frontend/packages/data-portal/app/graphql/getDepositionAnnotationsV2.server.ts b/frontend/packages/data-portal/app/graphql/getDepositionAnnotationsV2.server.ts new file mode 100644 index 000000000..96f252c9c --- /dev/null +++ b/frontend/packages/data-portal/app/graphql/getDepositionAnnotationsV2.server.ts @@ -0,0 +1,80 @@ +import type { + ApolloClient, + ApolloQueryResult, + NormalizedCacheObject, +} from '@apollo/client' + +import { gql } from 'app/__generated_v2__' +import type { GetDepositionAnnotationsQuery } from 'app/__generated_v2__/graphql' +import { MAX_PER_PAGE } from 'app/constants/pagination' + +const GET_DEPOSITION_ANNOTATIONS = gql(` + query GetDepositionAnnotations( + $id: Int!, + $limit: Int!, + $offset: Int!, + ) { + annotationShapes( + where: { + annotation: { + depositionId: { + _eq: $id + }, + }, + }, + + limitOffset: { + limit: $limit, + offset: $offset, + }, + ) { + id + shapeType + + annotation { + groundTruthStatus + id + methodType + objectName + + run { + id + name + + dataset { + id + title + } + } + } + + annotationFiles(first: 1) { + edges { + node { + s3Path + } + } + } + + } + } +`) + +export async function getDepositionAnnotations({ + client, + id, + page, +}: { + client: ApolloClient + id: number + page: number +}): Promise> { + return client.query({ + query: GET_DEPOSITION_ANNOTATIONS, + variables: { + id, + limit: MAX_PER_PAGE, + offset: (page - 1) * MAX_PER_PAGE, + }, + }) +} diff --git a/frontend/packages/data-portal/app/graphql/getDepositionByIdV2.server.ts b/frontend/packages/data-portal/app/graphql/getDepositionByIdV2.server.ts index 913ede46b..0616577fd 100644 --- a/frontend/packages/data-portal/app/graphql/getDepositionByIdV2.server.ts +++ b/frontend/packages/data-portal/app/graphql/getDepositionByIdV2.server.ts @@ -131,7 +131,39 @@ const GET_DEPOSITION_BY_ID = gql(` } } + allRuns: runs(where: { + annotations: { + depositionId: { _eq: $id } + } + }) { + ...DataContents + } + ...DatasetsAggregates + + annotationsCount: annotationsAggregate(where: { + depositionId: { + _eq: $id + } + }) { + aggregate { + count + } + } + + tomogramsCount: tomogramsAggregate(where: { + run: { + annotations: { + depositionId: { + _eq: $id + } + } + } + }) { + aggregate { + count + } + } } `) diff --git a/frontend/packages/data-portal/app/graphql/getDepositionTomogramsV2.server.ts b/frontend/packages/data-portal/app/graphql/getDepositionTomogramsV2.server.ts new file mode 100644 index 000000000..f65eb0d4f --- /dev/null +++ b/frontend/packages/data-portal/app/graphql/getDepositionTomogramsV2.server.ts @@ -0,0 +1,68 @@ +import type { + ApolloClient, + ApolloQueryResult, + NormalizedCacheObject, +} from '@apollo/client' + +import { gql } from 'app/__generated_v2__' +import type { GetDepositionTomogramsQuery } from 'app/__generated_v2__/graphql' +import { MAX_PER_PAGE } from 'app/constants/pagination' + +const GET_DEPOSITION_TOMOGRAMS = gql(` + query GetDepositionTomograms( + $id: Int!, + $limit: Int!, + $offset: Int!, + ) { + tomograms( + where: { + depositionId: { _eq: $id }, + }, + + limitOffset: { + limit: $limit, + offset: $offset, + }, + ) { + id + name + neuroglancerConfig + processing + reconstructionMethod + sizeX + sizeY + sizeZ + voxelSpacing + + run { + id + name + + dataset { + id + organismName + title + } + } + } + } +`) + +export async function getDepositionTomograms({ + client, + id, + page, +}: { + client: ApolloClient + id: number + page: number +}): Promise> { + return client.query({ + query: GET_DEPOSITION_TOMOGRAMS, + variables: { + id, + limit: MAX_PER_PAGE, + offset: (page - 1) * MAX_PER_PAGE, + }, + }) +} diff --git a/frontend/packages/data-portal/app/hooks/useDepositionById.ts b/frontend/packages/data-portal/app/hooks/useDepositionById.ts index e22721051..6d40bcf4d 100644 --- a/frontend/packages/data-portal/app/hooks/useDepositionById.ts +++ b/frontend/packages/data-portal/app/hooks/useDepositionById.ts @@ -4,7 +4,9 @@ import { useTypedLoaderData } from 'remix-typedjson' import { Annotation_Method_Link_Type_Enum, Annotation_Method_Type_Enum, + type GetDepositionAnnotationsQuery, GetDepositionByIdV2Query, + type GetDepositionTomogramsQuery, } from 'app/__generated_v2__/graphql' import { METHOD_TYPE_ORDER } from 'app/constants/methodTypes' @@ -21,8 +23,10 @@ export interface AnnotationMethodMetadata { } export function useDepositionById() { - const { v2 } = useTypedLoaderData<{ + const { v2, annotations, tomograms } = useTypedLoaderData<{ v2: GetDepositionByIdV2Query + annotations?: GetDepositionAnnotationsQuery + tomograms?: GetDepositionTomogramsQuery }>() const annotationMethods: AnnotationMethodMetadata[] = useMemo(() => { @@ -86,8 +90,23 @@ export function useDepositionById() { }, [v2.depositions]) return { - deposition: v2.depositions[0], - datasets: v2.datasets, annotationMethods, + annotations, + tomograms, + allRuns: v2.allRuns, + datasets: v2.datasets, + deposition: v2.depositions[0], + + annotationsCount: + v2.annotationsCount.aggregate?.reduce( + (total, node) => total + (node.count ?? 0), + 0, + ) ?? 0, + + tomogramsCount: + v2.tomogramsCount.aggregate?.reduce( + (total, node) => total + (node.count ?? 0), + 0, + ) ?? 0, } } diff --git a/frontend/packages/data-portal/app/hooks/useDepositionTab.ts b/frontend/packages/data-portal/app/hooks/useDepositionTab.ts new file mode 100644 index 000000000..8c5138c5f --- /dev/null +++ b/frontend/packages/data-portal/app/hooks/useDepositionTab.ts @@ -0,0 +1,15 @@ +import { QueryParams } from 'app/constants/query' + +import { useQueryParam } from './useQueryParam' + +export enum DepositionTab { + Annotations = 'annotations', + Tomograms = 'tomograms', +} + +export function useDepositionTab() { + return useQueryParam( + QueryParams.DepositionTab, + { defaultValue: DepositionTab.Annotations }, + ) +} diff --git a/frontend/packages/data-portal/app/routes/depositions.$id.tsx b/frontend/packages/data-portal/app/routes/depositions.$id.tsx index e87419940..02da25b9c 100644 --- a/frontend/packages/data-portal/app/routes/depositions.$id.tsx +++ b/frontend/packages/data-portal/app/routes/depositions.$id.tsx @@ -2,44 +2,42 @@ import { CellHeaderDirection } from '@czi-sds/components' import { ShouldRevalidateFunctionArgs } from '@remix-run/react' -import { LoaderFunctionArgs, redirect } from '@remix-run/server-runtime' +import { LoaderFunctionArgs } from '@remix-run/server-runtime' import { useEffect } from 'react' import { typedjson } from 'remix-typedjson' +import { match, P } from 'ts-pattern' import { OrderBy } from 'app/__generated_v2__/graphql' import { apolloClientV2 } from 'app/apollo.server' import { DatasetFilter } from 'app/components/DatasetFilter' import { DatasetsTable } from 'app/components/Deposition/DatasetsTable' +import { DepositionFilters } from 'app/components/Deposition/DepositionFilters' import { DepositionHeader } from 'app/components/Deposition/DepositionHeader' import { DepositionMetadataDrawer } from 'app/components/Deposition/DepositionMetadataDrawer' +import { DepositionTableRenderer } from 'app/components/Deposition/DepositionTableRenderer' import { NoFilteredResults } from 'app/components/NoFilteredResults' import { TablePageLayout } from 'app/components/TablePageLayout' +import { TableCount } from 'app/components/TablePageLayout/TableCount' +import type { TableHeaderProps } from 'app/components/TablePageLayout/types' import { DEPOSITION_FILTERS } from 'app/constants/filterQueryParams' import { QueryParams } from 'app/constants/query' +import { getDepositionAnnotations } from 'app/graphql/getDepositionAnnotationsV2.server' import { getDepositionByIdV2 } from 'app/graphql/getDepositionByIdV2.server' +import { getDepositionTomograms } from 'app/graphql/getDepositionTomogramsV2.server' import { useDatasetsFilterData } from 'app/hooks/useDatasetsFilterData' import { useDepositionById } from 'app/hooks/useDepositionById' +import { DepositionTab, useDepositionTab } from 'app/hooks/useDepositionTab' import { useI18n } from 'app/hooks/useI18n' import { useDepositionHistory, useSyncParamsWithState, } from 'app/state/filterHistory' -import { getFeatureFlag } from 'app/utils/featureFlags' +import { getFeatureFlag, useFeatureFlag } from 'app/utils/featureFlags' import { shouldRevalidatePage } from 'app/utils/revalidate' export async function loader({ params, request }: LoaderFunctionArgs) { const url = new URL(request.url) - const showDepositions = getFeatureFlag({ - env: process.env.ENV, - key: 'depositions', - params: url.searchParams, - }) - - if (!showDepositions) { - return redirect('/404') - } - const id = params.id ? +params.id : NaN const page = +(url.searchParams.get(QueryParams.Page) ?? '1') const sort = (url.searchParams.get(QueryParams.Sort) ?? undefined) as @@ -59,11 +57,12 @@ export async function loader({ params, request }: LoaderFunctionArgs) { orderByV2 = sort === 'asc' ? OrderBy.Asc : OrderBy.Desc } + const client = apolloClientV2 const { data: responseV2 } = await getDepositionByIdV2({ - client: apolloClientV2, + client, id, - orderBy: orderByV2, page, + orderBy: orderByV2, params: url.searchParams, }) @@ -74,8 +73,50 @@ export async function loader({ params, request }: LoaderFunctionArgs) { }) } + const isExpandDepositions = getFeatureFlag({ + env: process.env.ENV, + key: 'expandDepositions', + params: url.searchParams, + }) + + const depositionTab = url.searchParams.get( + QueryParams.DepositionTab, + ) as DepositionTab | null + + const { data } = await match({ + isExpandDepositions, + depositionTab, + }) + .with( + { + isExpandDepositions: true, + depositionTab: P.union(DepositionTab.Annotations, null), + }, + () => + getDepositionAnnotations({ + client, + id, + page, + }), + ) + .with( + { + isExpandDepositions: true, + depositionTab: DepositionTab.Tomograms, + }, + () => + getDepositionTomograms({ + client, + id, + page, + }), + ) + .otherwise(() => ({ data: undefined })) + return typedjson({ v2: responseV2, + annotations: data && 'annotationShapes' in data ? data : undefined, + tomograms: data && 'tomograms' in data ? data : undefined, }) } @@ -83,31 +124,32 @@ export function shouldRevalidate(args: ShouldRevalidateFunctionArgs) { return shouldRevalidatePage({ ...args, paramsToRefetch: [ - QueryParams.GroundTruthAnnotation, - QueryParams.AvailableFiles, - QueryParams.NumberOfRuns, - QueryParams.DatasetId, - QueryParams.EmpiarId, - QueryParams.EmdbId, QueryParams.AuthorName, QueryParams.AuthorOrcid, - QueryParams.Organism, + QueryParams.AvailableFiles, QueryParams.CameraManufacturer, - QueryParams.TiltRangeMin, - QueryParams.TiltRangeMax, + QueryParams.DatasetId, + QueryParams.DepositionTab, + QueryParams.EmdbId, + QueryParams.EmpiarId, QueryParams.FiducialAlignmentStatus, - QueryParams.ReconstructionMethod, - QueryParams.ReconstructionMethod, - QueryParams.ObjectName, + QueryParams.GroundTruthAnnotation, + QueryParams.NumberOfRuns, QueryParams.ObjectId, + QueryParams.ObjectName, QueryParams.ObjectShapeType, + QueryParams.Organism, + QueryParams.ReconstructionMethod, + QueryParams.ReconstructionMethod, QueryParams.Sort, + QueryParams.TiltRangeMax, + QueryParams.TiltRangeMin, ], }) } export default function DepositionByIdPage() { - const { deposition } = useDepositionById() + const { deposition, annotationsCount, tomogramsCount } = useDepositionById() const { filteredDatasetsCount, totalDatasetsCount } = useDatasetsFilterData() const { t } = useI18n() @@ -124,21 +166,74 @@ export default function DepositionByIdPage() { setParams: setPreviousSingleDepositionParams, }) + const isExpandDepositions = useFeatureFlag('expandDepositions') + + const [tab] = useDepositionTab() + return ( } tabs={[ { - title: t('datasetsWithDepositionData'), - table: , - totalCount: totalDatasetsCount, - filteredCount: filteredDatasetsCount, - filterPanel: , countLabel: t('datasets'), noFilteredResults: , + title: t('datasetsWithDepositionData'), + Header: isExpandDepositions ? TableCountHeader : undefined, + + table: isExpandDepositions ? ( + + ) : ( + + ), + + totalCount: match({ isExpandDepositions, tab }) + .with( + { isExpandDepositions: true, tab: DepositionTab.Annotations }, + () => annotationsCount, + ) + .with( + { isExpandDepositions: true, tab: DepositionTab.Tomograms }, + () => tomogramsCount, + ) + .otherwise(() => totalDatasetsCount), + + // TODO replace annotations and tomograms with filtered counts + filteredCount: match({ isExpandDepositions, tab }) + .with( + { isExpandDepositions: true, tab: DepositionTab.Annotations }, + () => annotationsCount, + ) + .with( + { isExpandDepositions: true, tab: DepositionTab.Tomograms }, + () => tomogramsCount, + ) + .otherwise(() => filteredDatasetsCount), + + filterPanel: isExpandDepositions ? ( + + ) : ( + + ), }, ]} drawers={} /> ) } + +function TableCountHeader({ + filteredCount, + totalCount, + countLabel, +}: TableHeaderProps) { + return ( +
+ +
+ ) +} diff --git a/frontend/packages/data-portal/public/locales/en/translation.json b/frontend/packages/data-portal/public/locales/en/translation.json index 5f1cae062..e7497aa9f 100644 --- a/frontend/packages/data-portal/public/locales/en/translation.json +++ b/frontend/packages/data-portal/public/locales/en/translation.json @@ -124,6 +124,7 @@ "currentDirectory": "Current Directory (default)", "cziiOrganization": "Chan Zuckerberg Imaging Institute (CZII)", "dataAcquisitionSoftware": "Data Acquisition Software", + "dataContents": "Data Contents", "dataSubmissionPolicy": "Data Submission Policy", "dataTypes": "Data Types", "dataTypesAndCounts": "Data Types and Counts", @@ -142,6 +143,8 @@ "datasetsDescription": "Datasets contain data such as annotations, tomograms, frames, tilt series, and metadata such as CTF and alignment from a single set of experimental conditions", "datasetsTab": "Datasets {{count}}", "datasetsWithDepositionData": "Datasets with Deposition Data", + "depositedData": "Deposited Data", + "depositedIn": "Deposited In", "deposition": "Deposition", "depositionAnnotationsOnly": "Deposition annotations only", "depositionContents": "Deposition Contents",