diff --git a/database/109-mention-views.sql b/database/109-mention-views.sql index 590784a31..c2b7e63e3 100644 --- a/database/109-mention-views.sql +++ b/database/109-mention-views.sql @@ -1,3 +1,4 @@ +-- SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -- SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) -- SPDX-FileCopyrightText: 2023 Netherlands eScience Center -- @@ -17,3 +18,61 @@ $$ SELECT mention FROM reference_paper_for_software ) $$; + +-- UNIQUE CITATIONS BY SOFTWARE ID +CREATE FUNCTION citation_by_software() RETURNS TABLE ( + software UUID, + id UUID, + doi CITEXT, + url VARCHAR, + title VARCHAR, + authors VARCHAR, + publisher VARCHAR, + publication_year SMALLINT, + journal VARCHAR, + page VARCHAR, + image_url VARCHAR, + mention_type mention_type, + source VARCHAR, + reference_papers UUID[] +)LANGUAGE sql STABLE AS +$$ +SELECT + reference_paper_for_software.software, + mention.id, + mention.doi, + mention.url, + mention.title, + mention.authors, + mention.publisher, + mention.publication_year, + mention.journal, + mention.page, + mention.image_url, + mention.mention_type, + mention.source, + ARRAY_AGG( + reference_paper_for_software.mention + ) AS reference_paper +FROM + reference_paper_for_software +INNER JOIN + citation_for_mention ON citation_for_mention.mention = reference_paper_for_software.mention +INNER JOIN + mention ON mention.id = citation_for_mention.citation +GROUP BY + reference_paper_for_software.software, + mention.id, + mention.doi, + mention.url, + mention.title, + mention.authors, + mention.publisher, + mention.publication_year, + mention.journal, + mention.page, + mention.image_url, + mention.mention_type, + mention.source +; +$$; diff --git a/frontend/components/mention/MentionItemBase.tsx b/frontend/components/mention/MentionItemBase.tsx index 40d73ce2d..265d5f532 100644 --- a/frontend/components/mention/MentionItemBase.tsx +++ b/frontend/components/mention/MentionItemBase.tsx @@ -75,7 +75,7 @@ export default function MentionItemBase({item,pos,nav,type,role='find'}:MentionI className="text-sm" role={role} /> - + ) } diff --git a/frontend/components/mention/MentionViewItem.tsx b/frontend/components/mention/MentionViewItem.tsx index 61eba4570..7a1abc8e7 100644 --- a/frontend/components/mention/MentionViewItem.tsx +++ b/frontend/components/mention/MentionViewItem.tsx @@ -43,7 +43,7 @@ export default function MentionViewItem({item, pos}: {item: MentionItemProps, po doi={item?.doi} className="text-sm" /> - +
{item?.url ? : null} diff --git a/frontend/components/mention/useEditMentionReducer.tsx b/frontend/components/mention/useEditMentionReducer.tsx index cd8674aa8..679a6cb65 100644 --- a/frontend/components/mention/useEditMentionReducer.tsx +++ b/frontend/components/mention/useEditMentionReducer.tsx @@ -1,9 +1,11 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 -import {useContext} from 'react' +import {useCallback, useContext} from 'react' import {EditMentionActionType} from './editMentionReducer' import EditMentionContext from './editMentionContext' import {MentionItemProps} from '~/types/Mention' @@ -11,58 +13,60 @@ import {MentionItemProps} from '~/types/Mention' export default function useEditMentionReducer() { const {state, dispatch} = useContext(EditMentionContext) - function setLoading(loading: boolean) { + const setLoading = useCallback((loading:boolean)=>{ dispatch({ type: EditMentionActionType.SET_LOADING, payload: loading }) - } + // eslint-disable-next-line react-hooks/exhaustive-deps + },[]) - function setMentions(mentions: MentionItemProps[]) { + const setMentions = useCallback((mentions: MentionItemProps[])=>{ dispatch({ type: EditMentionActionType.SET_MENTIONS, payload: mentions }) - } + // eslint-disable-next-line react-hooks/exhaustive-deps + },[]) - function onAdd(item: MentionItemProps) { + const onAdd = useCallback((item: MentionItemProps)=>{ dispatch({ type: EditMentionActionType.ON_ADD, payload: item }) - } + },[dispatch]) - function onNewItem() { + const onNewItem = useCallback(()=>{ dispatch({ type: EditMentionActionType.SET_EDIT_MODAL, payload: { open:true } }) - } + },[dispatch]) - function onSubmit(item:MentionItemProps) { + const onSubmit = useCallback((item:MentionItemProps)=>{ dispatch({ type: EditMentionActionType.ON_SUBMIT, payload: item }) - } + },[dispatch]) - function onUpdate(item:MentionItemProps) { + const onUpdate = useCallback((item:MentionItemProps)=>{ dispatch({ type: EditMentionActionType.ON_UPDATE, payload: item }) - } + },[dispatch]) - function onDelete(item:MentionItemProps) { + const onDelete = useCallback((item:MentionItemProps)=>{ dispatch({ type: EditMentionActionType.ON_DELETE, payload: item }) - } + },[dispatch]) - function confirmDelete(item?: MentionItemProps) { + const confirmDelete = useCallback((item?: MentionItemProps)=>{ if (item) { // open modal dispatch({ @@ -81,9 +85,9 @@ export default function useEditMentionReducer() { } }) } - } + },[dispatch]) - function setEditModal(item?: MentionItemProps) { + const setEditModal = useCallback((item?: MentionItemProps)=>{ if (item) { // show modal when item provided dispatch({ @@ -102,9 +106,9 @@ export default function useEditMentionReducer() { } }) } - } + },[dispatch]) - // console.group('useOutputContext') + // console.group('useEditMentionReducer') // console.log('state...', state) // console.groupEnd() diff --git a/frontend/components/software/edit/editSoftwarePages.tsx b/frontend/components/software/edit/editSoftwarePages.tsx index 32006c06c..d0441139d 100644 --- a/frontend/components/software/edit/editSoftwarePages.tsx +++ b/frontend/components/software/edit/editSoftwarePages.tsx @@ -18,6 +18,7 @@ import PersonAddIcon from '@mui/icons-material/PersonAdd' import ContentLoader from '~/components/layout/ContentLoader' import HomeRepairServiceIcon from '@mui/icons-material/HomeRepairService' import PendingActionsIcon from '@mui/icons-material/PendingActions' +import PostAddIcon from '@mui/icons-material/PostAdd' // import SoftwareInformation from './information' // import SoftwareContributors from './contributors' @@ -28,22 +29,25 @@ import PendingActionsIcon from '@mui/icons-material/PendingActions' // import SoftwareMaintainers from './maintainers' // use dynamic imports instead +const SoftwareContributors = dynamic(() => import('./contributors'),{ + loading: ()=> +}) const SoftwareInformation = dynamic(() => import('./information'),{ loading: ()=> }) -const SoftwareContributors = dynamic(() => import('./contributors'),{ +const SoftwareMaintainers = dynamic(() => import('./maintainers'),{ loading: ()=> }) -const SoftwareOgranisations = dynamic(() => import('./organisations'),{ +const SoftwareMentions = dynamic(() => import('./mentions'),{ loading: ()=> }) -const PackageManagers = dynamic(() => import('./package-managers'),{ +const SoftwareOgranisations = dynamic(() => import('./organisations'),{ loading: ()=> }) -const SoftwareMentions = dynamic(() => import('./mentions'),{ +const PackageManagers = dynamic(() => import('./package-managers'),{ loading: ()=> }) -const SoftwareTestimonials = dynamic(() => import('./testimonials'),{ +const ReferencePapers = dynamic(() => import('./reference-papers'),{ loading: ()=> }) const RelatedSoftware = dynamic(() => import('./related-software'),{ @@ -52,10 +56,11 @@ const RelatedSoftware = dynamic(() => import('./related-software'),{ const RelatedProjects = dynamic(() => import('./related-projects'),{ loading: ()=> }) -const SoftwareMaintainers = dynamic(() => import('./maintainers'),{ +const SoftwareTestimonials = dynamic(() => import('./testimonials'),{ loading: ()=> }) + export type EditSoftwarePageProps = { id: string status: string, @@ -82,6 +87,12 @@ export const editSoftwarePage:EditSoftwarePageProps[] = [{ icon: , render: () => , status: 'Optional information' +},{ + id: 'reference-paper', + label: 'Reference papers', + icon: , + render: () => , + status: 'Optional information' },{ id: 'mentions', label: 'Mentions', diff --git a/frontend/components/software/edit/mentions/EditMentionsProvider.tsx b/frontend/components/software/edit/mentions/EditMentionsProvider.tsx index c302c7082..da11313f1 100644 --- a/frontend/components/software/edit/mentions/EditMentionsProvider.tsx +++ b/frontend/components/software/edit/mentions/EditMentionsProvider.tsx @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -112,7 +114,7 @@ export default function EditMentionsProvider(props: any) { } } if (item.id) { - // existing RSD mention item to be added to project + // existing RSD mention item to be added to software const resp = await addToMentionForSoftware({ software, mention: item.id, diff --git a/frontend/components/software/edit/reference-papers/CitationItem.tsx b/frontend/components/software/edit/reference-papers/CitationItem.tsx new file mode 100644 index 000000000..e9b73db5a --- /dev/null +++ b/frontend/components/software/edit/reference-papers/CitationItem.tsx @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {MentionItemProps} from '~/types/Mention' +import {getMentionType} from '~/components/mention/config' +import MentionAuthors from '~/components/mention/MentionAuthors' +import MentionDoi from '~/components/mention/MentionDoi' +import {MentionTitle} from '~/components/mention/MentionItemBase' +import MentionPublisherItem from '~/components/mention/MentionPublisherItem' +import useEditMentionReducer from '~/components/mention/useEditMentionReducer' + +export type CitationItemProps = MentionItemProps & { + reference_papers: string[] +} + +function ReferencePapers({papers}:{papers:string[]}){ + const {mentions} = useEditMentionReducer() + const reference:{doi:string,url:string}[] =[] + + if (papers?.length===0 || mentions?.length===0){ + return ( +
+ Referenced paper: +
+ ) + } + + papers.forEach(uuid=>{ + const found = mentions.find(item=>item.id === uuid) + if (found?.doi && found.url) reference.push({doi:found.doi, url:found.url}) + }) + + if (reference.length === 0){ + return ( +
+ Referenced: None +
+ ) + } + + return( +
+ Referenced: { + reference.map(item=>{ + return ( + + {item.doi} + + ) + }) + } +
+ ) +} + + +export default function CitationItem({item}:{item:CitationItemProps}){ + const title = getMentionType(item?.mention_type,'singular') + return ( +
+
+ {title} +
+ + + + + +
+ ) +} diff --git a/frontend/components/software/edit/reference-papers/CitationsBySoftware.tsx b/frontend/components/software/edit/reference-papers/CitationsBySoftware.tsx new file mode 100644 index 000000000..8bf53415d --- /dev/null +++ b/frontend/components/software/edit/reference-papers/CitationsBySoftware.tsx @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {MouseEvent,ChangeEvent, useState, useEffect} from 'react' +import TablePagination from '@mui/material/TablePagination' + +import {rowsPerPageOptions} from '~/config/pagination' +import Searchbox from '~/components/form/Searchbox' +import useCitationsForSoftware from './useCitationsBySoftware' +import CitationItem from './CitationItem' +import {useCitationCnt} from './TabCountsProvider' +import NoCitations from './NoCitations' + +export default function CitationBySoftware() { + const [query,setQuery] = useState({ + search: '', + rows: 12, + page: 0 + }) + const {citations,count,loading} = useCitationsForSoftware(query) + const {setCitationCnt} = useCitationCnt() + + useEffect(()=>{ + if (count) { + setCitationCnt(count) + } + },[count,setCitationCnt]) + + function handleTablePageChange( + event: MouseEvent | null, + newPage: number, + ) { + // Pagination component starts counting from 0, but we need to start from 1 + // handleQueryChange('page',(newPage + 1).toString()) + setQuery({ + ...query, + page: newPage + }) + } + + function handleItemsPerPage( + event: ChangeEvent, + ) { + setQuery({ + ...query, + rows: parseInt(event.target.value), + // reset to first page when changing + page: 0 + }) + } + + function handleSearch(searchFor: string) { + setQuery({ + ...query, + search: searchFor, + // reset to first page when changing + page: 0 + }) + } + + if (citations?.length===0 && loading===false){ + return
+ } + + return ( + <> +
+
+ +
+ +
+
+ { + citations?.map(item=>{ + return + }) + } +
+ + ) +} diff --git a/frontend/components/software/edit/reference-papers/EditReferencePapersProvider.tsx b/frontend/components/software/edit/reference-papers/EditReferencePapersProvider.tsx new file mode 100644 index 000000000..4c8836a5c --- /dev/null +++ b/frontend/components/software/edit/reference-papers/EditReferencePapersProvider.tsx @@ -0,0 +1,221 @@ +// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all) +// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {useReducer} from 'react' + +import {useSession} from '~/auth' +import logger from '~/utils/logger' +import {deleteMentionItem, updateMentionItem} from '~/utils/editMentions' +import {MentionItemProps} from '~/types/Mention' +import useSnackbar from '~/components/snackbar/useSnackbar' +import { + EditMentionAction, EditMentionActionType, + editMentionReducer, EditMentionState +} from '~/components/mention/editMentionReducer' +import EditMentionContext from '~/components/mention/editMentionContext' +import NoRefferencePapers from './NoRefferencePapers' +import { + addToReferencePaperForSoftware, + addNewReferencePaperToSoftware, + removeReferencePaperForSoftware +} from './apiReferencePapers' +import useSoftwareContext from '../useSoftwareContext' + +const initalState:EditMentionState = { + settings: { + editModalTitle: 'Reference papers', + confirmDeleteModalTitle: 'Delete reference paper', + noItemsComponent:()=> + }, + loading: true, + processing: false, + mentions: [], + editModal: { + open:false + }, + confirmModal: { + open:false + } +} + +export default function EditReferencePapersProvider(props: any) { + const {user,token} = useSession() + const {software:{id:software}} = useSoftwareContext() + const {showErrorMessage,showSuccessMessage,showInfoMessage} = useSnackbar() + const [state, dispatch] = useReducer(editMentionReducer,initalState) + + // console.group('EditReferencePapersProvider') + // console.log('software...', software) + // console.groupEnd() + + async function processOnSubmit(action: EditMentionAction) { + const item:MentionItemProps = action.payload + // new item created manually + if (item.id === null || item.id === '') { + item.id = null + item.source = 'RSD' + // new item to be added + const resp = await addNewReferencePaperToSoftware({ + item, + software, + token + }) + // debugger + if (resp.status === 200) { + dispatch({ + type: EditMentionActionType.ADD_ITEM, + // updated item is in message + payload: resp.message + }) + // show success + showSuccessMessage(`Added ${item.title}`) + } else { + showErrorMessage(resp.message as string) + } + } else if (user?.role === 'rsd_admin') { + // rsd_admin can update mention + const resp = await updateMentionItem({ + mention: item, + token, + }) + if (resp.status !== 200) { + showErrorMessage(`Failed to update ${item.title}. ${resp.message}`) + return + } + dispatch({ + type: EditMentionActionType.UPDATE_ITEM, + // item is returned in message + payload: resp.message + }) + } + } + + async function processOnAdd(action: EditMentionAction) { + const item: MentionItemProps = action.payload + // check if already in collection + if (item.doi) { + const found = state.mentions.find(mention=>mention.doi===item.doi) + if (found) { + showInfoMessage(`Reference paper with DOI ${item.doi} is already in the list.`) + return true + } + } + if (item.id) { + // existing RSD mention item to be added to software + const resp = await addToReferencePaperForSoftware({ + software, + mention: item.id, + token + }) + // debugger + if (resp.status !== 200) { + showErrorMessage(`Failed to add ${item.title}. ${resp.message}`) + } else { + dispatch({ + type: EditMentionActionType.ADD_ITEM, + payload: item + }) + // show success + showSuccessMessage(`Added ${item.title}`) + } + } else { + // probably new item from crossref or datacite + const resp = await addNewReferencePaperToSoftware({item, software, token}) + // debugger + if (resp.status === 200) { + dispatch({ + type: EditMentionActionType.ADD_ITEM, + // updated item is in message + payload: resp.message + }) + // show success + showSuccessMessage(`Added ${item.title}`) + } else { + showErrorMessage(resp.message as string) + } + } + } + + async function processOnDelete(action: EditMentionAction) { + const item = action.payload + if (item.id) { + const resp = await removeReferencePaperForSoftware({ + software, + mention: item.id, + token + }) + if (resp.status == 200) { + dispatch({ + type: EditMentionActionType.DELETE_ITEM, + // item is returned in message + payload: item + }) + // try to remove mention item + // we do not handle response result + // because if mention is referenced in other + // software the delete action will fail (and that's ok) + const del = await deleteMentionItem({ + id: item.id, + token + }) + } else { + showErrorMessage(`Failed to delete ${item.title}. ${resp.message}`) + } + } else { + showErrorMessage(`Failed to delete ${item.title}. Invalid item id ${item.id}`) + } + } + + /** + * Middleware function that intercepts actions/messages send by mention module components + * or local components which use useMentionReducer hook. This middleware function is used + * to call other functions that perform api calls. In most cases we pass original + * action/message further to other listeners/subscribers. + * @param action + */ + function dispatchMiddleware(action: EditMentionAction) { + // console.log('impactMiddleware...action...', action) + switch (action.type) { + case EditMentionActionType.ON_SUBMIT: + // pass original action + dispatch(action) + // process item by api + // and dispatch next action (see function) + processOnSubmit(action) + break + case EditMentionActionType.ON_ADD: + // pass original action + dispatch(action) + // process item by api + processOnAdd(action) + break + case EditMentionActionType.ON_UPDATE: + // WE do not allow update of mentions with DOI + logger('ON_UPDATE action not supported','warn') + // pass original action + dispatch(action) + break + case EditMentionActionType.ON_DELETE: + // pass original action + dispatch(action) + // process item by api + processOnDelete(action) + break + default: + // just dispatch original action + dispatch(action) + } + } + + return ( + + ) +} diff --git a/frontend/components/software/edit/reference-papers/FindReferencePaper.tsx b/frontend/components/software/edit/reference-papers/FindReferencePaper.tsx new file mode 100644 index 000000000..321526215 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/FindReferencePaper.tsx @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) +// +// SPDX-License-Identifier: Apache-2.0 + +import {useAuth} from '~/auth' +import {getMentionByDoi} from '~/utils/getDOI' +import {getMentionByDoiFromRsd} from '~/utils/editMentions' +import {MentionItemProps} from '~/types/Mention' +import EditSectionTitle from '~/components/layout/EditSectionTitle' +import FindMention from '~/components/mention/FindMention' +import FindMentionInfoPanel from '~/components/mention/FindMentionInfoPanel' +import useEditMentionReducer from '~/components/mention/useEditMentionReducer' +import {extractSearchTerm} from '~/components/software/edit/mentions/utils' +import {findPublicationByTitle} from '~/components/software/edit/mentions/mentionForSoftwareApi' +import useSoftwareContext from '../useSoftwareContext' +import {cfgReferencePapers as config} from './config' + +export default function FindReferencePaper() { + const {software} = useSoftwareContext() + const {session: {token}} = useAuth() + const {onAdd} = useEditMentionReducer() + + async function findPublication(searchFor: string) { + const searchData = extractSearchTerm(searchFor) + if (searchData.type === 'doi') { + searchFor = searchData.term + // look first at RSD + const rsd = await getMentionByDoiFromRsd({ + doi: searchFor, + token + }) + if (rsd?.status === 200 && rsd.message?.length === 1) { + // return first found item in RSD + const item:MentionItemProps = rsd.message[0] + return [item] + } + // else find by DOI + const resp = await getMentionByDoi(searchFor) + if (resp?.status === 200) { + return [resp.message as MentionItemProps] + } + return [] + } else{ + searchFor = searchData.term + // find by title + const mentions = await findPublicationByTitle({ + software: software.id ?? '', + searchFor, + token + }) + return mentions + } + } + + return ( + <> + + {/*

Search

*/} + +
+ +
+
+ + ) +} diff --git a/frontend/components/software/edit/reference-papers/NoCitations.tsx b/frontend/components/software/edit/reference-papers/NoCitations.tsx new file mode 100644 index 000000000..12b4d45f7 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/NoCitations.tsx @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' + +export default function NoCitations() { + return ( + + No citations to show + Try adding the reference papers first. RSD scraper + will then periodically run and find all citations using OpenAlex api. + + ) +} diff --git a/frontend/components/software/edit/reference-papers/NoRefferencePapers.tsx b/frontend/components/software/edit/reference-papers/NoRefferencePapers.tsx new file mode 100644 index 000000000..d190a3b13 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/NoRefferencePapers.tsx @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import Alert from '@mui/material/Alert' +import AlertTitle from '@mui/material/AlertTitle' + +export default function NoReferencePapers() { + return ( + + No reference papers to show + Add one using search option! + + ) +} diff --git a/frontend/components/software/edit/reference-papers/PageTabs.tsx b/frontend/components/software/edit/reference-papers/PageTabs.tsx new file mode 100644 index 000000000..e14daafec --- /dev/null +++ b/frontend/components/software/edit/reference-papers/PageTabs.tsx @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {useState} from 'react' +import Tabs from '@mui/material/Tabs' +import Tab from '@mui/material/Tab' + +import {useSession} from '~/auth' +import useSoftwareContext from '../useSoftwareContext' +import CitationsBySoftware from './CitationsBySoftware' +import ReferencePapersTab from './ReferencePapersTab' +import {useTabCountsContext} from './TabCountsProvider' +import EditReferencePapersProvider from './EditReferencePapersProvider' + + +function TabContent({tab}:{tab:'reference'|'citation'}){ + switch(tab){ + case 'citation': + return + default: + // reference papers is default value + return + } +} + +export default function PageTabs() { + const {token} = useSession() + const {software} = useSoftwareContext() + const [tab, setTab] = useState<'reference'|'citation'>('reference') + const {referencePaperCnt,citationCnt} = useTabCountsContext() + return ( + <> + setTab(value)} + > + + + + {/* Reference papers tab provider is shared on both tabs to link to reference paper DOI*/} + + + + + ) +} diff --git a/frontend/components/software/edit/reference-papers/ReferencePaper.tsx b/frontend/components/software/edit/reference-papers/ReferencePaper.tsx new file mode 100644 index 000000000..14f53b049 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/ReferencePaper.tsx @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import IconButton from '@mui/material/IconButton' +import DeleteIcon from '@mui/icons-material/Delete' + +import {MentionItemProps} from '~/types/Mention' +import MentionAuthors from '~/components/mention/MentionAuthors' +import MentionDoi from '~/components/mention/MentionDoi' +import {MentionTitle} from '~/components/mention/MentionItemBase' +import MentionPublisherItem from '~/components/mention/MentionPublisherItem' +import {getMentionType} from '~/components/mention/config' + +export type ReferencePaperProps = { + item: MentionItemProps + onDelete: () => void +} + +export default function ReferencePaper({item,onDelete}:ReferencePaperProps){ + const mentionType = getMentionType(item?.mention_type,'singular') + return ( +
+ +
+
+
+ {mentionType} +
+ +
+ + + +
+ + + + +
+ ) +} diff --git a/frontend/components/software/edit/reference-papers/ReferencePapersList.tsx b/frontend/components/software/edit/reference-papers/ReferencePapersList.tsx new file mode 100644 index 000000000..1be9cb804 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/ReferencePapersList.tsx @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {useEffect} from 'react' +import {useSession} from '~/auth' +import ContentLoader from '~/components/layout/ContentLoader' +import useEditMentionReducer from '~/components/mention/useEditMentionReducer' +import useSoftwareContext from '../useSoftwareContext' +import ReferencePaper from './ReferencePaper' +import {useCitationCnt, useReferencePaperCnt} from './TabCountsProvider' +import {getCitationCountForSoftware, getReferencePapersForSoftware} from './apiReferencePapers' +import NoReferencePapers from './NoRefferencePapers' + +export default function ReferencePapersList() { + const {token} = useSession() + const {software} = useSoftwareContext() + const {mentions, setMentions, loading, setLoading, onDelete} = useEditMentionReducer() + const {setReferencePaperCnt} = useReferencePaperCnt() + const {setCitationCnt} = useCitationCnt() + + // console.group('ReferencePapersList') + // console.log('mentions...', mentions) + // console.log('loading...', loading) + // console.groupEnd() + // debugger + + useEffect(()=>{ + let abort = false + if (software.id && token){ + getCitationCountForSoftware(software.id,token) + .then(count=> { + setCitationCnt(count) + }) + } + return () => { abort = true } + },[software.id,token,setCitationCnt]) + + useEffect(() => { + let abort = false + async function getReferencePapersFromApi() { + setLoading(true) + const referencePapers = await getReferencePapersForSoftware({ + software: software.id, + token + }) + if (referencePapers && abort === false) { + // debugger + setMentions(referencePapers) + setLoading(false) + } + } + if (software?.id && token) { + getReferencePapersFromApi() + } + return () => { abort = true } + },[software.id,token,setLoading,setMentions]) + + + if (loading) { + return ( +
+ +
+ ) + } + + setReferencePaperCnt(mentions.length) + + if (mentions.length === 0){ + return + } + + return ( + <> + {mentions.map(item=>{ + return ( + { + onDelete(item) + }} + /> + ) + })} + + ) +} diff --git a/frontend/components/software/edit/reference-papers/ReferencePapersTab.tsx b/frontend/components/software/edit/reference-papers/ReferencePapersTab.tsx new file mode 100644 index 000000000..267552a26 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/ReferencePapersTab.tsx @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import EditSection from '~/components/layout/EditSection' +import FindReferencePaper from './FindReferencePaper' +import ReferencePapersList from './ReferencePapersList' + +export default function ReferencePapersTab() { + return ( + +
+ +
+
+ +
+
+ ) +} diff --git a/frontend/components/software/edit/reference-papers/TabCountsProvider.tsx b/frontend/components/software/edit/reference-papers/TabCountsProvider.tsx new file mode 100644 index 000000000..4450b5a53 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/TabCountsProvider.tsx @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {Dispatch, SetStateAction, createContext, useContext, useState} from 'react' + +type TabCounts={ + referencePaperCnt: number | null, + citationCnt: number | null, + setReferencePaperCnt: Dispatch>, + setCitationCnt: Dispatch>, +} + +const TabCountContext = createContext(null) + +export function TabCountsProvider(props:any){ + const [referencePaperCnt, setReferencePaperCnt] = useState(null) + const [citationCnt, setCitationCnt] = useState(null) + + return ( + + ) +} + +export function useTabCountsContext(){ + const context = useContext(TabCountContext) + if (context===null){ + throw new Error('useTabCountsContext must be used within TabCountProvider') + } + return context +} + +export function useReferencePaperCnt(){ + const {referencePaperCnt,setReferencePaperCnt} = useTabCountsContext() + return { + referencePaperCnt, + setReferencePaperCnt + } +} + +export function useCitationCnt(){ + const {citationCnt,setCitationCnt} = useTabCountsContext() + return { + citationCnt, + setCitationCnt + } +} diff --git a/frontend/components/software/edit/reference-papers/apiReferencePapers.ts b/frontend/components/software/edit/reference-papers/apiReferencePapers.ts new file mode 100644 index 000000000..4936ccd73 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/apiReferencePapers.ts @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import logger from '~/utils/logger' +import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers' +import {MentionItemProps, mentionColumns} from '~/types/Mention' +import {addOrGetMentionItem} from '~/utils/editMentions' +import {extractCountFromHeader} from '~/utils/extractCountFromHeader' +import {CitationItemProps} from './CitationItem' + +export async function addToReferencePaperForSoftware({mention, software, token}: + { mention: string, software: string, token: string }) { + const url = '/api/v1/reference_paper_for_software' + try { + const resp = await fetch(url, { + method: 'POST', + headers: createJsonHeaders(token), + body: JSON.stringify({ + software, + mention + }) + }) + + return extractReturnMessage(resp, mention) + + } catch (e: any) { + logger(`addToReferencePaperForSoftware: ${e?.message}`, 'error') + return { + status: 500, + message: e?.message + } + } +} + +export async function addNewReferencePaperToSoftware({item, software, token}: + { item: MentionItemProps, software: string, token: string }) { + // add new item or get existing by DOI + let resp = await addOrGetMentionItem({ + mention:item, + token + }) + // debugger + if (resp.status === 200) { + // mention item returned in message + const mention: MentionItemProps = resp.message + if (mention.id) { + resp = await addToReferencePaperForSoftware({ + software, + mention: mention.id, + token + }) + if (resp.status === 200) { + // we return mention item in message + return { + status: 200, + message: mention + } + } else { + return resp + } + } else { + return { + status: 500, + message: 'Reference paper id is missing.' + } + } + } else { + return resp + } +} + +export async function removeReferencePaperForSoftware({mention, software, token}: + { mention: string, software: string, token: string }) { + const url = `/api/v1/reference_paper_for_software?software=eq.${software}&mention=eq.${mention}` + try { + const resp = await fetch(url, { + method: 'DELETE', + headers: createJsonHeaders(token) + }) + + return extractReturnMessage(resp, mention) + + } catch (e: any) { + logger(`removeReferencePaperForSoftware: ${e?.message}`, 'error') + return { + status: 500, + message: e?.message + } + } +} + +export async function getReferencePapersForSoftware({software,token}:{software: string, token?: string}) { + try { + // the content is ordered by type ascending + const query = `software?id=eq.${software}&select=id,slug,mention!reference_paper_for_software(${mentionColumns})&mention.order=publication_year.desc,mention_type.asc` + // construct url + const url = `${getBaseUrl()}/${query}` + // make request + const resp = await fetch(url, { + method: 'GET', + headers: { + ...createJsonHeaders(token), + // request single object item + 'Accept': 'application/vnd.pgrst.object+json' + } + }) + if (resp.status === 200) { + const json = await resp.json() + // extract mentions from software object + const mentions: MentionItemProps[] = json?.mention ?? [] + return mentions + } + logger(`getReferencePapersForSoftware: [${resp.status}] [${url}]`, 'error') + // query not found + return [] + } catch (e: any) { + logger(`getReferencePapersForSoftware: ${e?.message}`, 'error') + return [] + } +} + +export type CitationState={ + count: number, + citations: CitationItemProps[] +} + + +type GetCitationsForSoftwareProps={ + software: string, + token?: string + search?: string | null + page?:number | null, + rows?:number | null +} + +export async function getCitationsForSoftware({ + software,token,search,page,rows}:GetCitationsForSoftwareProps +):Promise { + try { + // the content is ordered by type ascending + const offset = (page ?? 0) * (rows ?? 12) + let query = `rpc/citation_by_software?software=eq.${software}&order=publication_year.desc,mention_type&offset=${offset}&limit=${rows}` + if (search){ + query += `&or=(title.ilike.*${search}*,authors.ilike.*${search}*,publisher.ilike.*${search}*,url.ilike.*${search}*)` + } + // construct url + const url = `${getBaseUrl()}/${query}` + // make request + const resp = await fetch(url, { + method: 'GET', + headers: { + ...createJsonHeaders(token), + 'Prefer': 'count=exact' + } + }) + if ([200,206].includes(resp.status)===true) { + const json:CitationItemProps[] = await resp.json() + return { + count: extractCountFromHeader(resp.headers) ?? 0, + citations: json, + } + } + logger(`getCitationsForSoftware: [${resp.status}] [${url}]`, 'error') + // query not found + return { + count:0, + citations:[] + } + } catch (e: any) { + logger(`getCitationsForSoftware: ${e?.message}`, 'error') + return { + count:0, + citations:[] + } + } +} + +export async function getCitationCountForSoftware(software:string,token:string){ + try { + // the content is ordered by type ascending + let query = `rpc/citation_by_software?software=eq.${software}&order=publication_year.desc,mention_type&offset=0&limit=3` + // construct url + const url = `${getBaseUrl()}/${query}` + // make request + const resp = await fetch(url, { + method: 'GET', + headers: { + ...createJsonHeaders(token), + 'Prefer': 'count=exact' + } + }) + if ([200,206].includes(resp.status)===true) { + return extractCountFromHeader(resp.headers) ?? 0 + } + logger(`getCitationCountForSoftware: [${resp.status}] [${url}]`, 'error') + return 0 + } catch (e: any) { + logger(`getCitationCountForSoftware: ${e?.message}`, 'error') + return 0 + } +} diff --git a/frontend/components/software/edit/reference-papers/config.ts b/frontend/components/software/edit/reference-papers/config.ts new file mode 100644 index 000000000..1790b150e --- /dev/null +++ b/frontend/components/software/edit/reference-papers/config.ts @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) +// SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {findMention} from '~/components/mention/config' +import config from '~/components/mention/ImportMentions/config' + +export const cfgReferencePapers = { + title: 'Reference papers', + findMention: { + ...findMention, + title: 'Add reference paper' + }, + doiInput: config.doiInput +} diff --git a/frontend/components/software/edit/reference-papers/index.tsx b/frontend/components/software/edit/reference-papers/index.tsx new file mode 100644 index 000000000..0c051eba1 --- /dev/null +++ b/frontend/components/software/edit/reference-papers/index.tsx @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {TabCountsProvider} from './TabCountsProvider' +import PageTabs from './PageTabs' + +export default function ReferencePapers() { + return ( +
+ + + +
+ ) +} diff --git a/frontend/components/software/edit/reference-papers/useCitationsBySoftware.tsx b/frontend/components/software/edit/reference-papers/useCitationsBySoftware.tsx new file mode 100644 index 000000000..5e7ceb16c --- /dev/null +++ b/frontend/components/software/edit/reference-papers/useCitationsBySoftware.tsx @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {useEffect, useState} from 'react' +import {useSession} from '~/auth' +import useSoftwareContext from '../useSoftwareContext' +import {CitationState, getCitationsForSoftware} from './apiReferencePapers' + +type UseCitationsForSoftwareProps = { + search?:string | null + page?:number | null, + rows?:number | null +} + +export default function useCitationsForSoftware({search,page=0,rows=12}:UseCitationsForSoftwareProps){ + const {token} = useSession() + const {software} = useSoftwareContext() + const [loading, setLoading] = useState(true) + const [state, setState] = useState() + + useEffect(() => { + let abort = false + async function getCitationsFromApi() { + setLoading(true) + const state = await getCitationsForSoftware({ + software: software.id, + token, + search, + page, + rows + }) + if (abort === false) { + // debugger + setState(state) + setLoading(false) + } + } + if (software && token) { + getCitationsFromApi() + } + return () => { abort = true } + },[software,token,search,page,rows]) + + // console.group('useCitationsForSoftware') + // console.log('loading...', loading) + // console.log('citationCnt...', mentions.length) + // console.groupEnd() + + return { + loading, + ...state + } +} diff --git a/frontend/types/Mention.ts b/frontend/types/Mention.ts index 05dfaed48..99b2bfd7f 100644 --- a/frontend/types/Mention.ts +++ b/frontend/types/Mention.ts @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 @@ -34,7 +35,7 @@ export type MentionItemProps = { // is_featured?: boolean mention_type: MentionTypeKeys | null source: string - note: string | null + note?: string | null } export const mentionColumns ='id,doi,url,title,authors,publisher,publication_year,journal,page,image_url,mention_type,source,note' diff --git a/frontend/utils/editMentions.ts b/frontend/utils/editMentions.ts index 1fb242f49..f3a670ac2 100644 --- a/frontend/utils/editMentions.ts +++ b/frontend/utils/editMentions.ts @@ -17,7 +17,8 @@ import logger from './logger' export async function getMentionsForSoftware({software,token}:{software: string, token?: string}) { try { // the content is ordered by type ascending - const query = `software?id=eq.${software}&select=id,slug,mention(${mentionColumns})&mention.order=mention_type.asc` + // Try changing 'mention' to one of the following: 'mention!mention_for_software', 'mention!reference_paper_for_software'. Find the desired relationship in the 'details' key. + const query = `software?id=eq.${software}&select=id,slug,mention!mention_for_software(${mentionColumns})&mention.order=mention_type.asc` // construct url const url = `${getBaseUrl()}/${query}` // make request diff --git a/scrapers/jobs.cron b/scrapers/jobs.cron index 7ee245e89..2b2f5e14a 100644 --- a/scrapers/jobs.cron +++ b/scrapers/jobs.cron @@ -9,4 +9,5 @@ 1-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.doi.MainReleases > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 3-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.doi.MainMentions > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 5-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.git.MainContributors > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 +5-59/6 * * * * /usr/local/openjdk-18/bin/java -cp /usr/myjava/scrapers.jar nl.esciencecenter.rsd.scraper.doi.MainCitations > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1 0 1 * * * /usr/bin/python3 -u /usr/myjava/oaipmh.py > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1