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",