Skip to content

Commit e09d2ae

Browse files
authored
Merge pull request #3543 from OpenNeuroOrg/feature/app-contributors-gql-components
feat(app): Query and display components for contributors
2 parents b84fe68 + 8d92938 commit e09d2ae

File tree

14 files changed

+271
-26
lines changed

14 files changed

+271
-26
lines changed

packages/openneuro-app/src/scripts/datalad/dataset/dataset-query-fragments.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ export const DRAFT_FRAGMENT = gql`
6565
familyName
6666
orcid
6767
}
68+
contributors {
69+
name
70+
givenName
71+
familyName
72+
orcid
73+
}
6874
}
6975
}
7076
`
@@ -247,6 +253,12 @@ export const SNAPSHOT_FIELDS = gql`
247253
familyName
248254
orcid
249255
}
256+
contributors {
257+
name
258+
givenName
259+
familyName
260+
orcid
261+
}
250262
}
251263
${SNAPSHOT_ISSUES}
252264
`

packages/openneuro-app/src/scripts/dataset/__tests__/__snapshots__/snapshot-container.spec.tsx.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,14 +786,14 @@ OCI-1131441 (R. Poldrack, PI) in any publications.
786786
N/A
787787
</div>
788788
<div
789-
class="dataset-meta-block dmb-inline-list"
789+
class="dataset-meta-block "
790790
>
791791
<h2
792792
class="dmb-heading"
793793
>
794794
Authors
795795
</h2>
796-
J. Doe, J. Doe, J. Doe
796+
N/A
797797
</div>
798798
<div
799799
class="dataset-meta-block dmb-modalities"

packages/openneuro-app/src/scripts/dataset/draft-container.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { TabRoutesDraft } from "./routes/tab-routes-draft"
3535
import { FollowToggles } from "./common/follow-toggles"
3636
import { DateDistance } from "../components/date-distance"
3737
import { CreatorListDisplay } from "../users/creators-list"
38+
import { ContributorsListDisplay } from "../users/contributors-list"
3839

3940
export interface DraftContainerProps {
4041
dataset
@@ -184,8 +185,18 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
184185
/>
185186
}
186187
/>
188+
<MetaDataBlock
189+
heading="Authors"
190+
item={
191+
<ContributorsListDisplay
192+
contributors={dataset.draft.contributors}
193+
/>
194+
}
195+
/>
187196

188-
<EditDescriptionList
197+
{
198+
/* do we need to account for inline editing on the author list
199+
<EditDescriptionList
189200
className="dmb-inline-list"
190201
datasetId={datasetId}
191202
field="Authors"
@@ -194,7 +205,8 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
194205
editMode={hasEdit}
195206
>
196207
{description?.Authors?.length ? description.Authors : ["N/A"]}
197-
</EditDescriptionList>
208+
</EditDescriptionList> */
209+
}
198210
{summary && (
199211
<ModalitiesMetaDataBlock
200212
items={summary.modalities}

packages/openneuro-app/src/scripts/dataset/snapshot-container.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import schemaGenerator from "../utils/json-ld.js"
4040
import { FollowToggles } from "./common/follow-toggles"
4141
import { DateDistance } from "../components/date-distance"
4242
import { CreatorListDisplay } from "../users/creators-list"
43+
import { ContributorsListDisplay } from "../users/contributors-list"
4344

4445
// Helper function for getting version from URL
4546
const snapshotVersion = (location) => {
@@ -186,14 +187,15 @@ export const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
186187
/>
187188
}
188189
/>
189-
190190
<MetaDataBlock
191191
heading="Authors"
192-
item={description?.Authors?.length
193-
? description.Authors.join(", ")
194-
: "N/A"}
195-
className="dmb-inline-list"
192+
item={
193+
<ContributorsListDisplay
194+
contributors={snapshot.contributors}
195+
/>
196+
}
196197
/>
198+
197199
<>
198200
{summary && (
199201
<ModalitiesMetaDataBlock

packages/openneuro-app/src/scripts/search/components/SearchResultDetails.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { SearchResultItemProps } from "./SearchResultItem"
88
import { ModalityLabel } from "../../components/formatting/modality-label"
99
import { MetaListItemList } from "./MetaListItemList"
1010
import { CreatorListDisplay } from "../../users/creators-list"
11+
import { ContributorsListDisplay } from "../../users/contributors-list"
1112
import "../scss/search-result-details.scss"
1213

1314
interface SearchResultDetailsProps {
@@ -127,13 +128,21 @@ export const SearchResultDetails: FC<SearchResultDetailsProps> = (
127128
{itemData?.id}
128129
</Link>,
129130
)
130-
const authors = renderMetaItem(
131-
"Authors",
131+
const creators = renderMetaItem(
132+
"Creators",
132133
<CreatorListDisplay
133134
creators={itemData.latestSnapshot?.creators}
134135
separator=", "
135136
/>,
136137
)
138+
139+
const contributors = renderMetaItem(
140+
"Contributors",
141+
<ContributorsListDisplay
142+
contributors={itemData.latestSnapshot?.contributors}
143+
separator=", "
144+
/>,
145+
)
137146
const uploaderDisplay = renderMetaItem(
138147
"Uploader by",
139148
<div>
@@ -154,7 +163,8 @@ export const SearchResultDetails: FC<SearchResultDetailsProps> = (
154163
&times;
155164
</button>
156165
{moreDetailsHeader}
157-
{authors}
166+
{creators}
167+
{contributors}
158168
{modalityList}
159169
{taskList}
160170
{accessionNumberDisplay}

packages/openneuro-app/src/scripts/search/components/SearchResultItem.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import "../scss/search-result.scss"
99
import activityPulseIcon from "../../../assets/activity-icon.png"
1010
import { hasEditPermissions } from "../../authentication/profile"
1111
import { ModalityHexagon } from "../../components/modality-cube/ModalityHexagon"
12-
import type { Creator } from "../../types/creators"
12+
import type { Contributor, Creator } from "../../types/datacite"
1313
import { SearchResultsCitation } from "../../components/citation/search-results-citation"
1414

1515
export const formatDate = (dateObject) =>
@@ -88,6 +88,7 @@ export interface SearchResultItemProps {
8888
DatasetDOI: string
8989
}
9090
creators: Creator[]
91+
contributors: Contributor[]
9192
}
9293
analytics: {
9394
views: number

packages/openneuro-app/src/scripts/search/use-search-results.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ const searchQuery = gql`
104104
familyName
105105
orcid
106106
}
107+
contributors {
108+
name
109+
givenName
110+
familyName
111+
orcid
112+
contributorType
113+
}
107114
}
108115
analytics {
109116
views
@@ -176,6 +183,7 @@ export const useSearchResults = () => {
176183
"latestSnapshot.description.Name^6",
177184
"latestSnapshot.description.Authors^3", // TODO: Nell - do we need this still?
178185
"latestSnapshot.creators.name^3",
186+
"latestSnapshot.contributors.name^2",
179187
]),
180188
)
181189
}
@@ -271,13 +279,23 @@ export const useSearchResults = () => {
271279
)
272280
}
273281
if (authors.length) { // TODO - NELL - this was switched to creators - is that correct?
282+
const authorQuery = matchQuery(
283+
"latestSnapshot.creators.name",
284+
joinWithOR(authors),
285+
"2",
286+
)
287+
const contributorQuery = matchQuery(
288+
"latestSnapshot.contributors.name",
289+
joinWithOR(authors),
290+
"2",
291+
)
274292
boolQuery.addClause(
275293
"must",
276-
matchQuery(
277-
"latestSnapshot.creators.name",
278-
joinWithOR(authors),
279-
"2",
280-
),
294+
{
295+
bool: {
296+
should: [authorQuery, contributorQuery],
297+
},
298+
},
281299
)
282300
}
283301
if (sex_selected !== "All") {

packages/openneuro-app/src/scripts/types/creators.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export interface Creator {
2+
name?: string
3+
givenName?: string
4+
familyName?: string
5+
orcid?: string
6+
}
7+
8+
export interface Contributor {
9+
name?: string
10+
givenName?: string
11+
familyName?: string
12+
orcid?: string
13+
contributorType?: string
14+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from "react"
2+
import { render, screen } from "@testing-library/react"
3+
import { MemoryRouter } from "react-router-dom"
4+
import { MockedProvider } from "@apollo/client/testing"
5+
import { vi } from "vitest"
6+
import { SingleContributorDisplay } from "../contributor"
7+
8+
// --- Mock Dependencies ---
9+
vi.mock("../../queries/user", () => ({
10+
useUser: vi.fn(() => ({
11+
user: null,
12+
loading: false,
13+
error: undefined,
14+
})),
15+
}))
16+
17+
import { useUser } from "../../queries/user"
18+
19+
vi.mock("../../assets/ORCIDiD_iconvector.svg", () => ({
20+
default: "mock-orcid-logo.svg",
21+
}))
22+
23+
describe("SingleContributorDisplay - Basic Loading", () => {
24+
const renderComponent = (props: {
25+
contributor: { name?: string; orcid?: string | null }
26+
isLast?: boolean
27+
separator?: React.ReactNode
28+
}) => {
29+
return render(
30+
<MemoryRouter>
31+
<MockedProvider>
32+
<SingleContributorDisplay
33+
contributor={props.contributor}
34+
isLast={props.isLast ?? true}
35+
separator={props.separator ?? null}
36+
/>
37+
</MockedProvider>
38+
</MemoryRouter>,
39+
)
40+
}
41+
42+
beforeEach(() => {
43+
vi.clearAllMocks()
44+
vi.mocked(useUser).mockImplementation((userId) => {
45+
if (userId) {
46+
return {
47+
user: { id: userId, name: `Mock User ${userId}` },
48+
loading: false,
49+
error: undefined,
50+
}
51+
}
52+
return { user: null, loading: false, error: undefined }
53+
})
54+
})
55+
56+
it("renders the component and displays the contributor name", async () => {
57+
const contributor = { name: "Jane Doe", orcid: null }
58+
renderComponent({ contributor })
59+
expect(screen.getByText("Jane Doe")).toBeInTheDocument()
60+
})
61+
62+
it("renders 'Unknown Contributor' if the contributor name is missing", async () => {
63+
const contributor = { name: undefined, orcid: null }
64+
renderComponent({ contributor })
65+
expect(screen.getByText("Unknown Contributor")).toBeInTheDocument()
66+
})
67+
68+
it("renders the component and displays the ORCID link if an ID is provided", async () => {
69+
const testOrcid = "0000-0000-0000-0000"
70+
const contributor = { name: "Author With ORCID", orcid: testOrcid }
71+
renderComponent({ contributor })
72+
const orcidLink = screen.getByLabelText(
73+
`ORCID profile for ${contributor.name}`,
74+
)
75+
expect(orcidLink).toBeInTheDocument()
76+
expect(orcidLink).toHaveAttribute(
77+
"href",
78+
expect.stringContaining(testOrcid),
79+
)
80+
})
81+
})

0 commit comments

Comments
 (0)