diff --git a/cypress/e2e/searchWork.test.ts b/cypress/e2e/searchWork.test.ts
index a03e51ca..83f48fd8 100644
--- a/cypress/e2e/searchWork.test.ts
+++ b/cypress/e2e/searchWork.test.ts
@@ -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')
})
})
diff --git a/package.json b/package.json
index 850d13d3..5635cf82 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/(main)/doi.org/[...doi]/Content.tsx b/src/app/(main)/doi.org/[...doi]/Content.tsx
index 64aee9a5..bbe723df 100644
--- a/src/app/(main)/doi.org/[...doi]/Content.tsx
+++ b/src/app/(main)/doi.org/[...doi]/Content.tsx
@@ -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'
@@ -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
@@ -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 (
@@ -42,6 +55,7 @@ export default async function Content(props: Props) {
: 'https://doi.org/' + work.doi
return (
+ <>
@@ -82,6 +96,13 @@ export default async function Content(props: Props) {
+
+
+
+
+
+
+ >
)
}
diff --git a/src/app/(main)/doi.org/[...doi]/RelatedContent.tsx b/src/app/(main)/doi.org/[...doi]/RelatedContent.tsx
index 75c797a9..8082b6f5 100644
--- a/src/app/(main)/doi.org/[...doi]/RelatedContent.tsx
+++ b/src/app/(main)/doi.org/[...doi]/RelatedContent.tsx
@@ -1,7 +1,7 @@
'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'
@@ -9,7 +9,7 @@ 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'
import WorksListing from 'src/components/WorksListing/WorksListing'
@@ -17,18 +17,18 @@ 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
@@ -42,55 +42,31 @@ export default function RelatedContent(props: Props) {
- if (!data) return
+ if ((!data || data.works.totalCount === 0) && !searchParams) return
- 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())
- 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 (
@@ -101,14 +77,14 @@ export default function RelatedContent(props: Props) {
25}
+ hasPagination={relatedWorks.totalCount > 25}
hasNextPage={hasNextPage}
model={'doi'}
url={url}
diff --git a/src/app/(main)/doi.org/[...doi]/mapSearchParams.ts b/src/app/(main)/doi.org/[...doi]/mapSearchParams.ts
index bc94e7da..db32df5a 100644
--- a/src/app/(main)/doi.org/[...doi]/mapSearchParams.ts
+++ b/src/app/(main)/doi.org/[...doi]/mapSearchParams.ts
@@ -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
}
}
diff --git a/src/app/(main)/doi.org/[...doi]/page.tsx b/src/app/(main)/doi.org/[...doi]/page.tsx
index fda13004..ce78b377 100644
--- a/src/app/(main)/doi.org/[...doi]/page.tsx
+++ b/src/app/(main)/doi.org/[...doi]/page.tsx
@@ -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'
@@ -94,10 +92,6 @@ export default async function Page(props: Props) {
}>
-
-
-
-
>
}
diff --git a/src/app/(main)/doi.org/page.tsx b/src/app/(main)/doi.org/page.tsx
index ba59d2d7..2a14beb3 100644
--- a/src/app/(main)/doi.org/page.tsx
+++ b/src/app/(main)/doi.org/page.tsx
@@ -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
diff --git a/src/app/(main)/orcid.org/[orcid]/mapSearchParams.ts b/src/app/(main)/orcid.org/[orcid]/mapSearchParams.ts
index 31704398..db028949 100644
--- a/src/app/(main)/orcid.org/[orcid]/mapSearchParams.ts
+++ b/src/app/(main)/orcid.org/[orcid]/mapSearchParams.ts
@@ -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
diff --git a/src/app/(main)/ror.org/[rorid]/Content.tsx b/src/app/(main)/ror.org/[rorid]/Content.tsx
index f22d5eaf..9ebcae0b 100644
--- a/src/app/(main)/ror.org/[rorid]/Content.tsx
+++ b/src/app/(main)/ror.org/[rorid]/Content.tsx
@@ -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
@@ -25,7 +24,6 @@ export default function Content(props: Props) {
const { data, error, loading } = useROROrganization(rorId)
if (loading) return
const organization = data?.organization || {} as OrganizationType
- const rorFundingIds = organization.identifiers?.filter((id) => id.identifierType === 'fundref').map((id) => id.identifier) || []
if (error || !organization) return (
@@ -35,43 +33,42 @@ export default function Content(props: Props) {
return (
<>
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
- {organization.inceptionYear && (
- Founded {organization.inceptionYear}
- )}
-
-
-
-
-
+
+
+
+
+
+
+ {organization.inceptionYear && (
+ Founded {organization.inceptionYear}
+ )}
+
+
+
+
>
)
}
diff --git a/src/app/(main)/ror.org/[rorid]/RelatedContent.tsx b/src/app/(main)/ror.org/[rorid]/RelatedContent.tsx
index 5dfabcf1..b15da90d 100644
--- a/src/app/(main)/ror.org/[rorid]/RelatedContent.tsx
+++ b/src/app/(main)/ror.org/[rorid]/RelatedContent.tsx
@@ -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
@@ -60,7 +61,9 @@ export default function RelatedContent(props: Props) {
25}
diff --git a/src/app/(main)/ror.org/[rorid]/mapSearchParams.ts b/src/app/(main)/ror.org/[rorid]/mapSearchParams.ts
index b0d5f7d9..5ebc90c6 100644
--- a/src/app/(main)/ror.org/[rorid]/mapSearchParams.ts
+++ b/src/app/(main)/ror.org/[rorid]/mapSearchParams.ts
@@ -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
diff --git a/src/app/(main)/ror.org/[rorid]/page.tsx b/src/app/(main)/ror.org/[rorid]/page.tsx
index 5b9bf01a..fabc6ae5 100644
--- a/src/app/(main)/ror.org/[rorid]/page.tsx
+++ b/src/app/(main)/ror.org/[rorid]/page.tsx
@@ -4,6 +4,7 @@ import { notFound } from 'next/navigation'
import Content from './Content'
import { RORV2Client } from 'src/data/clients/ror-v2-client'
import { COMMONS_URL, LOGO_URL } from 'src/data/constants'
+import RelatedContent from './RelatedContent'
interface Props {
params: Promise<{
@@ -56,6 +57,7 @@ export default async function Page(props: Props) {
return (
<>
+
>
)
}
diff --git a/src/components/FacetList/FacetList.tsx b/src/components/FacetList/FacetList.tsx
index 93c504aa..847201d9 100644
--- a/src/components/FacetList/FacetList.tsx
+++ b/src/components/FacetList/FacetList.tsx
@@ -1,15 +1,11 @@
'use client'
import React from 'react'
-import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
-import Tooltip from 'react-bootstrap/Tooltip'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import FacetListItem from './FacetListItem'
import { Facet } from 'src/data/types'
-import { faQuestionCircle } from '@fortawesome/free-regular-svg-icons'
import Accordion from 'react-bootstrap/Accordion';
import styles from './FacetList.module.scss'
-
+import { InfoTooltip } from '../InfoTooltip/InfoTooltip';
interface FacetListProps {
data: Facet[] | undefined
@@ -26,21 +22,6 @@ interface FacetListProps {
tooltipText?: string
}
-// Custom InfoTooltip component
-const InfoTooltip = ({ text }) => (
- {text}}
- >
- e.stopPropagation()}
- className="ms-2"
- style={{ cursor: 'help' }}
- >
-
-
-);
-
export default function FacetList(props: FacetListProps) {
const { data, title, id, param, url, value, checked, radio } = props
if (!data || data.length === 0) return null
@@ -62,6 +43,7 @@ export default function FacetList(props: FacetListProps) {
value={value && value(facet.id)}
checked={checked && checked(i)}
radio={radio}
+ tooltipText={facet.tooltipText}
/>
))}
diff --git a/src/components/FacetList/FacetListItem.tsx b/src/components/FacetList/FacetListItem.tsx
index 258f40a9..26082fbf 100644
--- a/src/components/FacetList/FacetListItem.tsx
+++ b/src/components/FacetList/FacetListItem.tsx
@@ -1,8 +1,7 @@
'use client'
import React from 'react'
-import Link from 'next/link'
-import { useSearchParams } from 'next/navigation'
+import { useRouter, useSearchParams } from 'next/navigation'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faSquare, faCheckSquare,
@@ -11,28 +10,45 @@ import {
import { Facet } from 'src/data/types'
import styles from './FacetListItem.module.scss'
import ListGroup from 'react-bootstrap/ListGroup';
+import { InfoTooltip } from '../InfoTooltip/InfoTooltip';
+import ContentLoader from "react-content-loader"
interface Props {
facet: Facet
param: string
url: string
-
+ tooltipText?: string
checked?: boolean
radio?: boolean
value?: string
}
+const LoadingCount = () => (
+
+
+
+)
+
export default function FacetListItem(props: Props) {
const {
facet,
param,
url,
+ tooltipText,
checked = false,
radio = false,
value: customValue
} = props
+ const router = useRouter()
const checkIcon = radio ? faDotCircle : faCheckSquare
const uncheckIcon = radio ? faCircle : faSquare
@@ -53,13 +69,37 @@ export default function FacetListItem(props: Props) {
}
params.delete('cursor')
+ const handleClick = () => {
+ router.push(url + params.toString(), { scroll: false })
+ }
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ handleClick()
+ }
+ }
+
+ if (!facet.loading && facet.count === 0) {
+ return null
+ }
+
return (
-
+
- {facet.title}
- {facet.count.toLocaleString('en-US')}
-
+
+ {facet.title}
+ {tooltipText && }
+
+ {facet.loading ? : {facet.count.toLocaleString('en-US')}}
+
)
}
diff --git a/src/components/InfoTooltip/InfoTooltip.tsx b/src/components/InfoTooltip/InfoTooltip.tsx
new file mode 100644
index 00000000..737254cf
--- /dev/null
+++ b/src/components/InfoTooltip/InfoTooltip.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
+import Tooltip from 'react-bootstrap/Tooltip'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faQuestionCircle } from '@fortawesome/free-regular-svg-icons'
+
+interface InfoTooltipProps {
+ text: string
+}
+
+export const InfoTooltip = ({ text }: InfoTooltipProps) => (
+ {text}}
+ >
+ e.stopPropagation()}
+ className="ms-2"
+ style={{ cursor: 'help' }}
+ role="button"
+ tabIndex={0}
+ aria-label={text}
+ >
+
+
+
+)
\ No newline at end of file
diff --git a/src/components/MetricsDisplay/MetricsDisplay.module.scss b/src/components/MetricsDisplay/MetricsDisplay.module.scss
index a02e80b5..61c9fe4f 100644
--- a/src/components/MetricsDisplay/MetricsDisplay.module.scss
+++ b/src/components/MetricsDisplay/MetricsDisplay.module.scss
@@ -1,6 +1,5 @@
.metrics {
grid-row: 2;
- padding-bottom: 30px;
max-width: 600px;
dl {
@@ -12,11 +11,12 @@
dt {
grid-row: 2;
font-size: 1.2rem;
+ padding-bottom: 1rem;
}
dd{
grid-row: 1;
- border-top: 2px solid black;
font-size: 1.5rem;
+ padding-top: 1rem;
}
dt, dd{
line-height: 1;
diff --git a/src/components/MetricsDisplay/MetricsDisplay.tsx b/src/components/MetricsDisplay/MetricsDisplay.tsx
index 1da31e83..f341e827 100644
--- a/src/components/MetricsDisplay/MetricsDisplay.tsx
+++ b/src/components/MetricsDisplay/MetricsDisplay.tsx
@@ -19,9 +19,11 @@ type Props = {
views?: string
downloads?: string
}
+
+ displayWorksTotal?: boolean
}
-export function MetricsDisplay({ counts, links = {} }: Props) {
+export function MetricsDisplay({ counts, links = {}, displayWorksTotal = true }: Props) {
const metricsData = [
{
@@ -47,6 +49,7 @@ export function MetricsDisplay({ counts, links = {} }: Props) {
];
const metricList = metricsData.filter(metric => metric.count && metric.count > 0).map((metric, index) =>
+ metric.label === "Works" && !displayWorksTotal ? null :
{compactNumbers(metric.count || 0)}
{metric.label}
diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx
index 96b2b2ad..247200cf 100644
--- a/src/components/SearchBox/SearchBox.tsx
+++ b/src/components/SearchBox/SearchBox.tsx
@@ -17,8 +17,11 @@ export default function SearchBox({ path }: Props) {
const [searchInput, setSearchInput] = useState(params?.get('filterQuery') || '')
const onSubmit = () => {
- if (router)
- router.push(`${path}?filterQuery=${searchInput}`)
+ if (router) {
+ const newParams = new URLSearchParams(params)
+ newParams.set('filterQuery', searchInput)
+ router.push(`${path}?${newParams}`)
+ }
}
const onKeyDown = (event: React.KeyboardEvent) => {
@@ -33,7 +36,9 @@ export default function SearchBox({ path }: Props) {
const onSearchClear = () => {
setSearchInput('')
- router.replace(path);
+ const newParams = new URLSearchParams(params)
+ newParams.delete('filterQuery')
+ router.replace(`${path}?${newParams}`)
}
const searchBoxStyle = {
'--bs-heading-color': '#1abc9c',
diff --git a/src/components/SummarySearchMetrics/SummarySearchMetrics.tsx b/src/components/SummarySearchMetrics/SummarySearchMetrics.tsx
index 218f71e5..52fcea8c 100644
--- a/src/components/SummarySearchMetrics/SummarySearchMetrics.tsx
+++ b/src/components/SummarySearchMetrics/SummarySearchMetrics.tsx
@@ -42,5 +42,7 @@ export default function SummarySearchMetrics(variables: QueryVar) {
if (isLoading) return
if (!data || error) return null
- return
+ const displayWorksTotal = !variables.organizationRelationType
+
+ return
}
diff --git a/src/components/WorkFacets/WorkFacets.tsx b/src/components/WorkFacets/WorkFacets.tsx
index e7ae00dd..c685038d 100644
--- a/src/components/WorkFacets/WorkFacets.tsx
+++ b/src/components/WorkFacets/WorkFacets.tsx
@@ -7,12 +7,13 @@ import AuthorsFacet from '../AuthorsFacet/AuthorsFacet'
import { Work, Facet } from 'src/data/types'
import FacetList from '../FacetList/FacetList'
import FacetListGroup from '../FacetList/FacetListGroup'
+import { QueryVar, useSearchDoiQuery } from 'src/data/queries/searchDoiQuery'
interface Props {
data: Facets
model: string
url: string
- connectionTypesCounts?: { references: number, citations: number, parts: number, partOf: number, otherRelated: number, allRelated: number }
+ vars?: QueryVar
}
interface Facets {
@@ -26,6 +27,7 @@ interface Facets {
registrationAgencies?: Facet[]
authors?: Facet[]
creatorsAndContributors?: Facet[]
+ clients?: Facet[]
clientTypes?: Facet[]
nodes: Work[]
}
@@ -38,7 +40,7 @@ export default function WorkFacets({
data,
model,
url,
- connectionTypesCounts
+ vars
}: Props) {
// get current query parameters from next router
@@ -47,17 +49,52 @@ export default function WorkFacets({
// remove %2F? at the end of url
const path = url.substring(0, url.length - 2)
- const connectionTypeList: Facet[] = connectionTypesCounts ? [
- { id: 'allRelated', title: 'All', count: connectionTypesCounts.allRelated },
- { id: 'references', title: 'References', count: connectionTypesCounts.references },
- { id: 'citations', title: 'Citations', count: connectionTypesCounts.citations },
- { id: 'parts', title: 'Parts', count: connectionTypesCounts.parts },
- { id: 'partOf', title: 'Is Part Of', count: connectionTypesCounts.partOf },
- { id: 'otherRelated', title: 'Other', count: connectionTypesCounts.otherRelated }
- ] : []
+ const useConnectionQuery = (connectionType: string) =>
+ vars?.relatedToDoi
+ ? useSearchDoiQuery({ ...vars, connectionType, pageSize: 0 })
+ : { loading: false, data: undefined, error: undefined }
+
+ const allRelatedQuery = useConnectionQuery('allRelated')
+ const referencesQuery = useConnectionQuery('references')
+ const citationsQuery = useConnectionQuery('citations')
+ const partsQuery = useConnectionQuery('parts')
+ const partOfQuery = useConnectionQuery('partOf')
+ const versionOfQuery = useConnectionQuery('versionOf')
+ const versionsQuery = useConnectionQuery('versions')
+ const otherRelatedQuery = useConnectionQuery('otherRelated')
+
+ const connectionTypeList: Facet[] = [
+ { id: 'allRelated', title: 'All', count: allRelatedQuery.data?.works?.totalCount ?? 0, loading: allRelatedQuery.loading },
+ { id: 'references', title: 'References', count: referencesQuery.data?.works?.totalCount ?? 0, loading: referencesQuery.loading },
+ { id: 'citations', title: 'Citations', count: citationsQuery.data?.works?.totalCount ?? 0, loading: citationsQuery.loading },
+ { id: 'parts', title: 'Parts', count: partsQuery.data?.works?.totalCount ?? 0, loading: partsQuery.loading },
+ { id: 'partOf', title: 'Is Part Of', count: partOfQuery.data?.works?.totalCount ?? 0, loading: partOfQuery.loading },
+ { id: 'versionOf', title: 'Version Of', count: versionOfQuery.data?.works?.totalCount ?? 0, loading: versionOfQuery.loading },
+ { id: 'versions', title: 'Versions', count: versionsQuery.data?.works?.totalCount ?? 0, loading: versionsQuery.loading },
+ { id: 'otherRelated', title: 'Other', count: otherRelatedQuery.data?.works?.totalCount ?? 0, loading: otherRelatedQuery.loading }
+ ]
+
+ const useOrganizationRelationQuery = (organizationRelationType: string) =>
+ vars?.rorId
+ ? useSearchDoiQuery({ ...vars, organizationRelationType, pageSize: 0 })
+ : { loading: false, data: undefined, error: undefined }
+
+ const organizationAllRelatedQuery = useOrganizationRelationQuery('allRelated')
+ const organizationCreatedOrContributedByAffiliatedResearcherQuery = useOrganizationRelationQuery('createdOrContributedByAffiliatedResearcher')
+ const organizationCreatedContributedOrPublishedByQuery = useOrganizationRelationQuery('createdContributedOrPublishedBy')
+ const organizationFundedByQuery = useOrganizationRelationQuery('fundedBy')
+
+ const organizationRelationTypeList: Facet[] = [
+ { id: "allRelated", title: "All", count: organizationAllRelatedQuery.data?.works?.totalCount ?? 0, loading: organizationAllRelatedQuery.loading },
+ { id: "createdOrContributedByAffiliatedResearcher", title: "By Affiliated Researchers", tooltipText: "Works created or contributed by researchers affiliated with the organization.", count: organizationCreatedOrContributedByAffiliatedResearcherQuery.data?.works?.totalCount ?? 0, loading: organizationCreatedOrContributedByAffiliatedResearcherQuery.loading },
+ { id: "createdContributedOrPublishedBy", title: "Created By", tooltipText: "Works created, contributed, or published by the organization.", count: organizationCreatedContributedOrPublishedByQuery.data?.works?.totalCount ?? 0, loading: organizationCreatedContributedOrPublishedByQuery.loading },
+ { id: "fundedBy", title: "Funded By", tooltipText: "Works funded by the organization and its child organizations.", count: organizationFundedByQuery.data?.works?.totalCount ?? 0, loading: organizationFundedByQuery.loading },
+ // OMP relationships are included in allRelated, but we don't document or explain this functionality ATM.
+ // { id: "connectedToOrganizationOMPs", title: "Related to OMPs", tooltipText: "Works related to Output Management Plans associated with the organization.", count: 0 },
+ ]
const isConnectionTypeSet = searchParams?.has('connection-type')
- const totalConnectionTypeCount = connectionTypesCounts ? connectionTypesCounts.references + connectionTypesCounts.citations + connectionTypesCounts.parts + connectionTypesCounts.partOf + connectionTypesCounts.otherRelated : 0
+ const isOrganizationRelationTypeSet = searchParams?.has('organization-relation-type')
const defaultActiveKeys = [
"authors-facets",
@@ -68,8 +105,9 @@ export default function WorkFacets({
"language-facets",
"field-of-science-facets",
"registration-agency-facets",
- "conection-type-facets",
- "repository-type-facets"
+ "repository-type-facets",
+ "organization-relation-type-facets",
+ "repository-facets"
]
return (
@@ -79,9 +117,9 @@ export default function WorkFacets({
)}
- {totalConnectionTypeCount > 0 && (
+ {url.startsWith('/doi.org/') && (
f.count > 0)}
+ data={connectionTypeList}
title="Connection Types"
id="connection-type-facets"
param="connection-type"
@@ -91,6 +129,18 @@ export default function WorkFacets({
/>
)}
+ {url.startsWith('/ror.org') && (
+ !isOrganizationRelationTypeSet && i == 0}
+ radio
+ />
+ )}
+
{model == "person"
?
:
@@ -144,14 +194,28 @@ export default function WorkFacets({
param="registration-agency"
url={url}
/>
-
+
+ {!url.startsWith('/repositories') && (
+ <>
+
+
+
+ >
+ )}
>
)
diff --git a/src/components/WorksDashboard/WorksDashboard.module.scss b/src/components/WorksDashboard/WorksDashboard.module.scss
index 13e05bdb..e69de29b 100644
--- a/src/components/WorksDashboard/WorksDashboard.module.scss
+++ b/src/components/WorksDashboard/WorksDashboard.module.scss
@@ -1,3 +0,0 @@
-.graphsContainer {
- margin-top: 3rem;
-}
diff --git a/src/components/WorksDashboard/WorksDashboard.tsx b/src/components/WorksDashboard/WorksDashboard.tsx
index 39596524..61346e39 100644
--- a/src/components/WorksDashboard/WorksDashboard.tsx
+++ b/src/components/WorksDashboard/WorksDashboard.tsx
@@ -8,7 +8,6 @@ import { Works } from 'src/data/types'
import ProductionChart from '../ProductionChart/ProductionChart'
import HorizontalStackedBarChart from '../HorizontalStackedBarChart/HorizontalStackedBarChart'
import { resourceTypeDomain, resourceTypeRange, licenseRange, otherDomain, otherRange } from '../../data/color_palettes'
-import styles from './WorksDashboard.module.scss'
import { getTopFive, toBarRecord } from 'src/utils/helpers'
import VerticalBarChart from '../VerticalBarChart/VerticalBarChart'
@@ -42,7 +41,7 @@ function WorksDashboard({ works, show = {}, children }: Props) {
const licenses = getTopFive(licensesData.map(toBarRecord))
return (
-
+
{children &&
{children}
diff --git a/src/components/WorksListing/WorksListing.tsx b/src/components/WorksListing/WorksListing.tsx
index 4d81c89c..849cc256 100644
--- a/src/components/WorksListing/WorksListing.tsx
+++ b/src/components/WorksListing/WorksListing.tsx
@@ -14,9 +14,13 @@ import NoResults from 'src/components/NoResults/NoResults'
import Pager from 'src/components/Pager/Pager'
import WorksDashboard, { ShowCharts } from 'src/components/WorksDashboard/WorksDashboard'
import SankeyGraph, { multilevelToSankey } from 'src/components/SankeyGraph/SankeyGraph'
+import SummarySearchMetrics from 'src/components/SummarySearchMetrics/SummarySearchMetrics'
+import { QueryVar } from 'src/data/queries/searchDoiQuery'
interface Props {
works: Works
+ vars?: QueryVar
+ showMetrics?: boolean
showAnalytics: boolean
showSankey?: boolean
sankeyTitle?: string
@@ -34,8 +38,9 @@ interface Props {
export default function WorksListing({
works,
+ vars,
+ showMetrics,
showAnalytics,
- connectionTypesCounts,
showSankey,
sankeyTitle = 'Contributions to Related Works',
showClaimStatus,
@@ -58,7 +63,7 @@ export default function WorksListing({
model={model}
url={url}
data={works}
- connectionTypesCounts={connectionTypesCounts}
+ vars={vars}
/>
)
}
@@ -73,6 +78,9 @@ export default function WorksListing({
if (hasNoWorks) return renderNoWorks()
return (
<>
+ {showMetrics &&
+
+
}
{showAnalytics && }
{showSankey &&
diff --git a/src/data/constants.ts b/src/data/constants.ts
index 05a9a71c..f2c36aa1 100644
--- a/src/data/constants.ts
+++ b/src/data/constants.ts
@@ -74,6 +74,7 @@ export const FACETS = {
'registrationAgencies',
'authors',
'creatorsAndContributors',
+ 'clients',
'clientTypes',
// personToWorkTypesMultilevel: []
],
diff --git a/src/data/queries/doiRelatedContentQuery.ts b/src/data/queries/doiRelatedContentQuery.ts
index d25effaa..63e40c0c 100644
--- a/src/data/queries/doiRelatedContentQuery.ts
+++ b/src/data/queries/doiRelatedContentQuery.ts
@@ -1,7 +1,10 @@
-import { gql, useQuery } from "@apollo/client";
+import { gql } from "@apollo/client";
import { workConnection, workFragment } from "src/data/queries/queryFragments";
-import { QueryData } from "src/data/queries/doiQuery";
import { QueryVar } from "src/data/queries/searchDoiQuery";
+import { useSearchDoiFacetsQuery } from "src/data/queries/searchDoiFacetsQuery";
+import { useSearchDoiQuery } from "src/data/queries/searchDoiQuery";
+import { FACETS } from "src/data/constants";
+import { Works } from "src/data/types";
export function buildFilterQuery(variables: QueryVar) {
const queryParts = [
@@ -14,17 +17,28 @@ export function buildFilterQuery(variables: QueryVar) {
}
export function useDoiRelatedContentQuery(variables: QueryVar) {
- const filterQuery = buildFilterQuery(variables)
+ const results = useSearchDoiQuery(variables)
+ const facets = useSearchDoiFacetsQuery(variables, [...FACETS.DEFAULT, ...FACETS.METRICS])
- const { loading, data, error } = useQuery(
- RELATED_CONTENT_QUERY,
- {
- variables: { ...variables, filterQuery },
- errorPolicy: 'all'
- }
- )
+ const loading = results.loading || facets.loading;
+ const error = results.error || facets.error;
+
+ if (loading || error) return { loading, data: undefined, error }
+
+
+ const works = {
+ ...results.data?.works || {},
+ ...facets.data?.works
+ }
- return { loading, data, error }
+ return {
+ ...results,
+ data: { works } as QueryData,
+ }
+}
+
+export interface QueryData {
+ works: Works
}
@@ -155,5 +169,4 @@ export const RELATED_CONTENT_QUERY = gql`
${workFragment}
`;
-
-export type { QueryVar, QueryData }
+export type { QueryVar }
diff --git a/src/data/queries/searchDoiFacetsQuery.ts b/src/data/queries/searchDoiFacetsQuery.ts
index 707c009d..976d428c 100644
--- a/src/data/queries/searchDoiFacetsQuery.ts
+++ b/src/data/queries/searchDoiFacetsQuery.ts
@@ -35,6 +35,7 @@ function convertToQueryData(json: any): QueryData {
fieldsOfScience: meta.fieldsOfScience?.slice(0, 10),
affiliations: meta.affiliations,
repositories: [],
+ clients: meta.clients?.slice(0, 10),
registrationAgencies: meta.registrationAgencies,
funders: meta.funders,
authors: meta.authors?.slice(0, 10),
diff --git a/src/data/queries/searchDoiQuery.ts b/src/data/queries/searchDoiQuery.ts
index 326911d0..78cfef4f 100644
--- a/src/data/queries/searchDoiQuery.ts
+++ b/src/data/queries/searchDoiQuery.ts
@@ -10,18 +10,71 @@ function extractRORId(rorString: string): string {
return rorString.replace('https://', '').replace('ror.org/', '')
}
-function buildOrgQuery(rorId: string | undefined, rorFundingIds: string[]): string {
+function buildOrgQuery(rorId: string | undefined, organizationRelationType: string | undefined): string {
if (!rorId) return ''
const id = 'ror.org/' + extractRORId(rorId)
const urlId = `"https://${id}"`
- const rorFundingIdsQuery = rorFundingIds.map(id => '"https://doi.org/' + id + '"').join(' OR ')
- return `((organization_id:${id} OR affiliation_id:${id} OR related_dmp_organization_id:${id} OR provider.ror_id:${urlId}) OR funding_references.funderIdentifier:(${urlId} ${rorFundingIdsQuery && `OR ${rorFundingIdsQuery}`}))`
+
+ const fundedByQuery = `funder_rors:${urlId}`
+ const fundedByChildOrganizations = `funder_parent_rors:${urlId}`
+ const createdContributedOrPublishedBy = `(organization_id:${id} OR provider.ror_id:${urlId})`
+ const createdOrContributedByAffiliatedResearcher = `affiliation_id:${id}`
+ const connectedToOrganizationOMPs = `related_dmp_organization_id:${id}`
+
+ switch (organizationRelationType) {
+ case 'fundedBy':
+ return `(${fundedByQuery} OR ${fundedByChildOrganizations})`
+ case 'createdContributedOrPublishedBy':
+ return `(${createdContributedOrPublishedBy})`
+ case 'createdOrContributedByAffiliatedResearcher':
+ return `(${createdOrContributedByAffiliatedResearcher})`
+ case 'connectedToOrganizationOMPs':
+ return `(${connectedToOrganizationOMPs})`
+ default:
+ return `(${createdContributedOrPublishedBy} OR ${createdOrContributedByAffiliatedResearcher} OR ${connectedToOrganizationOMPs} OR ${fundedByQuery} OR ${fundedByChildOrganizations})`
+ }
+
}
+function buildRelatedToDoiQuery(relatedToDoi: string | undefined, relatedDois: string[] | undefined, connectionType: string | undefined): string {
+ if (!relatedToDoi) return ''
+ const citationsQuery = '(reference_ids:"' + relatedToDoi + '")'
+ const referencesQuery = '(citation_ids:"' + relatedToDoi + '")'
+
+ const partOfQuery = '(part_ids:"' + relatedToDoi + '")'
+ const isPartOfQuery = '(part_of_ids:"' + relatedToDoi + '")'
+ const versionOfQuery = '(version_ids:"' + relatedToDoi + '")'
+ const versionsQuery = '(version_of_ids:"' + relatedToDoi + '")'
+
+ const outwardRelatedDois = relatedDois && relatedDois.length > 0 && '(doi:(' + relatedDois?.map(doi => '"' + doi + '"').join(' OR ') + '))'
+ const inwardRelatedDois = relatedToDoi && '(relatedIdentifiers.relatedIdentifier:("https://doi.org/' + relatedToDoi?.toLowerCase() + '" OR "' + relatedToDoi?.toLowerCase() + '") AND agency:"datacite")'
+
+ switch (connectionType) {
+ case 'citations':
+ return citationsQuery
+ case 'references':
+ return referencesQuery
+ case 'parts':
+ return partOfQuery
+ case 'partOf':
+ return isPartOfQuery
+ case 'versionOf':
+ return versionOfQuery
+ case 'versions':
+ return versionsQuery
+ case 'otherRelated':
+ return ('(' + [outwardRelatedDois, inwardRelatedDois].filter(Boolean).join(' OR ') + ') AND NOT (' + [citationsQuery, referencesQuery, partOfQuery, isPartOfQuery, versionOfQuery, versionsQuery].join(' OR ') + ')')
+ default:
+ return ('(' + [citationsQuery, referencesQuery, partOfQuery, isPartOfQuery, versionOfQuery, versionsQuery, outwardRelatedDois, inwardRelatedDois].filter(Boolean).join(' OR ') + ')')
+ }
+}
+
+
export function buildQuery(variables: QueryVar): string {
const queryParts = [
variables.query,
- buildOrgQuery(variables.rorId, variables.rorFundingIds || []),
+ buildOrgQuery(variables.rorId || undefined, variables.organizationRelationType || undefined),
+ buildRelatedToDoiQuery(variables.relatedToDoi || undefined, variables.relatedDois || undefined, variables.connectionType || undefined),
variables.language ? `language:${variables.language}` : '',
variables.registrationAgency ? `agency:${variables.registrationAgency}` : '',
variables.userId ? `creators_and_contributors.nameIdentifiers.nameIdentifier:(${variables.userId} OR "https://orcid.org/${variables.userId}")` : '',
@@ -73,7 +126,8 @@ function buildDoiSearchParams(variables: QueryVar, count?: number): URLSearchPar
include_other_registration_agencies: 'true'
})
- if (count) searchParams.append('page[size]', count.toString())
+ const pageSize = variables.pageSize ?? count
+ if (pageSize !== undefined) searchParams.append('page[size]', pageSize.toString())
searchParams.append('page[number]', variables.cursor || '1')
// Default to 'relevance' if sort is missing or invalid
@@ -208,8 +262,8 @@ export interface QueryData {
export interface QueryVar {
query?: string
filterQuery?: string
- rorId?: string
- rorFundingIds?: string[]
+ rorId?: string,
+ organizationRelationType?: string,
userId?: string
clientId?: string
cursor?: string
@@ -221,7 +275,11 @@ export interface QueryVar {
license?: string
registrationAgency?: string
clientType?: string
+ relatedToDoi?: string
+ relatedDois?: string[]
+ connectionType?: string
sort?: SortOption
+ pageSize?: number
}
diff --git a/src/data/types.ts b/src/data/types.ts
index f892ceea..72c026c5 100644
--- a/src/data/types.ts
+++ b/src/data/types.ts
@@ -41,6 +41,8 @@ export type Work = WorkMetadata & {
name: string
}
+ relatedIdentifiers?: RelatedIdentifier[]
+
registered?: Date
formattedCitation?: string
claims?: Claim[]
@@ -74,6 +76,7 @@ export type Works = {
funders: Facet[]
authors?: Facet[]
creatorsAndContributors?: Facet[]
+ clients?: Facet[]
clientTypes?: Facet[]
citations?: Facet[]
views?: Facet[]
@@ -322,10 +325,22 @@ type Identifier = {
identifierUrl: string
}
+type RelatedIdentifier = {
+ relatedIdentifier: string
+ relatedIdentifierType: string
+ relationType: string
+ resourceTypeGeneral?: string
+ relatedMetadataScheme?: string
+ schemeUri?: string
+ schemeType?: string
+}
+
export type Facet = {
id: string
title: string
count: number
+ tooltipText?: string
+ loading?: boolean
}
export type MultilevelFacet = Facet & {
diff --git a/yarn.lock b/yarn.lock
index 1a90c20a..7a53857a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1466,10 +1466,10 @@
"@emnapi/runtime" "^1.4.3"
"@tybys/wasm-util" "^0.10.0"
-"@next/env@15.4.8":
- version "15.4.8"
- resolved "https://registry.yarnpkg.com/@next/env/-/env-15.4.8.tgz#f41741d07651958bccb31fb685da0303a9ef1373"
- integrity sha512-LydLa2MDI1NMrOFSkO54mTc8iIHSttj6R6dthITky9ylXV2gCGi0bHQjVCtLGRshdRPjyh2kXbxJukDtBWQZtQ==
+"@next/env@15.4.10":
+ version "15.4.10"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-15.4.10.tgz#a794b738d043d9e98ea435bd45254899f7f77714"
+ integrity sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==
"@next/eslint-plugin-next@15.4.6":
version "15.4.6"
@@ -7393,12 +7393,12 @@ next-plausible@^3.12.5:
resolved "https://registry.yarnpkg.com/next-plausible/-/next-plausible-3.12.5.tgz#e8776b59c014b52d4238d4cb72ca88c8c3115cf4"
integrity sha512-l1YMuTI9akb2u7z4hyTuxXpudy8KfSteRNXCYpWpnhAoBjaWQlv6sITai1TwcR7wWvVW8DFbLubvMQAsirAjcA==
-next@15.4.8:
- version "15.4.8"
- resolved "https://registry.yarnpkg.com/next/-/next-15.4.8.tgz#0f20a6cad613dc34547fa6519b2d09005ac370ca"
- integrity sha512-jwOXTz/bo0Pvlf20FSb6VXVeWRssA2vbvq9SdrOPEg9x8E1B27C2rQtvriAn600o9hH61kjrVRexEffv3JybuA==
+next@15.4.10:
+ version "15.4.10"
+ resolved "https://registry.yarnpkg.com/next/-/next-15.4.10.tgz#4ee237d4eb16289f6e16167fbed59d8ada86aa59"
+ integrity sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ==
dependencies:
- "@next/env" "15.4.8"
+ "@next/env" "15.4.10"
"@swc/helpers" "0.5.15"
caniuse-lite "^1.0.30001579"
postcss "8.4.31"