-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#525 - Implements the author link component with orc ID icon + link, dashed link styling, and email icon - Adds author link to dataset description and annotation side panel ## Demos https://dev-orc-id.cryoet.dev.si.czi.technology/ <img width="1132" alt="image" src="https://github.com/chanzuckerberg/cryoet-data-portal/assets/2176050/6a741bfb-3f8a-492c-9076-63448fcc9577"> <img width="447" alt="image" src="https://github.com/chanzuckerberg/cryoet-data-portal/assets/2176050/b1bc0e95-1e72-45eb-bc7c-edbc136bacb3">
- Loading branch information
1 parent
28a0738
commit 9488f2a
Showing
27 changed files
with
588 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
coverage/ | ||
node_modules/ | ||
/test-results/ | ||
/playwright-report/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
frontend/packages/data-portal/app/components/AuthorLink/AuthorLink.mock.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { ComponentProps } from 'react' | ||
|
||
import { MockLinkComponent } from 'app/components/Link' | ||
|
||
import { AuthorLink } from './AuthorLink' | ||
|
||
export function MockAuthorLink({ | ||
author, | ||
large, | ||
}: ComponentProps<typeof AuthorLink>) { | ||
return ( | ||
<AuthorLink | ||
author={author} | ||
large={large} | ||
LinkComponent={MockLinkComponent} | ||
/> | ||
) | ||
} |
62 changes: 62 additions & 0 deletions
62
frontend/packages/data-portal/app/components/AuthorLink/AuthorLink.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { it } from '@jest/globals' | ||
import { render, screen } from '@testing-library/react' | ||
import { pick } from 'lodash-es' | ||
|
||
import { MockLinkComponent } from 'app/components/Link' | ||
import { TestIds } from 'app/constants/testIds' | ||
|
||
import { AuthorLink } from './AuthorLink' | ||
import { ORC_ID_URL } from './constants' | ||
import { AuthorInfo } from './types' | ||
|
||
const DEFAULT_AUTHOR: AuthorInfo = { | ||
corresponding_author_status: true, | ||
email: '[email protected]', | ||
name: 'Actin Filament', | ||
orcid: '0000-0000-0000-0000', | ||
primary_author_status: false, | ||
} | ||
|
||
it('should not be link if orc ID is not provided', () => { | ||
render(<AuthorLink author={pick(DEFAULT_AUTHOR, 'name')} />) | ||
expect(screen.queryByRole('link')).not.toBeInTheDocument() | ||
}) | ||
|
||
it('should be a link if orc ID is provided', () => { | ||
render( | ||
<AuthorLink | ||
author={pick(DEFAULT_AUTHOR, 'name', 'orcid')} | ||
LinkComponent={MockLinkComponent} | ||
/>, | ||
) | ||
|
||
const link = screen.getByRole('link') | ||
expect(link).toBeInTheDocument() | ||
expect(link).toHaveProperty('href', `${ORC_ID_URL}/${DEFAULT_AUTHOR.orcid}`) | ||
expect(screen.getByTestId(TestIds.OrcIdIcon)).toBeInTheDocument() | ||
}) | ||
|
||
it('should have icon if user is corresponding author', () => { | ||
render( | ||
<AuthorLink | ||
author={pick(DEFAULT_AUTHOR, 'name', 'corresponding_author_status')} | ||
LinkComponent={MockLinkComponent} | ||
/>, | ||
) | ||
|
||
expect(screen.getByTestId(TestIds.EnvelopeIcon)).toBeInTheDocument() | ||
}) | ||
|
||
it('should use regular icon size', () => { | ||
render(<AuthorLink author={pick(DEFAULT_AUTHOR, 'name')} />) | ||
|
||
const text = screen.getByText(DEFAULT_AUTHOR.name) | ||
expect(text).toHaveClass('text-xs') | ||
}) | ||
|
||
it('should use large icon size', () => { | ||
render(<AuthorLink author={pick(DEFAULT_AUTHOR, 'name')} large />) | ||
|
||
const text = screen.getByText(DEFAULT_AUTHOR.name) | ||
expect(text).toHaveClass('text-sm') | ||
}) |
70 changes: 70 additions & 0 deletions
70
frontend/packages/data-portal/app/components/AuthorLink/AuthorLink.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { LinkProps } from '@remix-run/react' | ||
import { ComponentType } from 'react' | ||
|
||
import { EnvelopeIcon, ORCIDIcon } from 'app/components/icons' | ||
import { Link } from 'app/components/Link' | ||
import { cns } from 'app/utils/cns' | ||
|
||
import { ORC_ID_URL } from './constants' | ||
import { AuthorInfo } from './types' | ||
|
||
const BASE_ICON_SIZE_PX = 10 | ||
const LARGE_ICON_SIZE_PX = 14 | ||
|
||
export function AuthorLink({ | ||
author, | ||
large, | ||
LinkComponent = Link, | ||
}: { | ||
author: AuthorInfo | ||
large?: boolean | ||
LinkComponent?: ComponentType<LinkProps> | ||
}) { | ||
const iconSize = large ? LARGE_ICON_SIZE_PX : BASE_ICON_SIZE_PX | ||
const content = ( | ||
<span className="inline"> | ||
<span | ||
className={cns( | ||
'inline border-b pb-sds-xxxs', | ||
|
||
author.orcid | ||
? [ | ||
'border-dashed hover:border-solid', | ||
|
||
author.primary_author_status | ||
? 'border-black' | ||
: 'border-sds-gray-500', | ||
] | ||
: 'border-transparent', | ||
)} | ||
> | ||
{author.orcid && ( | ||
<ORCIDIcon className="inline mb-0.5" width={iconSize} /> | ||
)} | ||
|
||
<span className={cns('ml-sds-xxxs', large ? 'text-sm' : 'text-xs')}> | ||
{author.name} | ||
</span> | ||
</span> | ||
|
||
{author.corresponding_author_status && ( | ||
<EnvelopeIcon | ||
className={cns( | ||
'text-sds-gray-400 mx-sds-xxxs', | ||
'mb-2 inline-block h-sds-icon-xs w-sds-icon-xs', | ||
)} | ||
/> | ||
)} | ||
</span> | ||
) | ||
|
||
if (author.orcid) { | ||
return ( | ||
<LinkComponent to={`${ORC_ID_URL}/${author.orcid}`}> | ||
{content} | ||
</LinkComponent> | ||
) | ||
} | ||
|
||
return content | ||
} |
1 change: 1 addition & 0 deletions
1
frontend/packages/data-portal/app/components/AuthorLink/constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const ORC_ID_URL = 'https://orcid.org' |
3 changes: 3 additions & 0 deletions
3
frontend/packages/data-portal/app/components/AuthorLink/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './AuthorLink' | ||
export * from './AuthorLink.mock' | ||
export * from './types' |
10 changes: 10 additions & 0 deletions
10
frontend/packages/data-portal/app/components/AuthorLink/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Dataset_Authors } from 'app/__generated__/graphql' | ||
|
||
export type AuthorInfo = Pick< | ||
Dataset_Authors, | ||
| 'corresponding_author_status' | ||
| 'email' | ||
| 'name' | ||
| 'orcid' | ||
| 'primary_author_status' | ||
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { render, screen } from '@testing-library/react' | ||
|
||
import { AuthorInfo, MockAuthorLink } from 'app/components/AuthorLink' | ||
|
||
import { DatasetAuthors } from './DatasetAuthors' | ||
|
||
const DEFAULT_AUTHORS: AuthorInfo[] = [ | ||
{ name: 'Foo', corresponding_author_status: true }, | ||
{ name: 'Bar' }, | ||
{ name: 'Foo Bar', primary_author_status: true }, | ||
{ name: 'Foobar Foo' }, | ||
{ name: 'Foo Bar 2', primary_author_status: true }, | ||
{ name: 'Foo 2', corresponding_author_status: true }, | ||
{ name: 'Bar 2' }, | ||
] | ||
|
||
const AUTHOR_MAP = Object.fromEntries( | ||
DEFAULT_AUTHORS.map((author) => [author.name, author]), | ||
) | ||
|
||
it('should render authors', () => { | ||
render(<DatasetAuthors authors={DEFAULT_AUTHORS} />) | ||
|
||
DEFAULT_AUTHORS.forEach((author) => | ||
expect(screen.getByText(author.name)).toBeInTheDocument(), | ||
) | ||
}) | ||
|
||
it('should sort primary authors', () => { | ||
render(<DatasetAuthors authors={DEFAULT_AUTHORS} />) | ||
const authorNode = screen.getByRole('paragraph') | ||
const authors = (authorNode.textContent ?? '').split(', ') | ||
|
||
expect(AUTHOR_MAP[authors[0]].primary_author_status).toBe(true) | ||
expect(AUTHOR_MAP[authors[1]].primary_author_status).toBe(true) | ||
}) | ||
|
||
it('should sort other authors', () => { | ||
render(<DatasetAuthors authors={DEFAULT_AUTHORS} />) | ||
const authorNode = screen.getByRole('paragraph') | ||
const authors = (authorNode.textContent ?? '').split(', ') | ||
const otherAuthors = authors.slice(2, -2) | ||
|
||
otherAuthors.forEach((author) => { | ||
expect(AUTHOR_MAP[author].primary_author_status).toBeUndefined() | ||
expect(AUTHOR_MAP[author].corresponding_author_status).toBeUndefined() | ||
}) | ||
}) | ||
|
||
it('should sort corresponding authors', () => { | ||
render(<DatasetAuthors authors={DEFAULT_AUTHORS} />) | ||
const authorNode = screen.getByRole('paragraph') | ||
const authors = (authorNode.textContent ?? '').split(', ') | ||
|
||
expect(AUTHOR_MAP[authors.at(-1) ?? ''].corresponding_author_status).toBe( | ||
true, | ||
) | ||
expect(AUTHOR_MAP[authors.at(-2) ?? ''].corresponding_author_status).toBe( | ||
true, | ||
) | ||
}) | ||
|
||
it('should render author links', () => { | ||
const authors = DEFAULT_AUTHORS.map((author, idx) => ({ | ||
...author, | ||
orcid: `0000-0000-0000-000${idx}`, | ||
})) | ||
|
||
render( | ||
<DatasetAuthors authors={authors} AuthorLinkComponent={MockAuthorLink} />, | ||
) | ||
|
||
authors.forEach((author) => | ||
expect( | ||
screen.getByRole('link', { name: `${author.name}` }), | ||
).toBeInTheDocument(), | ||
) | ||
}) | ||
|
||
it('should not render author links when compact', () => { | ||
const authors = DEFAULT_AUTHORS.map((author, idx) => ({ | ||
...author, | ||
orcid: `0000-0000-0000-000${idx}`, | ||
})) | ||
|
||
render( | ||
<DatasetAuthors | ||
authors={authors} | ||
AuthorLinkComponent={MockAuthorLink} | ||
compact | ||
/>, | ||
) | ||
|
||
expect(screen.queryByRole('link')).not.toBeInTheDocument() | ||
}) | ||
|
||
it('should not render other authors when compact', () => { | ||
render(<DatasetAuthors authors={DEFAULT_AUTHORS} compact />) | ||
const authorNode = screen.getByRole('paragraph') | ||
const authors = (authorNode.textContent ?? '').split(', ') | ||
const otherAuthors = authors.slice(2, -2) | ||
|
||
otherAuthors.forEach((author) => | ||
expect(screen.queryByText(author)).not.toBeInTheDocument(), | ||
) | ||
}) | ||
|
||
it('should render comma if compact and has corresponding authors', () => { | ||
render(<DatasetAuthors authors={DEFAULT_AUTHORS} compact />) | ||
expect(screen.getByText((text) => text.includes('... ,'))).toBeInTheDocument() | ||
}) | ||
|
||
it('should not render comma for others if compact and no corresponding authors', () => { | ||
render( | ||
<DatasetAuthors | ||
authors={DEFAULT_AUTHORS.filter( | ||
(author) => !author.corresponding_author_status, | ||
)} | ||
compact | ||
/>, | ||
) | ||
|
||
expect(screen.getByText((text) => text.includes('...'))).toBeInTheDocument() | ||
expect( | ||
screen.queryByText((text) => text.includes('... ,')), | ||
).not.toBeInTheDocument() | ||
}) |
Oops, something went wrong.