Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1e76cec
Migrate Organization funder querying logic to normalized funder_ror O…
codycooperross Dec 11, 2025
c3fe7b5
Adds connection type facets for organizations
codycooperross Dec 12, 2025
fc2b180
Display citation, view, and download metrics when viewing organizatio…
codycooperross Dec 12, 2025
89f7c5f
Linting
codycooperross Dec 12, 2025
4000c26
Make prop optional
codycooperross Dec 12, 2025
ce03ff0
Fix type errors
codycooperross Dec 15, 2025
f34f3a4
Bump next from 15.4.8 to 15.4.9
dependabot[bot] Dec 12, 2025
64e55c8
Bump next from 15.4.9 to 15.4.10
dependabot[bot] Dec 12, 2025
6b003c7
Cleanup
codycooperross Dec 15, 2025
296b98f
Maintain scroll position when clicking facets
codycooperross Dec 16, 2025
bfb83a9
Styling and labeling tweaks
codycooperross Dec 16, 2025
3485070
Accessibility improvements for InfoTooltip
codycooperross Dec 17, 2025
95e408c
Update imports for consistency
codycooperross Dec 17, 2025
693e2b8
Change url check to startsWith
codycooperross Dec 17, 2025
6191795
Add client-id filter except for repository pages
codycooperross Dec 17, 2025
f860b33
Fix test
codycooperross Dec 17, 2025
8f3e3d7
Add id to InfoTooltip
codycooperross Dec 17, 2025
ffdc42c
Use REST API for related works query
codycooperross Dec 18, 2025
2b615c9
Bug fix for when relatedIdentifier or relatedIdentifier DOI is missing
codycooperross Dec 19, 2025
36b1345
Only include DataCite DOIs when searching related DOIs via relatedIde…
codycooperross Dec 19, 2025
ad8b937
Fix related query when combined with other queries
codycooperross Dec 19, 2025
7c40b23
Move related aggregate graph below work content
codycooperross Dec 19, 2025
09a1875
Add loading states to FacetListItem for loaded facet counts
codycooperross Dec 22, 2025
b039ce3
Remove to do
codycooperross Dec 22, 2025
3493c57
Linting and type checking
codycooperross Dec 22, 2025
2f5ed11
Fix for search box replacing all search params when adding or removin…
codycooperross Dec 22, 2025
49d6a60
Transition clientId variable to REST API in related works
codycooperross Dec 22, 2025
72cb9d5
Revert hasNextPage logic
codycooperross Dec 22, 2025
e54d997
Complete RelatedIdentifier type
codycooperross Dec 22, 2025
3f56d08
Remove unused loading prop
codycooperross Dec 22, 2025
257728c
Remove duplicate and incorrect active key
codycooperross Dec 22, 2025
517c28b
Remove extra space
codycooperross Dec 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cypress/e2e/searchWork.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ describe('Search Works', () => {
expect($facet.eq(4)).to.contain('Language')
expect($facet.eq(5)).to.contain('Field of Science')
expect($facet.eq(6)).to.contain('Registration Agency')
expect($facet.eq(7)).to.contain('Repository Type')
expect($facet.eq(7)).to.contain('Repository')
expect($facet.eq(8)).to.contain('Repository Type')
})
})

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"linkify-react": "^4.2.0",
"linkifyjs": "^4.2.0",
"maltipoo": "https://github.com/datacite/maltipoo#2.0.3",
"next": "15.4.8",
"next": "15.4.10",
"next-plausible": "^3.12.5",
"node-fetch": "^2.6.0",
"nuqs": "^1.16.0",
Expand Down
23 changes: 22 additions & 1 deletion src/app/(main)/doi.org/[...doi]/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { Suspense } from 'react'
import Container from 'react-bootstrap/Container'
import Row from 'react-bootstrap/Row'
import Col from 'react-bootstrap/Col'
Expand All @@ -15,6 +15,8 @@ import DownloadMetadata from 'src/components/DownloadMetadata/DownloadMetadata'
import Work from 'src/components/Work/Work'
// import { isProject } from 'src/utils/helpers';
import ExportMetadata from 'src/components/DownloadMetadata/ExportMetadata'
import RelatedContent from './RelatedContent'
import RelatedAggregateGraph from './RelatedAggregateGraph'

interface Props {
doi: string
Expand All @@ -27,6 +29,17 @@ export default async function Content(props: Props) {

const { data, error } = await fetchDoi(doi)

const relatedIdentifiers = data?.work.relatedIdentifiers || []
const relatedDois: string[] = Array.from(new Set(
relatedIdentifiers
?.filter((identifier) => identifier.relatedIdentifierType === 'DOI')
?.map((identifier) => {
const match = identifier.relatedIdentifier?.match(/(?:https?:\/\/doi\.org\/)?(.+)/)
const doi = match ? match[1] : identifier.relatedIdentifier
return doi && doi.toLowerCase()
}) || []
)).slice(0, 150)

if (error) return (
<Col md={{ span: 9, offset: 3 }}>
<Error title="An error occured." message={error.message} />
Expand All @@ -42,6 +55,7 @@ export default async function Content(props: Props) {
: 'https://doi.org/' + work.doi

return (
<>
<Container fluid>
<Row className="mb-4">
<Col md={{ offset: 3 }}>
Expand Down Expand Up @@ -82,6 +96,13 @@ export default async function Content(props: Props) {
<Work doi={work} />
</Col>
</Row>
<Row>
<Suspense>
<RelatedAggregateGraph doi={doi} />
</Suspense>
</Row>
</Container>
<RelatedContent work={work} relatedDois={relatedDois} />
</>
)
}
80 changes: 28 additions & 52 deletions src/app/(main)/doi.org/[...doi]/RelatedContent.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
'use client'

import React from 'react'
import { useParams, useSearchParams } from 'next/navigation'
import { useSearchParams } from 'next/navigation'
import Container from 'react-bootstrap/Container'
import Col from 'react-bootstrap/Col'
import Row from 'react-bootstrap/Row'

import Loading from 'src/components/Loading/Loading'

import { useDoiRelatedContentQuery } from 'src/data/queries/doiRelatedContentQuery'
import { Works } from 'src/data/types'
import { Work, Works } from 'src/data/types'

import Error from 'src/components/Error/Error'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Rename the imported Error component to avoid shadowing the global.

The import shadows the global Error constructor, which can cause confusion and unexpected behavior if the global Error is needed elsewhere in this file.

🔎 Proposed fix
-import Error from 'src/components/Error/Error'
+import ErrorDisplay from 'src/components/Error/Error'

And update the usage at line 41:

-        <Error title="An error occured loading related content." message={error.message} />
+        <ErrorDisplay title="An error occured loading related content." message={error.message} />

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 14-14: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🤖 Prompt for AI Agents
In src/app/(main)/doi.org/[...doi]/RelatedContent.tsx around line 14, the
imported name "Error" shadows the global Error constructor; rename the import to
a non-conflicting identifier (for example ErrorComponent or DoiError) in the
import statement and update all usages (notably the component usage at line 41)
to the new name so the file no longer masks the global Error.

import WorksListing from 'src/components/WorksListing/WorksListing'
import mapSearchparams from './mapSearchParams'
import { isDMP, isProject } from 'src/utils/helpers'

interface Props {
work: Work,
relatedDois: string[],
isBot?: boolean
}

export default function RelatedContent(props: Props) {
const { isBot = false } = props
const doiParams = useParams().doi as string[]
const doi = decodeURIComponent(doiParams.join('/'))
const { isBot = false, work, relatedDois } = props

const searchParams = useSearchParams()
const { variables, connectionType } = mapSearchparams(Object.fromEntries(searchParams.entries()) as any)
const { variables } = mapSearchparams(Object.fromEntries(searchParams.entries()) as any)

const vars = { id: doi, ...variables }
const vars = { relatedToDoi: work.doi, relatedDois, ...variables }
const { loading, data, error } = useDoiRelatedContentQuery(vars)

if (isBot) return null
Expand All @@ -42,55 +42,31 @@ export default function RelatedContent(props: Props) {
</Col>
</Row>

if (!data) return
if ((!data || data.works.totalCount === 0) && !searchParams) return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: The !searchParams condition is always falsy.

useSearchParams() always returns a URLSearchParams object (never null or undefined), so !searchParams will always be false. This means the early return will never trigger based on the searchParams check—only when !data || data.works.totalCount === 0.

If the intent is to return early when there are no search parameters set, use searchParams.size === 0 instead.

🔎 Proposed fix
-  if ((!data || data.works.totalCount === 0) && !searchParams) return
+  if ((!data || data.works.totalCount === 0) && searchParams.size === 0) return

Or if the searchParams check is not needed:

-  if ((!data || data.works.totalCount === 0) && !searchParams) return
+  if (!data || data.works.totalCount === 0) return null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ((!data || data.works.totalCount === 0) && !searchParams) return
if ((!data || data.works.totalCount === 0) && searchParams.size === 0) return
🤖 Prompt for AI Agents
In src/app/(main)/doi.org/[...doi]/RelatedContent.tsx around line 45, the
condition uses `!searchParams` which is always false because useSearchParams()
returns a URLSearchParams object; change the check to test for no params (e.g.
`searchParams.size === 0`) or remove the searchParams clause entirely if it
isn’t needed, so the early return accurately triggers when there are no query
parameters or no data.


const showSankey = isDMP(data.work) || isProject(data.work)
const relatedWorks = data.work
const relatedWorks = (data?.works ?? {
nodes: [],
totalCount: 0,
pageInfo: { endCursor: '', hasNextPage: false }
}) as Works

const showSankey = isDMP(work) || isProject(work)

const allRelatedCount = relatedWorks.allRelated?.totalCount || 0
const referenceCount = relatedWorks.references?.totalCount || 0
const citationCount = relatedWorks.citations?.totalCount || 0
const partCount = relatedWorks.parts?.totalCount || 0
const partOfCount = relatedWorks.partOf?.totalCount || 0
const otherRelatedCount = relatedWorks.otherRelated?.totalCount || 0

if (referenceCount + citationCount + partCount + partOfCount + otherRelatedCount === 0) return ''

const url = '/doi.org/' + relatedWorks.doi + '/?'

const connectionTypeCounts = {
allRelated: allRelatedCount,
references: referenceCount,
citations: citationCount,
parts: partCount,
partOf: partOfCount,
otherRelated: otherRelatedCount
}

const defaultConnectionType =
allRelatedCount > 0 ? 'allRelated' :
referenceCount > 0 ? 'references' :
citationCount > 0 ? 'citations' :
partCount > 0 ? 'parts' :
partOfCount > 0 ? 'partOf' : 'otherRelated'

const displayedConnectionType = connectionType ? connectionType : defaultConnectionType
const defaultConnectionType = 'allRelated'
const connectionType = searchParams.get('connection-type') || defaultConnectionType
//convert camel case to title and make first letter uppercase
//convert connectionType to title, allRelated becomes All Related Wokrs, references becomes References, citations becomes Citations, parts becomes Parts, partOf becomes Part Of, and otherRelated becomes Other Works
const displayedConnectionTitle =
displayedConnectionType === 'allRelated' ? 'All Related Works' :
displayedConnectionType === 'otherRelated' ? 'Other Works' :
displayedConnectionType.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())



connectionType === 'allRelated' ? 'All Related Works' :
connectionType === 'otherRelated' ? 'Other Works' :
connectionType.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())
Comment on lines 57 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in comment: "Wokrs" should be "Works".

🔎 Proposed fix
   //convert camel case to title and make first letter uppercase
-  //convert connectionType to title, allRelated becomes All Related Wokrs, references becomes References, citations becomes Citations, parts becomes Parts, partOf becomes Part Of, and otherRelated becomes Other Works
+  //convert connectionType to title, allRelated becomes All Related Works, references becomes References, citations becomes Citations, parts becomes Parts, partOf becomes Part Of, and otherRelated becomes Other Works
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//convert camel case to title and make first letter uppercase
//convert connectionType to title, allRelated becomes All Related Wokrs, references becomes References, citations becomes Citations, parts becomes Parts, partOf becomes Part Of, and otherRelated becomes Other Works
const displayedConnectionTitle =
displayedConnectionType === 'allRelated' ? 'All Related Works' :
displayedConnectionType === 'otherRelated' ? 'Other Works' :
displayedConnectionType.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())
connectionType === 'allRelated' ? 'All Related Works' :
connectionType === 'otherRelated' ? 'Other Works' :
connectionType.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())
//convert camel case to title and make first letter uppercase
//convert connectionType to title, allRelated becomes All Related Works, references becomes References, citations becomes Citations, parts becomes Parts, partOf becomes Part Of, and otherRelated becomes Other Works
const displayedConnectionTitle =
connectionType === 'allRelated' ? 'All Related Works' :
connectionType === 'otherRelated' ? 'Other Works' :
connectionType.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())
🤖 Prompt for AI Agents
In src/app/(main)/doi.org/[...doi]/RelatedContent.tsx around lines 57 to 62,
there is a typo in the comment: change "Wokrs" to "Works" so the comment reads
"...allRelated becomes All Related Works, references becomes References,
citations becomes Citations, parts becomes Parts, partOf becomes Part Of, and
otherRelated becomes Other Works".


const works: Works = displayedConnectionType in relatedWorks ?
relatedWorks[displayedConnectionType] :
{ totalCount: 0, nodes: [] }
const url = '/doi.org/' + work.doi + '/?'

const hasNextPage = works.pageInfo ? works.pageInfo.hasNextPage : false
const endCursor = works.pageInfo ? works.pageInfo.endCursor : ''
const hasNextPage = relatedWorks.pageInfo.hasNextPage
const endCursor = relatedWorks.pageInfo
? relatedWorks.pageInfo.endCursor
: ''

return (
<Container fluid>
Expand All @@ -101,14 +77,14 @@ export default function RelatedContent(props: Props) {
</Row>
<Row>
<WorksListing
works={works}
works={relatedWorks}
loading={loading}
connectionTypesCounts={connectionTypeCounts}
vars={vars}
showAnalytics={true}
showSankey={showSankey}
sankeyTitle={`Contributions to ${displayedConnectionTitle}`}
showClaimStatus={true}
hasPagination={works.totalCount > 25}
hasPagination={relatedWorks.totalCount > 25}
hasNextPage={hasNextPage}
model={'doi'}
url={url}
Expand Down
3 changes: 2 additions & 1 deletion src/app/(main)/doi.org/[...doi]/mapSearchParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export default function mapSearchparams(searchParams: SearchParams) {
license: searchParams.license,
fieldOfScience: searchParams['field-of-science'],
registrationAgency: searchParams['registration-agency'],
clientId: searchParams['client-id'],
connectionType: searchParams['connection-type'],
},

connectionType: searchParams['connection-type'],
isBot: false
}
}
Expand Down
6 changes: 0 additions & 6 deletions src/app/(main)/doi.org/[...doi]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { rorFromUrl } from 'src/utils/helpers'
import { fetchCrossrefFunder } from 'src/data/queries/crossrefFunderQuery'
import Content from './Content'
import { fetchDoi } from 'src/data/queries/doiQuery'
import RelatedContent from './RelatedContent'
import RelatedAggregateGraph from './RelatedAggregateGraph'
import Loading from 'src/components/Loading/Loading'
import { COMMONS_URL, LOGO_URL } from 'src/data/constants'

Expand Down Expand Up @@ -94,10 +92,6 @@ export default async function Page(props: Props) {
<Suspense fallback={<Loading />}>
<Content doi={doi} />
</Suspense>
<Suspense>
<RelatedAggregateGraph doi={doi} />
</Suspense>
<RelatedContent />
<Script type="application/ld+json" id="schemaOrg">{data.work.schemaOrg}</Script>
</>
}
3 changes: 2 additions & 1 deletion src/app/(main)/doi.org/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export default async function SearchDoiPage(props: Props) {
resourceTypeId: vars['resource-type'],
fieldOfScience: vars['field-of-science'],
registrationAgency: vars['registration-agency'],
clientType: vars['repository-type']
clientType: vars['repository-type'],
clientId: vars['client-id'],
}

// Show example text if there is no query
Expand Down
1 change: 1 addition & 0 deletions src/app/(main)/orcid.org/[orcid]/mapSearchParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function mapSearchparams(searchParams: SearchParams) {
license: searchParams.license,
fieldOfScience: searchParams['field-of-science'],
registrationAgency: searchParams['registration-agency'],
clientId: searchParams['client-id'],
},

isBot: false
Expand Down
73 changes: 35 additions & 38 deletions src/app/(main)/ror.org/[rorid]/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import DownloadReports from 'src/components/DownloadReports/DownloadReports'
import OrganizationMetadata from 'src/components/OrganizationMetadata/OrganizationMetadata'
import SummarySearchMetrics from 'src/components/SummarySearchMetrics/SummarySearchMetrics'
import Loading from 'src/components/Loading/Loading'
import RelatedContent from 'src/app/(main)/ror.org/[rorid]/RelatedContent';

interface Props {
rorid: string
Expand All @@ -25,7 +24,6 @@ export default function Content(props: Props) {
const { data, error, loading } = useROROrganization(rorId)
if (loading) return <Loading />
const organization = data?.organization || {} as OrganizationType
const rorFundingIds = organization.identifiers?.filter((id) => id.identifierType === 'fundref').map((id) => id.identifier) || []

if (error || !organization) return (
<Col md={{ span: 9, offset: 3 }}>
Expand All @@ -35,43 +33,42 @@ export default function Content(props: Props) {

return (
<>
<Container fluid>
<Row className="mb-4">
<Col md={{ offset: 3 }}>
<Title title={organization.name} titleLink={organization.id} link={organization.id} />
</Col>
</Row>
<Container fluid>
<Row className="mb-4">
<Col md={{ offset: 3 }}>
<Title title={organization.name} titleLink={organization.id} link={organization.id} />
</Col>
</Row>

<Row>
<Col md={3}>
<DownloadReports
links={[
{
title: 'Related Works (CSV)',
helpText: 'Includes descriptions and formatted citations in APA style for up to 200 DOIs associated with this organization.',
type: 'ror/related-works',
},
{
title: 'Funders (CSV)',
helpText: 'Includes up to 200 funders associated with related works.',
type: 'ror/funders',
}
]}
variables={{ rorId }}
/>
</Col>
<Col md={9} className="px-0">
<SummarySearchMetrics rorId={organization.id} rorFundingIds={rorFundingIds} />
{organization.inceptionYear && (
<p className="mb-3">Founded {organization.inceptionYear}</p>
)}
<OrganizationMetadata metadata={organization}
linkToExternal={false}
showTitle={false} />
</Col>
</Row>
</Container>
<RelatedContent isBot={props.isBot} rorId={rorId} rorFundingIds={rorFundingIds} />
<Row>
<Col md={3}>
<DownloadReports
links={[
{
title: 'Related Works (CSV)',
helpText: 'Includes descriptions and formatted citations in APA style for up to 200 DOIs associated with this organization.',
type: 'ror/related-works',
},
{
title: 'Funders (CSV)',
helpText: 'Includes up to 200 funders associated with related works.',
type: 'ror/funders',
}
]}
variables={{ rorId }}
/>
</Col>
<Col md={9} className="px-0">
<SummarySearchMetrics rorId={organization.id}/>
{organization.inceptionYear && (
<p className="mb-3">Founded {organization.inceptionYear}</p>
)}
<OrganizationMetadata metadata={organization}
linkToExternal={false}
showTitle={false} />
</Col>
</Row>
</Container>
</>
)
}
9 changes: 6 additions & 3 deletions src/app/(main)/ror.org/[rorid]/RelatedContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ import mapSearchparams from './mapSearchParams';
interface Props {
isBot?: boolean
rorId: string
rorFundingIds: string[]
}

export default function RelatedContent(props: Props) {
const { isBot = false, rorId, rorFundingIds } = props
const { isBot = false, rorId } = props

const searchParams = useSearchParams()
const { variables } = mapSearchparams(Object.fromEntries(searchParams.entries()) as any)

const vars = { rorId, rorFundingIds, ...variables }
const vars = { rorId, ...variables }
const { loading, data, error } = useOrganizationRelatedContentQuery(vars)

const showMetrics = searchParams.size > 0

if (isBot) return null

Expand Down Expand Up @@ -60,7 +61,9 @@ export default function RelatedContent(props: Props) {
</Row>
<WorksListing
works={relatedWorks}
vars={vars}
loading={loading}
showMetrics={showMetrics}
showAnalytics={true}
showClaimStatus={true}
hasPagination={relatedWorks.totalCount > 25}
Expand Down
4 changes: 3 additions & 1 deletion src/app/(main)/ror.org/[rorid]/mapSearchParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export default function mapSearchparams(searchParams: SearchParams) {
fieldOfScience: searchParams['field-of-science'],
license: searchParams.license,
registrationAgency: searchParams['registration-agency'],
clientType: searchParams['repository-type']
clientId: searchParams['client-id'],
clientType: searchParams['repository-type'],
organizationRelationType: searchParams['organization-relation-type'] || 'allRelated',
},

isBot: false
Expand Down
Loading