Skip to content

Commit

Permalink
feat: Implement collapsing Annotated Objects list (#1024)
Browse files Browse the repository at this point in the history
  • Loading branch information
bchu1 authored Aug 15, 2024
1 parent 572972d commit 9343d12
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 39 deletions.
60 changes: 40 additions & 20 deletions frontend/packages/data-portal/app/components/CollapsibleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,58 @@ interface ListEntry {
entry: ReactNode
}

export interface CollapsibleListProps {
entries?: ListEntry[]

// Number of items displayed when collapsed.
// Collapse triggers when entries has >= collapseAfter + 2 items, so minimum "Show _ more" value
// is 2.
collapseAfter?: number

inlineVariant?: boolean
tableVariant?: boolean
}

export function CollapsibleList({
entries,
collapseAfter,
inlineVariant = false,
tableVariant = false,
}: {
entries?: ListEntry[]
collapseAfter?: number
tableVariant?: boolean
}) {
}: CollapsibleListProps) {
const collapsible =
collapseAfter !== undefined &&
collapseAfter >= 0 &&
entries !== undefined &&
entries.length > collapseAfter + 1
entries.length > collapseAfter + 1 // Prevent "Show 1 more"

const { t } = useI18n()
const [collapsed, setCollapsed] = useState(true)

const lastIndex =
collapsible && collapsed ? collapseAfter - 1 : (entries ?? []).length - 1

return entries ? (
<ul
className={cns(
'flex flex-col gap-sds-xs',
'text-sds-body-xxs leading-sds-body-xxs text-sds-gray-600',
collapsible && 'transition-[max-height_0.2s_ease-out]',
)}
>
{entries.map(
({ key, entry }, i) =>
!(collapsible && collapsed && i + 1 > collapseAfter) && (
<li key={key}>{entry}</li>
),
)}
<>
<ul
className={cns(
'flex',
inlineVariant ? 'flex-wrap gap-sds-xxs' : 'flex-col gap-sds-xs',
'text-sds-body-xxs leading-sds-body-xxs',
collapsible && 'transition-[max-height_0.2s_ease-out]',
)}
>
{entries.slice(0, lastIndex + 1).map(({ key, entry }, i) => (
<li key={key}>
{entry}
{inlineVariant && i !== lastIndex && ', '}
{inlineVariant &&
collapsible &&
collapsed &&
i === lastIndex &&
'...'}
</li>
))}
</ul>
{collapsible && (
<div
className={cns(
Expand Down Expand Up @@ -80,7 +100,7 @@ export function CollapsibleList({
</button>
</div>
)}
</ul>
</>
) : (
<p className="text-sds-body-xxs leading-sds-body-xxs text-sds-gray-600">
{t('notSubmitted')}
Expand Down
13 changes: 13 additions & 0 deletions frontend/packages/data-portal/app/components/Run/RunHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import {
} from 'app/hooks/useMetadataDrawer'
import { useRunById } from 'app/hooks/useRunById'
import { i18n } from 'app/i18n'
import { TableDataValue } from 'app/types/table'
import { useFeatureFlag } from 'app/utils/featureFlags'
import { getTiltRangeLabel } from 'app/utils/tiltSeries'

import { CollapsibleList } from '../CollapsibleList'

interface FileSummaryData {
key: string
value: number
Expand Down Expand Up @@ -248,6 +251,16 @@ export function RunHeader() {
.map((annotation) => annotation.object_name),
),
),
renderValues: (values: TableDataValue[]) => (
<CollapsibleList
entries={values.map((value) => ({
key: value.toString(),
entry: value.toString(),
}))}
inlineVariant
collapseAfter={6}
/>
),
},
]}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,26 @@ export function MetadataTable({
{datum.renderValue?.(values[0]) ?? values[0]}
</span>
))
.otherwise(() => (
<ul className="list-none flex flex-wrap gap-1">
{values.map((value, valueIdx) => (
<li
className={cns(
'overflow-x-auto',
datum.inline && 'inline-block',
datum.className,
)}
key={value}
>
{datum.renderValue?.(value) ?? value}
{valueIdx < values.length - 1 && ', '}
</li>
))}
</ul>
))}
.otherwise(
() =>
datum.renderValues?.(values) ?? (
<ul className="list-none flex flex-wrap gap-1">
{values.map((value, valueIdx) => (
<li
className={cns(
'overflow-x-auto',
datum.inline && 'inline-block',
datum.className,
)}
key={value}
>
{datum.renderValue?.(value) ?? value}
{valueIdx < values.length - 1 && ', '}
</li>
))}
</ul>
),
)}
</TableCell>
</TableRow>
)
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/data-portal/app/types/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export interface TableData {
labelTooltip?: ReactNode
labelTooltipProps?: Partial<TooltipProps>
renderValue?(value: TableDataValue): ReactNode
renderValues?(values: TableDataValue[]): ReactNode
values: TableDataValue[] | (() => TableDataValue[])
}
16 changes: 14 additions & 2 deletions frontend/packages/data-portal/e2e/pageObjects/singleRunPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@ import { BasePage } from './basePage'

/** /runs/$id */
export class SingleRunPage extends BasePage {
async goToPage() {
await this.goTo(SINGLE_RUN_URL)
goToPage(): Promise<void> {
return this.goTo(SINGLE_RUN_URL)
}

getPrimaryViewTomogramButton(): Locator {
return this.page.locator('a:has-text("View Tomogram")')
}

findAnnotatedObjectsCell(): Locator {
return this.page.locator(`td:has-text("Annotated Objects")`).locator('+ td')
}

async findAnnotatedObjectsTexts(): Promise<Array<string>> {
return (await this.findAnnotatedObjectsCell().textContent())!.split(',')
}

findAnnotatedObjectsCollapseToggle(): Locator {
return this.findAnnotatedObjectsCell().locator('svg')
}
}
57 changes: 57 additions & 0 deletions frontend/packages/data-portal/e2e/singleRun.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { ApolloClient, NormalizedCacheObject } from '@apollo/client'
import { expect, test } from '@playwright/test'

import { getRunById } from 'app/graphql/getRunById.server'

import { getApolloClient } from './apollo'
import { E2E_CONFIG } from './constants'
import { NeuroglancerPage } from './pageObjects/neuroglancerPage'
import { SingleRunPage } from './pageObjects/singleRunPage'

test.describe('Single run page: ', () => {
let client: ApolloClient<NormalizedCacheObject>
let page: SingleRunPage
let neuroglancerPage: NeuroglancerPage
test.beforeEach(async ({ page: playwrightPage }) => {
client = getApolloClient()
page = new SingleRunPage(playwrightPage)
neuroglancerPage = new NeuroglancerPage(playwrightPage)
await page.goToPage()
Expand All @@ -33,4 +40,54 @@ test.describe('Single run page: ', () => {
await expect(neuroglancerPage.findViewer()).toBeVisible()
await expect(neuroglancerPage.findErrorText()).toHaveCount(0)
})

test('Annotated Objects collapse after 7 items', async () => {
const response = Array.from(
new Set(
(
await getRunById({
client,
id: Number(E2E_CONFIG.runId),
annotationsPage: 1,
})
).data.runs[0].tomogram_stats
.flatMap((tomogramVoxelSpacing) => tomogramVoxelSpacing.annotations)
.map((annotation) => annotation.object_name),
),
)

if (response.length > 7) {
// Collapsed:
expect((await page.findAnnotatedObjectsTexts()).length).toBe(6)
await expect(
page
.findAnnotatedObjectsCell()
.getByText(
`Show ${
response.length - (await page.findAnnotatedObjectsTexts()).length
} More`,
),
).toBeVisible()

await page.findAnnotatedObjectsCollapseToggle().click()

// Expanded:
expect((await page.findAnnotatedObjectsTexts()).length).toBe(
response.length,
)
await expect(
page.findAnnotatedObjectsCell().getByText('Show less'),
).toBeVisible()

await page.findAnnotatedObjectsCollapseToggle().click()

// Collapsed:
expect((await page.findAnnotatedObjectsTexts()).length).toBe(6)
} else {
expect((await page.findAnnotatedObjectsTexts()).length).toBe(
response.length,
)
await expect(page.findAnnotatedObjectsCollapseToggle()).toHaveCount(0)
}
})
})

0 comments on commit 9343d12

Please sign in to comment.