diff --git a/api/package-lock.json b/api/package-lock.json index f542609d6..0640eec9d 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -14,7 +14,7 @@ "@middy/http-json-body-parser": "^4.7.0", "@opensearch-project/opensearch": "^2.5.0", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^4.16.2", + "@prisma/client": "^5.11.0", "@sparticuz/chromium": "^114.0.0", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -7537,15 +7537,12 @@ } }, "node_modules/@prisma/client": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz", - "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.11.0.tgz", + "integrity": "sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw==", "hasInstallScript": true, - "dependencies": { - "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" - }, "engines": { - "node": ">=14.17" + "node": ">=16.13" }, "peerDependencies": { "prisma": "*" @@ -7564,11 +7561,6 @@ "optional": true, "peer": true }, - "node_modules/@prisma/engines-version": { - "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz", - "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==" - }, "node_modules/@puppeteer/browsers": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", diff --git a/api/package.json b/api/package.json index d69e731d5..5832aba2d 100644 --- a/api/package.json +++ b/api/package.json @@ -34,7 +34,7 @@ "@middy/http-json-body-parser": "^4.7.0", "@opensearch-project/opensearch": "^2.5.0", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^4.16.2", + "@prisma/client": "^5.11.0", "@sparticuz/chromium": "^114.0.0", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", diff --git a/api/src/components/affiliations/controller.ts b/api/src/components/affiliations/controller.ts index 3e1962bfd..8e4e025b7 100644 --- a/api/src/components/affiliations/controller.ts +++ b/api/src/components/affiliations/controller.ts @@ -71,7 +71,7 @@ export const updateAffiliations = async ( // update affiliations for this author await coAuthorService.update(coAuthor.id, { - affiliations: isIndependent ? [] : (affiliations as unknown[] as Prisma.JsonArray), // we can safely remove author affiliations if they declared they are independent + affiliations: isIndependent ? [] : (affiliations as unknown[] as Prisma.InputJsonValue[]), // we can safely remove author affiliations if they declared they are independent isIndependent }); diff --git a/api/src/components/publication/service.ts b/api/src/components/publication/service.ts index 80d435628..f2ca2f126 100644 --- a/api/src/components/publication/service.ts +++ b/api/src/components/publication/service.ts @@ -13,8 +13,8 @@ export const isIdInUse = async (id: string) => { return Boolean(publication); }; -export const get = (id: string) => - client.prisma.publication.findUnique({ +export const get = async (id: string) => { + const publication = await client.prisma.publication.findUnique({ where: { id }, @@ -181,6 +181,17 @@ export const get = (id: string) => } }); + // Provide counts + return publication + ? { + ...publication, + flagCount: publication.publicationFlags.filter((flag) => !flag.resolved).length, + peerReviewCount: publication.linkedFrom.filter((child) => child.publicationFrom.type === 'PEER_REVIEW') + .length + } + : publication; +}; + export const getSeedDataPublications = (title: string) => client.prisma.publication.findMany({ where: { @@ -619,7 +630,7 @@ export const getLinksForPublication = async ( const publicationIds = linkedTo.map((link) => link.id).concat(linkedFrom.map((link) => link.id)); - // get coAuthors for each latest LIVE version of each publication + // Get extra details for linked publications const versions = await client.prisma.publicationVersion.findMany({ where: { isLatestLiveVersion: true, @@ -644,25 +655,50 @@ export const getLinksForPublication = async ( orderBy: { position: 'asc' } + }, + publication: { + select: { + publicationFlags: { + where: { + resolved: false + } + }, + linkedFrom: { + where: { + publicationFrom: { + type: 'PEER_REVIEW', + versions: { + some: { + isLatestLiveVersion: true + } + } + } + } + } + } } } }); - // add authors to 'linkedTo' publications + // Add authors and counts to 'linkedTo' publications linkedTo.forEach((link) => { - const authors = versions.find((version) => version.versionOf === link.id)?.coAuthors || []; + const latestVersion = versions.find((version) => version.versionOf === link.id); Object.assign(link, { - authors + authors: latestVersion?.coAuthors || [], + flagCount: latestVersion?.publication.publicationFlags.length || 0, + peerReviewCount: latestVersion?.publication.linkedFrom.length || 0 }); }); - // add authors to 'linkedFrom' publications + // Add authors and counts to 'linkedFrom' publications linkedFrom.forEach((link) => { - const authors = versions.find((version) => version.versionOf === link.id)?.coAuthors || []; + const latestVersion = versions.find((version) => version.versionOf === link.id); Object.assign(link, { - authors + authors: latestVersion?.coAuthors || [], + flagCount: latestVersion?.publication.publicationFlags.length || 0, + peerReviewCount: latestVersion?.publication.linkedFrom.length || 0 }); }); @@ -757,7 +793,9 @@ export const getLinksForPublication = async ( firstName: author.user?.firstName || '', lastName: author.user?.lastName || '' } - })) + })), + flagCount: publication.flagCount, + peerReviewCount: publication.peerReviewCount }, linkedTo: orderedParents, linkedFrom: orderedChildren @@ -823,6 +861,23 @@ export const getDirectLinksForPublication = async ( include: { user: true } + }, + publicationFlags: { + where: { + resolved: false + } + }, + linkedFrom: { + where: { + publicationFrom: { + type: 'PEER_REVIEW', + versions: { + some: { + isLatestLiveVersion: true + } + } + } + } } } } @@ -861,10 +916,32 @@ export const getDirectLinksForPublication = async ( include: { user: true } + }, + publicationFlags: { + where: { + resolved: false + } + }, + linkedFrom: { + where: { + publicationFrom: { + type: 'PEER_REVIEW', + versions: { + some: { + isLatestLiveVersion: true + } + } + } + } } } } } + }, + publicationFlags: { + where: { + resolved: false + } } } }); @@ -909,7 +986,9 @@ export const getDirectLinksForPublication = async ( authorLastName: user.lastName || '', currentStatus, publishedDate: publishedDate?.toISOString() || '', - authors: [] + authors: [], + flagCount: link.publicationTo.publicationFlags.length, + peerReviewCount: link.publicationTo.linkedFrom.length }; }); @@ -935,7 +1014,9 @@ export const getDirectLinksForPublication = async ( authorLastName: user.lastName || '', currentStatus, publishedDate: publishedDate?.toISOString() || '', - authors: [] + authors: [], + flagCount: link.publicationFrom.publicationFlags.length, + peerReviewCount: link.publicationFrom.linkedFrom.length }; }); @@ -1007,7 +1088,10 @@ export const getDirectLinksForPublication = async ( firstName: author.user?.firstName || '', lastName: author.user?.lastName || '' } - })) + })), + flagCount: publication.publicationFlags.length, + peerReviewCount: publication.linkedFrom.filter((child) => child.publicationFrom.type === 'PEER_REVIEW') + .length }, linkedTo, linkedFrom diff --git a/api/src/components/publicationVersion/service.ts b/api/src/components/publicationVersion/service.ts index 5766a4d6c..ec32350be 100644 --- a/api/src/components/publicationVersion/service.ts +++ b/api/src/components/publicationVersion/service.ts @@ -132,7 +132,24 @@ export const getAllByPublicationIds = async (ids: string[]) => { id: true, type: true, doi: true, - url_slug: true + url_slug: true, + linkedFrom: { + where: { + publicationFrom: { + type: 'PEER_REVIEW', + versions: { + some: { + isLatestLiveVersion: true + } + } + } + } + }, + publicationFlags: { + where: { + resolved: false + } + } } }, user: { @@ -166,7 +183,22 @@ export const getAllByPublicationIds = async (ids: string[]) => { throw Error('Unable to find all latest versions for all requested publications.'); } - return latestVersions; + // Provide counts + const mappedResults = latestVersions.map((version) => { + // Remove linkedFrom and flags from return + const { linkedFrom, publicationFlags, ...rest } = version.publication; + + return { + ...version, + publication: { + ...rest, + flagCount: version.publication.publicationFlags.length, + peerReviewCount: version.publication.linkedFrom.length + } + }; + }); + + return mappedResults; }; export const update = (id: string, data: Prisma.PublicationVersionUpdateInput) => @@ -425,7 +457,7 @@ export const create = async (previousVersion: I.PublicationVersion, user: I.User linkedUser: user.id, confirmedCoAuthor: true, approvalRequested: false, - affiliations: coAuthor.affiliations, + affiliations: coAuthor.affiliations as unknown[] as Prisma.InputJsonValue[], isIndependent: coAuthor.isIndependent, position: index } diff --git a/api/src/components/user/service.ts b/api/src/components/user/service.ts index f799981be..5d62e74ec 100644 --- a/api/src/components/user/service.ts +++ b/api/src/components/user/service.ts @@ -259,15 +259,44 @@ export const getPublications = async ( } } } + }, + linkedFrom: { + where: { + publicationFrom: { + type: 'PEER_REVIEW', + versions: { + some: { + isLatestLiveVersion: true + } + } + } + } + }, + publicationFlags: { + where: { + resolved: false + } } } }); const totalUserPublications = await client.prisma.publication.count({ where }); + // Provide counts + const mappedPublications = userPublications.map((publication) => { + // Remove linkedFrom and flags from return + const { linkedFrom, publicationFlags, ...rest } = publication; + + return { + ...rest, + flagCount: publication.publicationFlags.length, + peerReviewCount: publication.linkedFrom.length + }; + }); + // Because the sorting is conditional on the publication state of a publication's versions, we can't do it in prisma. const sortedPublications = isAccountOwner // If account owner, put publications with an active draft first (sub-sorted by updated time descending), then others (sub-sorted by published date descending) - ? userPublications.sort((a, b) => { + ? mappedPublications.sort((a, b) => { const aLatest = a.versions.find((version) => version.isLatestVersion); const bLatest = b.versions.find((version) => version.isLatestVersion); @@ -291,7 +320,7 @@ export const getPublications = async ( return bLatest.updatedAt.getTime() - aLatest.updatedAt.getTime(); } }) // If not account owner, we only have latest live publications - sort by published date descending - : userPublications.sort((a, b) => { + : mappedPublications.sort((a, b) => { const aLatestLive = a.versions.find((version) => version.isLatestLiveVersion); const bLatestLive = b.versions.find((version) => version.isLatestLiveVersion); diff --git a/api/src/lib/interface.ts b/api/src/lib/interface.ts index 638d50a97..00e456bf0 100644 --- a/api/src/lib/interface.ts +++ b/api/src/lib/interface.ts @@ -77,7 +77,7 @@ export interface JSONResponse { * @description Publications */ -const prismaGeneratedPublicationType = Prisma.validator()({}); +const prismaGeneratedPublicationType = Prisma.validator()({}); export type Publication = Prisma.PublicationGetPayload; export interface CreatePublicationRequestBody { @@ -188,6 +188,10 @@ export type PublicationVersion = Exclude()({}); +export type Link = Prisma.LinksGetPayload; + export interface CreateLinkBody { to: string; from: string; @@ -204,6 +208,8 @@ export interface LinkedPublication { authorFirstName: string; authorLastName: string; authors: Pick[]; + flagCount: number; + peerReviewCount: number; // Only returned with ?direct=true on the getPublicationLinks endpoint. // Just used at present to show, and link to, the version a Peer Review was created against. parentVersionId?: string; @@ -279,6 +285,9 @@ export interface ConfirmVerificationCodeBody { * @description Flags */ +const prismaGeneratedFlagType = Prisma.validator()({}); +export type Flag = Prisma.PublicationFlagsGetPayload; + export type FlagCategory = | 'PLAGIARISM' | 'ETHICAL_ISSUES' diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 74a0c6f7f..96d681ccf 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -29,7 +29,7 @@ const config: PlaywrightTestConfig = { /* Give failing tests one retry attempt */ retries: 1, /* Running tests in parallel speeds up overall testing time but it also increases the chance of failing tests eg: one or more tests are manipulating the same DB resource */ - workers: 3, + workers: !!process.env.CI ? 3 : 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ diff --git a/ui/src/__tests__/components/Publication/EngagementCounts.test.tsx b/ui/src/__tests__/components/Publication/EngagementCounts.test.tsx new file mode 100644 index 000000000..0d717bea6 --- /dev/null +++ b/ui/src/__tests__/components/Publication/EngagementCounts.test.tsx @@ -0,0 +1,55 @@ +import { render, screen } from '@testing-library/react'; + +import * as Components from '@/components'; + +describe('Flag and peer review', () => { + beforeEach(() => { + render(); + }); + it('Red flag count is shown', () => { + expect(screen.getByTitle('Red flag count')).toBeInTheDocument(); + expect(screen.getByText('1')).toBeInTheDocument(); + }); + it('Peer review count is shown', () => { + expect(screen.getByTitle('Peer review count')).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); + }); +}); + +describe('Flag only', () => { + beforeEach(() => { + render(); + }); + it('Red flag count is shown', () => { + expect(screen.getByTitle('Red flag count')).toBeInTheDocument(); + expect(screen.getByText('1')).toBeInTheDocument(); + }); + it('Peer review count is not shown', () => { + expect(screen.queryByTitle('Peer review count')).not.toBeInTheDocument(); + }); +}); + +describe('Peer review only', () => { + beforeEach(() => { + render(); + }); + it('Red flag count is not shown', () => { + expect(screen.queryByTitle('Red flag count')).not.toBeInTheDocument(); + }); + it('Peer review count is not shown', () => { + expect(screen.getByTitle('Peer review count')).toBeInTheDocument(); + expect(screen.getByText('1')).toBeInTheDocument(); + }); +}); + +describe('No engagements', () => { + beforeEach(() => { + render(); + }); + it('Red flag count is not shown', () => { + expect(screen.queryByTitle('Red flag count')).not.toBeInTheDocument(); + }); + it('Peer review count is not shown', () => { + expect(screen.queryByTitle('Peer review count')).not.toBeInTheDocument(); + }); +}); diff --git a/ui/src/__tests__/testUtils/index.tsx b/ui/src/__tests__/testUtils/index.tsx index 45357e2c5..5372e6e9c 100644 --- a/ui/src/__tests__/testUtils/index.tsx +++ b/ui/src/__tests__/testUtils/index.tsx @@ -107,7 +107,9 @@ export const testLinkedPublication: Interfaces.LinkedPublication = { createdBy: testUser.id, authorFirstName: testUser.firstName, authorLastName: testUser.lastName, - authors: [] + authors: [], + flagCount: 0, + peerReviewCount: 0 }; export const testLinkedFromPublication: Interfaces.LinkedFromPublication = { diff --git a/ui/src/components/Publication/Card/index.tsx b/ui/src/components/Publication/Card/index.tsx index ed016b50b..b02c8726f 100644 --- a/ui/src/components/Publication/Card/index.tsx +++ b/ui/src/components/Publication/Card/index.tsx @@ -45,6 +45,10 @@ const Card: React.FC = (props): React.ReactElement => { [authors] ); + const { flagCount, peerReviewCount } = props.publicationVersion.publication; + const hasFlagAndPeerReview = flagCount && peerReviewCount; + const hasOneOfFlagOrPeerReview = flagCount || peerReviewCount; + return (
= (props): React.ReactElement => {
{authors.map((author, index) => ( - - - <> - {author.user?.firstName[0]}. {author.user?.lastName} - - - {author.linkedUser !== 'octopus' && ( - <> -   - - - - - )} - {index < authors.length - 1 ? ', ' : ''} - +
+ + + <> + {author.user?.firstName[0]}. {author.user?.lastName} + + + {author.linkedUser !== 'octopus' && ( + <> +   + + + + + )} + {index < authors.length - 1 ? ', ' : ''} + + +
))}
diff --git a/ui/src/components/Publication/Creation/LinkedItems/LinkedPublicationsCombobox.tsx b/ui/src/components/Publication/Creation/LinkedItems/LinkedPublicationsCombobox.tsx index fdbed311f..817c5651f 100644 --- a/ui/src/components/Publication/Creation/LinkedItems/LinkedPublicationsCombobox.tsx +++ b/ui/src/components/Publication/Creation/LinkedItems/LinkedPublicationsCombobox.tsx @@ -119,42 +119,69 @@ const LinkedPublicationsCombobox: React.FC = (p > {!isValidating && - results.data.map((publicationVersion: Interfaces.PublicationVersion, index: number) => ( - - `relative cursor-default select-none p-2 text-teal-900 ${ - active && 'ring-2 ring-inset ring-yellow-400' - } ${index === 0 && 'rounded-t'} ${index === results.length - 1 && 'rounded-b'}` - } - value={publicationVersion} - title={ - publicationVersion.content - ? Helpers.truncateString(Helpers.htmlToText(publicationVersion.content), 220) - : '' - } - > -
- - {Helpers.formatPublicationType(publicationVersion.publication.type)} - -

{publicationVersion.title}

-
- - {publicationVersion.publishedDate && ( - - )} - , - - - {publicationVersion.user.firstName[0]}. {publicationVersion.user.lastName} + results.data.map((publicationVersion: Interfaces.PublicationVersion, index: number) => { + const { flagCount, peerReviewCount } = publicationVersion.publication; + const hasFlagAndPeerReview = flagCount && peerReviewCount; + const hasOneOfFlagOrPeerReview = flagCount || peerReviewCount; + return ( + + `relative cursor-default select-none p-2 text-teal-900 ${ + active && 'ring-2 ring-inset ring-yellow-400' + } ${index === 0 && 'rounded-t'} ${index === results.length - 1 && 'rounded-b'}` + } + value={publicationVersion} + title={ + publicationVersion.content + ? Helpers.truncateString( + Helpers.htmlToText(publicationVersion.content), + 220 + ) + : '' + } + > +
+ + {Helpers.formatPublicationType(publicationVersion.publication.type)} +

{publicationVersion.title}

+
+
+ + {publicationVersion.publishedDate && ( + + )} + , + + + {publicationVersion.user.firstName[0]}.{' '} + {publicationVersion.user.lastName} + +
+ +
-
- - ))} + + ); + })} diff --git a/ui/src/components/Publication/EngagementCounts/index.tsx b/ui/src/components/Publication/EngagementCounts/index.tsx new file mode 100644 index 000000000..edffb317b --- /dev/null +++ b/ui/src/components/Publication/EngagementCounts/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import * as SolidIcons from '@heroicons/react/24/solid'; + +type Props = { + flagCount?: number; + peerReviewCount?: number; + className?: string; + childClasses?: string; + narrow?: boolean; // Halves horizontal spacing +}; + +const EngagementCounts: React.FC = (props): React.ReactElement => { + const { flagCount, peerReviewCount } = props; + const classes = 'flex ' + (props.className || ''); + return !!props.flagCount || !!props.peerReviewCount ? ( + + {!!flagCount && ( + + + {flagCount} + + )} + {!!peerReviewCount && ( + + + {peerReviewCount} + + )} + + ) : ( + <> + ); +}; + +export default EngagementCounts; diff --git a/ui/src/components/Publication/SearchResult/index.tsx b/ui/src/components/Publication/SearchResult/index.tsx index 681939133..b011ee506 100644 --- a/ui/src/components/Publication/SearchResult/index.tsx +++ b/ui/src/components/Publication/SearchResult/index.tsx @@ -30,6 +30,8 @@ const SearchResult: React.FC = (props): React.ReactElement => { return authors.join(', '); }, [props.publicationVersion.coAuthors, props.publicationVersion.user]); + const { flagCount, peerReviewCount } = props.publicationVersion.publication; + return ( = (props): React.ReactElement => { `} >
- - {Helpers.formatPublicationType(props.publicationVersion.publication.type)} - -

- {props.publicationVersion.title} -

+
+ + {Helpers.formatPublicationType(props.publicationVersion.publication.type)} + +

+ {props.publicationVersion.title} +

-
- {props.publicationVersion.description ? ( -

{props.publicationVersion.description}

- ) : props.publicationVersion.content ? ( - parse(Helpers.truncateString(props.publicationVersion.content, 370)) - ) : null} +
+ {props.publicationVersion.description ? ( +

{props.publicationVersion.description}

+ ) : props.publicationVersion.content ? ( + parse(Helpers.truncateString(props.publicationVersion.content, 370)) + ) : null} +
- - {props.publicationVersion.publishedDate - ? `Published ${Helpers.relativeDate(props.publicationVersion.publishedDate)}` - : 'Draft'} - , by {authors} - +
+ + {props.publicationVersion.publishedDate + ? `Published ${Helpers.relativeDate(props.publicationVersion.publishedDate)}` + : 'Draft'} + , by {authors} + + +
diff --git a/ui/src/components/Publication/SimpleResult/index.tsx b/ui/src/components/Publication/SimpleResult/index.tsx index 9d0612587..aeac91707 100644 --- a/ui/src/components/Publication/SimpleResult/index.tsx +++ b/ui/src/components/Publication/SimpleResult/index.tsx @@ -37,6 +37,7 @@ const SimpleResult: React.FC = (props): React.ReactElement => { const publishedVersionCount = props.publication.versions.filter( (version) => version.currentStatus === 'LIVE' ).length; + const { flagCount, peerReviewCount } = props.publication; const requestControl = hasAlreadyRequestedControl ? ( = (props): React.ReactElement => {

You are not listed as an author on the latest published version

)}
- } - title="View" - className="mt-5 bg-teal-500 px-3 text-white-50 children:border-none children:text-white-50" - /> +
+ } + title="View" + className="bg-teal-500 px-3 text-white-50 children:border-none children:text-white-50" + /> + +
) : (

Never published

diff --git a/ui/src/components/Publication/Visualization/index.tsx b/ui/src/components/Publication/Visualization/index.tsx index 2a7180df1..6ed45d34d 100644 --- a/ui/src/components/Publication/Visualization/index.tsx +++ b/ui/src/components/Publication/Visualization/index.tsx @@ -19,6 +19,8 @@ interface BoxEntry { publishedDate: string; authors: Interfaces.LinkedPublication['authors']; pointers: string[]; + flagCount: number; + peerReviewCount: number; } type BoxProps = BoxEntry & { @@ -54,6 +56,10 @@ const Box: React.FC = (props): React.ReactElement => { return authors[0]; }, [props.authorFirstName, props.authorLastName, props.authors, props.createdBy]); + const { flagCount, peerReviewCount } = props; + const hasFlagAndPeerReview = flagCount && peerReviewCount; + const hasOneOfFlagOrPeerReview = flagCount || peerReviewCount; + return ( = (props): React.ReactElement => { relative z-20 block overflow-hidden rounded-md border-2 px-3 py-2 text-grey-800 shadow transition-colors duration-500 dark:text-white-100 `} > - <> +
+ {props.title} +
+
- {props.title} -
-
= (props): React.ReactElement => { > {`${mainAuthor.firstName ? `${mainAuthor.firstName[0]}. ` : ''}${mainAuthor.lastName}`} -
- + +
{props.pointers.map((pointer, index) => ( publication.id) // get the ids of all direct child publications + .map((publication) => publication.id), // get the ids of all direct child publications + flagCount: publication.flagCount, + peerReviewCount: publication.peerReviewCount } ]; } @@ -169,7 +191,9 @@ const getPublicationsByType = (data: Interfaces.PublicationWithLinks, type: stri (publication) => publication.type !== 'PEER_REVIEW' && publication.id === linkedPublication.id ) - .map((publication) => publication.childPublication) // get the ids of all direct child publications + .map((publication) => publication.childPublication), // get the ids of all direct child publications + flagCount: linkedPublication.flagCount, + peerReviewCount: linkedPublication.peerReviewCount }); } } @@ -198,7 +222,9 @@ const getPublicationsByType = (data: Interfaces.PublicationWithLinks, type: stri publication.type !== 'PEER_REVIEW' && publication.parentPublication === linkedPublication.id ) - .map((publication) => publication.id) // get the ids of all direct child publications + .map((publication) => publication.id), // get the ids of all direct child publications + flagCount: linkedPublication.flagCount, + peerReviewCount: linkedPublication.peerReviewCount }); } } diff --git a/ui/src/components/index.tsx b/ui/src/components/index.tsx index a107f7d16..3c1cc4e74 100644 --- a/ui/src/components/index.tsx +++ b/ui/src/components/index.tsx @@ -19,6 +19,7 @@ export { default as Delay } from './Delay'; export { default as EditAffiliationsModal } from './EditAffiliationsModal'; export { default as EditReferenceModal } from './References/EditReferenceModal'; export { default as EnableDarkMode } from './EnableDarkMode'; +export { default as EngagementCounts } from './Publication/EngagementCounts'; export { default as ExtendedLink } from './ExtendedLink'; export { default as FileUpload } from './FileUpload'; export { default as FlagComment } from './Flag/Comment'; diff --git a/ui/src/lib/interfaces.ts b/ui/src/lib/interfaces.ts index cfa2e0a91..326edd422 100644 --- a/ui/src/lib/interfaces.ts +++ b/ui/src/lib/interfaces.ts @@ -54,6 +54,8 @@ export interface CorePublication { type: Types.PublicationType; doi: string | null; url_slug: string; + flagCount?: number; + peerReviewCount?: number; } export interface PublicationVersionUser { @@ -119,6 +121,8 @@ export interface LinkedPublication { authorFirstName: string; authorLastName: string; authors: Pick[]; + flagCount: number; + peerReviewCount: number; // Only returned with ?direct=true on the getPublicationLinks endpoint. // Just used at present to show, and link to, the version a Peer Review was created against. parentVersionId?: string;