Skip to content

Commit

Permalink
single dataset route (#101)
Browse files Browse the repository at this point in the history
## Description

Implements the `/datasets/$id` route with the header and back navigation
implemented. The back navigation works by passing the previous URL as a
query parameter. This simplifies the back to search results
functionality because it allows us to keep the previous URL data
stateless.

## Demo


https://github.com/chanzuckerberg/cryoet-data-portal/assets/2176050/4b6eab3b-2a62-4f20-8e3c-4bee2f0c8778
  • Loading branch information
codemonkey800 authored Oct 26, 2023
1 parent 4b07272 commit e82082c
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import Paper from '@mui/material/Paper'
import Skeleton from '@mui/material/Skeleton'
import TableContainer from '@mui/material/TableContainer'
import { useLoaderData, useSearchParams } from '@remix-run/react'
import { useLoaderData, useLocation, useSearchParams } from '@remix-run/react'
import { range } from 'lodash-es'
import { ComponentProps, ReactNode } from 'react'

Expand Down Expand Up @@ -110,6 +110,8 @@ export function DatasetTable() {
)
: data.datasets

const location = useLocation()

return (
// Need to subtract 244px from 100vw to account for the sidebar and padding:
// sidebar width = 200px, padding = 22px * 2 = 44px
Expand Down Expand Up @@ -162,6 +164,11 @@ export function DatasetTable() {
.map((val) => `Object ${val}`)
.map((obj) => <li key={obj}>{obj}</li>)

const previousUrl = `${location.pathname}${location.search}`
const datasetUrl = `/datasets/${
dataset.id
}?prev=${encodeURIComponent(previousUrl)}`

return (
<TableRow className="hover:!bg-sds-gray-100" key={dataset.title}>
{/* Dataset information cell */}
Expand All @@ -183,9 +190,7 @@ export function DatasetTable() {
{isLoadingDebounced ? (
<Skeleton className="max-w-[70%]" variant="text" />
) : (
<Link to={`/datasets/${dataset.id}`}>
{dataset.title}
</Link>
<Link to={datasetUrl}>{dataset.title}</Link>
)}
</p>

Expand Down Expand Up @@ -229,7 +234,7 @@ export function DatasetTable() {
{dataset.authors.length > AUTHOR_MAX && (
<Link
className="text-sds-primary-500 inline"
to={`/datasets/${dataset.id}`}
to={datasetUrl}
>
+ {dataset.authors.length + 1 - AUTHOR_MAX} more
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { TopNavigation } from './TopNavigation'

export function Layout({ children }: { children: ReactNode }) {
return (
<main className="flex flex-col flex-auto">
<>
<TopNavigation />
<Drawer />
<div className="flex flex-col flex-[1_0_auto]">{children}</div>
<main className="flex flex-col flex-[1_0_auto]">{children}</main>
<Footer />
</main>
</>
)
}
2 changes: 2 additions & 0 deletions frontend/packages/data-portal/app/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export const i18n = {
goToDocs: 'Go to Documentation',
helpAndReport: 'Help & Report',
keyPhoto: 'key photo',
lastModified: (date: string) => `Last Modified: ${date}`,
license: 'License',
napariPlugin: 'napari Plugin',
portalId: (id: number | string) => `Portal ID: ${id}`,
privacy: 'Privacy',
privacyPolicy: 'Privacy Policy',
releaseDate: (date: string) => `Release Date: ${date}`,
reportIssueOnGithub: 'Report Issue on GitHub',
runs: 'Runs',
runsTab: (count: number) => `Runs ${count}`,
Expand Down
101 changes: 101 additions & 0 deletions frontend/packages/data-portal/app/routes/datasets.$id.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* eslint-disable @typescript-eslint/no-throw-literal */

import { Icon } from '@czi-sds/components'
import { useLoaderData, useSearchParams } from '@remix-run/react'
import { json, LoaderFunctionArgs } from '@remix-run/server-runtime'

import { gql } from 'app/__generated__'
import { GetDatasetByIdQuery } from 'app/__generated__/graphql'
import { apolloClient } from 'app/apollo.server'
import { Demo } from 'app/components/Demo'
import { Link } from 'app/components/Link'
import { i18n } from 'app/i18n'
import { cns } from 'app/utils/cns'

const GET_DATASET_BY_ID = gql(`
query GetDatasetById($id: Int) {
datasets(where: { id: { _eq: $id } }) {
id
deposition_date
last_modified_date
release_date
}
}
`)

export async function loader({ params }: LoaderFunctionArgs) {
const id = params.id ? +params.id : NaN

if (Number.isNaN(+id)) {
throw new Response(null, {
status: 400,
statusText: 'ID is not defined',
})
}

const { data } = await apolloClient.query({
query: GET_DATASET_BY_ID,
variables: {
id: +id,
},
})

if (data.datasets.length === 0) {
throw new Response(null, {
status: 404,
statusText: `Dataset with ID ${id} not found`,
})
}

return json(data)
}

export default function DatasetByIdPage() {
const [params] = useSearchParams()
const previousUrl = params.get('prev')

const {
datasets: [dataset],
} = useLoaderData<GetDatasetByIdQuery>()

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>
)}

<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,
)}
</p>
</div>
</div>
</header>

<Demo>Dataset {dataset.id}</Demo>
</>
)
}
24 changes: 23 additions & 1 deletion frontend/packages/data-portal/app/utils/url.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isExternalUrl } from './url'
import { createUrl, isExternalUrl } from './url'

describe('utils/url', () => {
describe('isExternalUrl()', () => {
Expand All @@ -10,4 +10,26 @@ describe('utils/url', () => {
expect(isExternalUrl('/faq')).toBe(false)
})
})

describe('createUrl()', () => {
it('should create url object without host', () => {
const url = createUrl('/path')
expect(url.pathname).toBe('/path')
expect(url.host).toBe('tmp.com')
})

it('should create url object with host', () => {
const testCases: [string, string?][] = [
['https://example.com/path'],
['/path', 'http://example.com'],
]

for (const testCase of testCases) {
const url = createUrl(...testCase)

expect(url.pathname).toBe('/path')
expect(url.host).toBe('example.com')
}
})
})
})
20 changes: 20 additions & 0 deletions frontend/packages/data-portal/app/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,23 @@ export function isExternalUrl(url: string): boolean {
return false
}
}
/**
* Wrapper over the URL constructor with additional functionality. URLs that
* cannot be constructor without a base will automatically have the base
* `http://tmp.com` added to the URL. This is to ensure URLs can be created from
* paths and full URLs, and for use cases where operating on the URL pathname
* matter more than the actual host.
*
* @param urlOrPath URL or path string.
* @param baseUrl URL to use for final URL.
* @returns The combined URL object.
*/
export function createUrl(urlOrPath: string, baseUrl?: string): URL {
let base = baseUrl

if (!base && !isExternalUrl(urlOrPath)) {
base = 'http://tmp.com'
}

return new URL(urlOrPath, base)
}
7 changes: 7 additions & 0 deletions frontend/packages/data-portal/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ const config: CodegenConfig = {
'./app/__generated__/': {
preset: 'client',
plugins: [],

presetConfig: {
gqlTagName: 'gql',
},

config: {
scalars: {
date: 'string',
},
},
},
},
ignoreNoDocuments: true,
Expand Down
8 changes: 7 additions & 1 deletion frontend/packages/data-portal/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import type { Config } from 'tailwindcss'
export default {
content: ['./app/**/*.{ts,tsx,scss}'],
theme: {
extend: sds,
extend: {
...sds,

maxWidth: {
content: '1600px',
},
},
},
plugins: [],
} satisfies Config

0 comments on commit e82082c

Please sign in to comment.