diff --git a/database/002-create-image-table.sql b/database/002-create-image-table.sql index 69f0aeec9..3c01c5bf0 100644 --- a/database/002-create-image-table.sql +++ b/database/002-create-image-table.sql @@ -1,7 +1,8 @@ +-- SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -- SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) --- SPDX-FileCopyrightText: 2022 Netherlands eScience Center -- SPDX-FileCopyrightText: 2022 dv4all +-- SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -- -- SPDX-License-Identifier: Apache-2.0 @@ -41,6 +42,7 @@ CREATE TRIGGER sanitise_update_image BEFORE UPDATE ON image FOR EACH ROW EXECUTE -- ---------------------------------------- -- RPC to get image by id => sha-1 of data +-- cache incrased to 1 year based on lighthouse audit -- ---------------------------------------- CREATE FUNCTION get_image(uid VARCHAR(40)) RETURNS BYTEA STABLE LANGUAGE plpgsql AS @@ -52,7 +54,7 @@ BEGIN SELECT format( '[{"Content-Type": "%s"},' '{"Content-Disposition": "inline; filename=\"%s\""},' - '{"Cache-Control": "max-age=259200"}]', + '{"Cache-Control": "max-age=31536001"}]', mime_type, uid) FROM image WHERE id = uid INTO headers; diff --git a/docker-compose.yml b/docker-compose.yml index fa974507a..9b028a5bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,7 +108,7 @@ services: # dockerfile to use for build dockerfile: Dockerfile # update version number to correspond to frontend/package.json - image: rsd/frontend:2.0.0 + image: rsd/frontend:2.0.1 environment: # it uses values from .env file - POSTGREST_URL diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bd8918a3d..c4141fe19 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,7 @@ # SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) # SPDX-FileCopyrightText: 2021 - 2022 dv4all +# SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +# SPDX-FileCopyrightText: 2023 Netherlands eScience Center # # SPDX-License-Identifier: Apache-2.0 @@ -10,7 +12,7 @@ # ---------------------------------------- # 1. Install dependencies only when needed # ---------------------------------------- -FROM node:18.5-buster-slim AS deps +FROM node:20.5-slim AS deps WORKDIR /app @@ -45,7 +47,7 @@ RUN yarn build # ---------------------------------------- # 3. Production image (standalone mode) # ---------------------------------------- -FROM node:18.5-buster-slim AS runner +FROM node:20.5-slim AS runner # optional install updates # RUN apt-get upgrade -y diff --git a/frontend/auth/api/useLoginProviders.tsx b/frontend/auth/api/useLoginProviders.tsx index ddebdc03a..f1c4c8b9a 100644 --- a/frontend/auth/api/useLoginProviders.tsx +++ b/frontend/auth/api/useLoginProviders.tsx @@ -1,5 +1,7 @@ // 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 @@ -7,19 +9,38 @@ import {useEffect, useState} from 'react' import {Provider} from 'pages/api/fe/auth' +// save info after initial call +let loginProviders:Provider[] = [] + export default function useLoginProviders() { const [providers, setProviders] = useState([]) + // console.group('useLoginProviders') + // console.log('providers...', providers) + // console.log('loginProviders...', loginProviders) + // console.groupEnd() + useEffect(() => { let abort = false async function getProviders() { - const url = '/api/fe/auth' - const resp = await fetch(url) - if (resp.status === 200 && abort === false) { - const providers: Provider[] = await resp.json() - if (abort) return - setProviders(providers) + if (loginProviders.length === 0){ + const url = '/api/fe/auth' + const resp = await fetch(url) + if (resp.status === 200 && abort === false) { + const providers: Provider[] = await resp.json() + if (abort) return + setProviders(providers) + // api response is the same once the app is started + // because the info eventually comes from .env file + // to avoid additional api calls we save api response + // into the loginProviders variable and reuse it + loginProviders = [ + ...providers + ] + } + }else if (abort===false){ + setProviders(loginProviders) } } if (abort === false) { diff --git a/frontend/components/AppHeader/index.tsx b/frontend/components/AppHeader/index.tsx index 03dd722a1..7e3e3767e 100644 --- a/frontend/components/AppHeader/index.tsx +++ b/frontend/components/AppHeader/index.tsx @@ -64,8 +64,20 @@ export default function AppHeader() { className="flex-1 flex flex-col px-4 xl:flex-row items-start lg:container lg:mx-auto">
- - + + diff --git a/frontend/components/layout/ImageWithPlaceholder.tsx b/frontend/components/layout/ImageWithPlaceholder.tsx index cc3b41d26..9e43c6684 100644 --- a/frontend/components/layout/ImageWithPlaceholder.tsx +++ b/frontend/components/layout/ImageWithPlaceholder.tsx @@ -55,6 +55,9 @@ export default function ImageWithPlaceholder({ title={placeholder ?? alt} role="img" src={src} + // lighthouse audit requires explicit width and height + height="100%" + width="100%" style={{ objectFit: bgSize, objectPosition: bgPosition diff --git a/frontend/components/layout/LogoAvatar.tsx b/frontend/components/layout/LogoAvatar.tsx index 6b0d2f8d2..0e741b67f 100644 --- a/frontend/components/layout/LogoAvatar.tsx +++ b/frontend/components/layout/LogoAvatar.tsx @@ -1,5 +1,7 @@ // 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 @@ -19,13 +21,14 @@ export default function LogoAvatar({name,src,sx,...props}:LogoAvatarProps) { alt={name} src={src} sx={{ + // lighthouse audit requires explicit width and height width: '100%', height: '100%', fontSize: '3rem', '& img': { - height: 'auto', + // height: 'auto', maxHeight: '100%', - width: 'auto', + // width: 'auto', maxWidth: '100%' }, ...sx diff --git a/frontend/components/login/LoginButton.tsx b/frontend/components/login/LoginButton.tsx index d557bc99a..94da49e61 100644 --- a/frontend/components/login/LoginButton.tsx +++ b/frontend/components/login/LoginButton.tsx @@ -18,10 +18,8 @@ import useLoginProviders from '~/auth/api/useLoginProviders' import {getUserMenuItems} from '~/config/userMenuItems' import UserMenu from '~/components/layout/UserMenu' import LoginDialog from './LoginDialog' -import useRsdSettings from '~/config/useRsdSettings' export default function LoginButton() { - const {host} = useRsdSettings() const providers = useLoginProviders() const {session} = useAuth() const status = session?.status || 'loading' diff --git a/frontend/components/projects/edit/impact/EditProjectImpactIndex.test.tsx b/frontend/components/projects/edit/impact/EditProjectImpactIndex.test.tsx index 86d108856..8015f8b7c 100644 --- a/frontend/components/projects/edit/impact/EditProjectImpactIndex.test.tsx +++ b/frontend/components/projects/edit/impact/EditProjectImpactIndex.test.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Netherlands eScience Center // @@ -21,7 +22,7 @@ import projectState from '../__mocks__/editProjectState' const mockGetImpactForProject = jest.fn((props) => Promise.resolve(mockImpactForProject)) jest.mock('~/utils/getProjects', () => ({ - getImpactForProject: jest.fn((props)=>mockGetImpactForProject(props)) + getMentionsForProject: jest.fn((props)=>mockGetImpactForProject(props)) })) const mockGetMentionByDoiFromRsd = jest.fn((props) => Promise.resolve([] as any)) diff --git a/frontend/components/projects/edit/impact/ImportProjectImpact.tsx b/frontend/components/projects/edit/impact/ImportProjectImpact.tsx index 42e363315..c7d9d7eb8 100644 --- a/frontend/components/projects/edit/impact/ImportProjectImpact.tsx +++ b/frontend/components/projects/edit/impact/ImportProjectImpact.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Netherlands eScience Center @@ -10,7 +11,7 @@ import ImportMentions from '~/components/mention/ImportMentions/index' import ImportMentionsInfoPanel from '~/components/mention/ImportMentions/ImportMentionsInfoPanel' import useEditMentionReducer from '~/components/mention/useEditMentionReducer' import useProjectContext from '~/components/projects/edit/useProjectContext' -import {getImpactForProject} from '~/utils/getProjects' +import {getMentionsForProject} from '~/utils/getProjects' import {cfgImpact as config} from './config' export default function ImportProjectImpact() { @@ -20,7 +21,7 @@ export default function ImportProjectImpact() { async function reloadImpact() { setLoading(true) - const data = await getImpactForProject({project: project.id, token: token, frontend: true}) + const data = await getMentionsForProject({project: project.id,table:'impact_for_project',token: token}) setMentions(data) setLoading(false) } diff --git a/frontend/components/projects/edit/impact/useImpactForProject.tsx b/frontend/components/projects/edit/impact/useImpactForProject.tsx index 9c0a41607..7377d10ec 100644 --- a/frontend/components/projects/edit/impact/useImpactForProject.tsx +++ b/frontend/components/projects/edit/impact/useImpactForProject.tsx @@ -1,11 +1,13 @@ // 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 {useEffect, useState} from 'react' -import {getImpactForProject} from '~/utils/getProjects' +import {getMentionsForProject} from '~/utils/getProjects' import {sortOnNumProp} from '~/utils/sortFn' import useEditMentionReducer from '~/components/mention/useEditMentionReducer' @@ -24,11 +26,10 @@ export default function useImpactForProject({project, token}: ImpactForProjectPr let abort = false async function getImpactFromApi() { setLoading(true) - // TODO! this request is made two times, investigate - const mentionsForProject = await getImpactForProject({ + const mentionsForProject = await getMentionsForProject({ project, - token, - frontend: true + table:'impact_for_project', + token }) if (mentionsForProject && abort === false) { const mentions:MentionItemProps[] = mentionsForProject.sort((a, b) => { diff --git a/frontend/components/projects/edit/output/EditProjectOutputIndex.test.tsx b/frontend/components/projects/edit/output/EditProjectOutputIndex.test.tsx index 0972b7270..14fdb428f 100644 --- a/frontend/components/projects/edit/output/EditProjectOutputIndex.test.tsx +++ b/frontend/components/projects/edit/output/EditProjectOutputIndex.test.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) @@ -22,7 +23,7 @@ import outputForProject from './__mocks__/outputForProject.json' // MOCK getOutputForProject const mockGetOutputForProject = jest.fn((props) => Promise.resolve(outputForProject)) jest.mock('~/utils/getProjects', () => ({ - getOutputForProject: jest.fn((props)=>mockGetOutputForProject(props)) + getMentionsForProject: jest.fn((props)=>mockGetOutputForProject(props)) })) // MOCK getMentionByDoiFromRsd const mockGetMentionByDoiFromRsd = jest.fn((props) => Promise.resolve([] as any)) diff --git a/frontend/components/projects/edit/output/ImportProjectOutput.tsx b/frontend/components/projects/edit/output/ImportProjectOutput.tsx index 084fe6c45..d9e7728ea 100644 --- a/frontend/components/projects/edit/output/ImportProjectOutput.tsx +++ b/frontend/components/projects/edit/output/ImportProjectOutput.tsx @@ -7,7 +7,7 @@ // SPDX-License-Identifier: Apache-2.0 import {useSession} from '~/auth' -import {getOutputForProject} from '~/utils/getProjects' +import {getMentionsForProject} from '~/utils/getProjects' import ImportMentions from '~/components/mention/ImportMentions/index' import ImportMentionsInfoPanel from '~/components/mention/ImportMentions/ImportMentionsInfoPanel' import useEditMentionReducer from '~/components/mention/useEditMentionReducer' @@ -21,7 +21,7 @@ export default function ImportProjectOutput() { async function reloadOutput() { setLoading(true) - const data = await getOutputForProject({project: project.id, token: token, frontend: true}) + const data = await getMentionsForProject({project: project.id,table:'output_for_project',token: token}) setMentions(data) setLoading(false) } diff --git a/frontend/components/projects/edit/output/useOutputForProject.tsx b/frontend/components/projects/edit/output/useOutputForProject.tsx index e1103841b..6aa48b8ab 100644 --- a/frontend/components/projects/edit/output/useOutputForProject.tsx +++ b/frontend/components/projects/edit/output/useOutputForProject.tsx @@ -1,12 +1,14 @@ // 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 {useEffect, useState} from 'react' import useEditMentionReducer from '~/components/mention/useEditMentionReducer' import {MentionItemProps} from '~/types/Mention' -import {getOutputForProject} from '~/utils/getProjects' +import {getMentionsForProject} from '~/utils/getProjects' import {sortOnNumProp} from '~/utils/sortFn' type OutputForProjectProps = { @@ -22,10 +24,10 @@ export default function useOutputForProject({project, token}: OutputForProjectPr let abort = false async function getImpact() { setLoading(true) - const mentions = await getOutputForProject({ + const mentions = await getMentionsForProject({ project, - token, - frontend: true + table:'output_for_project', + token }) const output:MentionItemProps[] = mentions.sort((a, b) => { // sort mentions on publication year, newest at the top @@ -41,12 +43,7 @@ export default function useOutputForProject({project, token}: OutputForProjectPr if (project && token && project!==loadedProject) { getImpact() } - // else { - // console.group('skip request useOutputForProject') - // console.log('project...', project) - // console.log('loadedProject...', loadedProject) - // console.groupEnd() - // } + return () => { abort = true } // we skip setMentions and setLoading methods in the deps to avoid loop // TODO! try wrapping methods of useEditMentionReducer in useCallback? diff --git a/frontend/components/projects/overview/list/ListImageWithGradientPlaceholder.tsx b/frontend/components/projects/overview/list/ListImageWithGradientPlaceholder.tsx new file mode 100644 index 000000000..376bce8c4 --- /dev/null +++ b/frontend/components/projects/overview/list/ListImageWithGradientPlaceholder.tsx @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import useValidateImageSrc from '~/utils/useValidateImageSrc' + +export default function ListImageWithGradientPlaceholder({imgSrc,alt}:{imgSrc:string|null, alt:string|null}) { + const validImg = useValidateImageSrc(imgSrc) + + // console.group('ListItemImageWithGradientPlaceholder') + // console.log('imgSrc...', imgSrc) + // console.log('validImg...', validImg) + // console.groupEnd() + + if (validImg === false || imgSrc === null){ + // return gradient square as placeholder + return ( +
+ ) + } + + return ( + {alt + ) +} diff --git a/frontend/components/projects/overview/list/ProjectListItemContent.tsx b/frontend/components/projects/overview/list/ProjectListItemContent.tsx index 0ff12d016..ea7b04683 100644 --- a/frontend/components/projects/overview/list/ProjectListItemContent.tsx +++ b/frontend/components/projects/overview/list/ProjectListItemContent.tsx @@ -7,8 +7,8 @@ import {JSX} from 'react' import {getImageUrl} from '~/utils/editImage' -import useValidateImageSrc from '~/utils/useValidateImageSrc' import ProjectMetrics from '../cards/ProjectMetrics' +import ListImageWithGradientPlaceholder from './ListImageWithGradientPlaceholder' type ProjectListItemProps = { title: string @@ -19,23 +19,14 @@ type ProjectListItemProps = { statusBanner?: JSX.Element } - export default function ProjectListItemContent(item: ProjectListItemProps) { const imgSrc = getImageUrl(item.image_id ?? null) - const validImg = useValidateImageSrc(imgSrc) return ( <> - {validImg ? - {`Cover - : -
- } +
diff --git a/frontend/components/software/edit/mentions/ImportSoftwareMentions.tsx b/frontend/components/software/edit/mentions/ImportSoftwareMentions.tsx index af1089116..439d7ae95 100644 --- a/frontend/components/software/edit/mentions/ImportSoftwareMentions.tsx +++ b/frontend/components/software/edit/mentions/ImportSoftwareMentions.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Netherlands eScience Center @@ -20,7 +21,7 @@ export default function ImportSoftwareMentions() { async function reloadMentions() { setLoading(true) - const data = await getMentionsForSoftware({software: software.id, token: token, frontend: true}) + const data = await getMentionsForSoftware({software: software.id, token: token}) setMentions(data) setLoading(false) } diff --git a/frontend/components/software/edit/mentions/useMentionForSoftware.tsx b/frontend/components/software/edit/mentions/useMentionForSoftware.tsx index e23711d49..13d46cf53 100644 --- a/frontend/components/software/edit/mentions/useMentionForSoftware.tsx +++ b/frontend/components/software/edit/mentions/useMentionForSoftware.tsx @@ -1,6 +1,8 @@ // 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 @@ -20,13 +22,12 @@ export default function useMentionForSoftware({software, token}: MentionForSoftw useEffect(() => { let abort = false - async function getImpactFromApi() { + async function getMentionsFromApi() { setLoading(true) // TODO! this request is made two times, investigate const mentionsForSoftware = await getMentionsForSoftware({ software, - token, - frontend: true + token }) if (mentionsForSoftware && abort === false) { // debugger @@ -37,7 +38,7 @@ export default function useMentionForSoftware({software, token}: MentionForSoftw } if (software && token && software !== loadedSoftware) { - getImpactFromApi() + getMentionsFromApi() } return () => { abort = true } diff --git a/frontend/components/software/overview/cards/SoftwareMasonryCard.tsx b/frontend/components/software/overview/cards/SoftwareMasonryCard.tsx index 088dadf54..e3189c117 100644 --- a/frontend/components/software/overview/cards/SoftwareMasonryCard.tsx +++ b/frontend/components/software/overview/cards/SoftwareMasonryCard.tsx @@ -34,12 +34,16 @@ export default function SoftwareMasonryCard({item}:SoftwareCardProps){ className="hover:text-inherit">
{/* Cover image, show only if valid image link */} - { - validImg && + { validImg === false ? null + : {`Cover } {/* Card content */} diff --git a/frontend/components/software/overview/highlights/HighlightsCard.tsx b/frontend/components/software/overview/highlights/HighlightsCard.tsx index ec2a361e1..fee0dad8a 100644 --- a/frontend/components/software/overview/highlights/HighlightsCard.tsx +++ b/frontend/components/software/overview/highlights/HighlightsCard.tsx @@ -37,6 +37,11 @@ export default function HighlightsCard(item: HighlightsCardProps) { const visibleNumberOfKeywords: number = 3 const visibleNumberOfProgLang: number = 3 + // console.group('HighlightsCard') + // console.log('imgSrc...', imgSrc) + // console.log('validImg...', validImg) + // console.groupEnd() + return (
- {/* Cover image, show only if valid image link */} - { - validImg && + {/* + Cover image, show only if valid image link! To avoid the layout shift we + flip the logic to hide the image only when the image link is not valid. + */} + { validImg === false ? null + : {`Cover } + {/* Card content */} -
+
{ // card size + margin const cardMovement: number = 680 const divRef =useRef(null) - const [distance, setDistance] = useState(0) - + const [distance, setDistance] = useState() // Keep track of the current scroll position of the carousel. const [scrollPosition, setScrollPosition] = useState(0) const carousel = useRef(null) + // console.group('HighlightsCarousel') + // console.log('distance...',distance) + // console.log('scrollPosition...', scrollPosition) + // console.groupEnd() + useEffect(() => { const calculateDistance = () => { const rect = divRef.current?.getBoundingClientRect() @@ -53,16 +57,24 @@ export const HighlightsCarousel = ({items=[]}: {items:SoftwareHighlight[]}) => { setScrollPosition(event.target.scrollLeft) } + if (typeof distance === 'undefined'){ + // return only empty reference div if distance is not calculated and keep highlight space + return
+ } + return ( <> -
{/* Reference Div to center align card */} +
- {scrollPosition > 0 && } - - + className="group relative w-full overflow-x-visible sm:h-[22rem]" + > + {/* Left button */} + {scrollPosition > 0 ? + + : null + } {/* Carousel */}
{ style={{scrollbarWidth:'none',left:-scrollPosition, paddingLeft: distance +'px'}}> {/* render software card in the row direction */} {items.map(highlight => ( -
- )) - } + ))}
+ {/* Right button */} +
) diff --git a/frontend/components/software/overview/list/SoftwareListItemContent.tsx b/frontend/components/software/overview/list/SoftwareListItemContent.tsx index ab98d4828..02bc200f6 100644 --- a/frontend/components/software/overview/list/SoftwareListItemContent.tsx +++ b/frontend/components/software/overview/list/SoftwareListItemContent.tsx @@ -5,7 +5,7 @@ import {JSX} from 'react' import {getImageUrl} from '~/utils/editImage' -import useValidateImageSrc from '~/utils/useValidateImageSrc' +import ListImageWithGradientPlaceholder from '~/components/projects/overview/list/ListImageWithGradientPlaceholder' import SoftwareMetrics from '../cards/SoftwareMetrics' type SoftwareOverviewListItemProps = { @@ -27,20 +27,13 @@ type SoftwareOverviewListItemProps = { export default function SoftwareListItemContent(item:SoftwareOverviewListItemProps) { const imgSrc = getImageUrl(item.image_id ?? null) - const validImg = useValidateImageSrc(imgSrc) + return ( <> - {validImg ? - {`Cover - : -
- } +
diff --git a/frontend/package.json b/frontend/package.json index b1f794c82..32fcf020e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "rsd-frontend", - "version": "2.0.0", + "version": "2.0.1", "private": true, "scripts": { "dev": "next dev", diff --git a/frontend/pages/api/fe/auth/index.ts b/frontend/pages/api/fe/auth/index.ts index a6351df92..5d0c19888 100644 --- a/frontend/pages/api/fe/auth/index.ts +++ b/frontend/pages/api/fe/auth/index.ts @@ -1,7 +1,8 @@ +// SPDX-FileCopyrightText: 2022 - 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2022 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 @@ -35,6 +36,9 @@ export type Provider = { type Data = Provider[] | ApiError +// cached list of providers +let loginProviders:Provider[] = [] + async function getRedirectInfo(provider: string) { // select provider switch (provider.toLocaleLowerCase()) { @@ -56,11 +60,9 @@ async function getRedirectInfo(provider: string) { } } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - try { +async function getProvidersInfo(){ + // only if we did not loaded info previously + if (loginProviders.length === 0){ // extract list of providers, default value surfconext const strProviders = process.env.RSD_AUTH_PROVIDERS || 'surfconext' // split providers to array on ; @@ -82,8 +84,25 @@ export default async function handler( info.push(item.value as Provider) } }) + // save response into cached variable + loginProviders = [ + ...info + ] + return loginProviders + } + // console.log("getProvidersInfo...cached...loginProviders") + return loginProviders +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + try { + // extract list of providers from .env file + const providers = await getProvidersInfo() // return only 'valid' providers - res.status(200).json(info as Provider[]) + res.status(200).json(providers) } catch (e: any) { logger(`api/fe/auth/index: ${e?.message}`, 'error') res.status(500).json({ diff --git a/frontend/pages/projects/[slug]/index.tsx b/frontend/pages/projects/[slug]/index.tsx index 25fe1c56a..cb1b4fb96 100644 --- a/frontend/pages/projects/[slug]/index.tsx +++ b/frontend/pages/projects/[slug]/index.tsx @@ -12,11 +12,11 @@ import {getAccountFromToken} from '~/auth/jwtUtils' import isMaintainerOfProject from '~/auth/permissions/isMaintainerOfProject' import logger from '~/utils/logger' import { - getLinksForProject, getImpactForProject, - getOutputForProject, getOrganisations, + getLinksForProject, getOrganisations, getProjectItem, getRelatedSoftwareForProject, getTeamForProject, getResearchDomainsForProject, - getKeywordsForProject, getRelatedProjectsForProject + getKeywordsForProject, getRelatedProjectsForProject, + getMentionsForProject } from '~/utils/getProjects' import { KeywordForProject, Project, ProjectLink, @@ -66,7 +66,7 @@ export default function ProjectPage(props: ProjectPageProps) { if (!project?.title){ return } - // console.log('ProjectPage...project...', project) + // console.log('ProjectPage...output...', output) return ( <> {/* Page Head meta tags */} @@ -148,26 +148,12 @@ export async function getServerSideProps(context:any) { // console.log('getServerSideProps...userInfo...', userInfo) const project = await getProjectItem({slug: params?.slug, token}) if (typeof project == 'undefined'){ - // returning this value - // triggers 404 page on frontend + // returning this value triggers 404 page on frontend return { notFound: true, } } // fetch all info about project in parallel based on project.id - const fetchData = [ - getOrganisations({project: project.id, token, frontend: false}), - getResearchDomainsForProject({project: project.id, token, frontend: false}), - getKeywordsForProject({project: project.id, token, frontend: false}), - getOutputForProject({project: project.id, token, frontend: false}), - getImpactForProject({project: project.id, token, frontend: false}), - getTeamForProject({project: project.id, token, frontend: false}), - getRelatedSoftwareForProject({project: project.id, token, frontend: false}), - getRelatedProjectsForProject({project: project.id, token, frontend: false}), - getLinksForProject({project: project.id, token, frontend: false}), - isMaintainerOfProject({slug, account:userInfo?.account, token, frontend: false}), - ] - const [ organisations, researchDomains, @@ -179,7 +165,20 @@ export async function getServerSideProps(context:any) { relatedProjects, links, isMaintainer - ] = await Promise.all(fetchData) + ] = await Promise.all([ + getOrganisations({project: project.id, token, frontend: false}), + getResearchDomainsForProject({project: project.id, token, frontend: false}), + getKeywordsForProject({project: project.id, token, frontend: false}), + // Output + getMentionsForProject({project: project.id, token, table:'output_for_project'}), + // Impact + getMentionsForProject({project: project.id, token, table:'impact_for_project'}), + getTeamForProject({project: project.id, token, frontend: false}), + getRelatedSoftwareForProject({project: project.id, token, frontend: false}), + getRelatedProjectsForProject({project: project.id, token, frontend: false}), + getLinksForProject({project: project.id, token, frontend: false}), + isMaintainerOfProject({slug, account:userInfo?.account, token, frontend: false}), + ]) // console.log("getServerSideProps...project...", project) return { diff --git a/frontend/pages/software/[slug]/index.tsx b/frontend/pages/software/[slug]/index.tsx index 0ff244b68..b06ec18b9 100644 --- a/frontend/pages/software/[slug]/index.tsx +++ b/frontend/pages/software/[slug]/index.tsx @@ -79,7 +79,6 @@ interface SoftwareIndexData extends ScriptProps{ } export default function SoftwareIndexPage(props:SoftwareIndexData) { - const [resolvedUrl, setResolvedUrl] = useState('') const [author, setAuthor] = useState('') // extract data from props const { @@ -90,11 +89,6 @@ export default function SoftwareIndexPage(props:SoftwareIndexData) { slug, organisations } = props - useEffect(() => { - if (typeof location != 'undefined') { - setResolvedUrl(location.href) - } - }, []) useEffect(() => { const contact = contributors.filter(item => item.is_contact_person) if (contact.length > 0) { @@ -106,7 +100,7 @@ export default function SoftwareIndexPage(props:SoftwareIndexData) { if (!software?.brand_name){ return } - // console.log('SoftwareIndexPage...releases...', releases) + // console.log('SoftwareIndexPage...mentions...', mentions) return ( <> {/* Page Head meta tags */} @@ -218,7 +212,20 @@ export async function getServerSideProps(context:GetServerSidePropsContext) { } } // fetch all info about software in parallel based on software.id - const fetchData = [ + const [ + releases, + keywords, + licenseInfo, + repositoryInfo, + softwareIntroCounts, + mentions, + testimonials, + contributors, + relatedSoftware, + relatedProjects, + isMaintainer, + organisations + ] = await Promise.all([ // software versions info getReleasesForSoftware(software.id,token), // keywords @@ -230,7 +237,7 @@ export async function getServerSideProps(context:GetServerSidePropsContext) { // softwareMentionCounts getContributorMentionCount(software.id,token), // mentions - getMentionsForSoftware({software: software.id, frontend: false, token}), + getMentionsForSoftware({software:software.id,token}), // testimonials getTestimonialsForSoftware({software:software.id,frontend: false,token}), // contributors @@ -243,22 +250,7 @@ export async function getServerSideProps(context:GetServerSidePropsContext) { isMaintainerOfSoftware({slug, account:userInfo?.account, token, frontend: false}), // get organisations getParticipatingOrganisations({software:software.id,frontend:false,token}) - ] - const [ - releases, - keywords, - licenseInfo, - repositoryInfo, - softwareIntroCounts, - mentions, - testimonials, - contributors, - relatedSoftware, - relatedProjects, - isMaintainer, - organisations - ] = await Promise.all(fetchData) - + ]) // pass data to page component as props return { props: { diff --git a/frontend/utils/editMentions.ts b/frontend/utils/editMentions.ts index 52483bbd8..1fb242f49 100644 --- a/frontend/utils/editMentions.ts +++ b/frontend/utils/editMentions.ts @@ -2,43 +2,37 @@ // 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 import { - MentionByType, - MentionItemProps, MentionForSoftware, + MentionByType, MentionItemProps, mentionColumns, MentionTypeKeys } from '../types/Mention' -import {createJsonHeaders, extractReturnMessage} from './fetchHelpers' +import {createJsonHeaders, extractReturnMessage, getBaseUrl} from './fetchHelpers' import {getMentionByDoi} from './getDOI' import logger from './logger' -export async function getMentionsForSoftware({software,token,frontend}:{software: string, token?: string,frontend?:boolean}) { +export async function getMentionsForSoftware({software,token}:{software: string, token?: string}) { try { - // the content is order by type ascending - const query = `mention?select=${mentionColumns},mention_for_software!inner(software)&mention_for_software.software=eq.${software}&order=mention_type.asc` - let url = `${process.env.POSTGREST_URL}/${query}` - if (frontend) { - url = `/api/v1/${query}` - } - + // the content is ordered by type ascending + const query = `software?id=eq.${software}&select=id,slug,mention(${mentionColumns})&mention.order=mention_type.asc` + // construct url + const url = `${getBaseUrl()}/${query}` + // make request const resp = await fetch(url, { method: 'GET', - headers: createJsonHeaders(token) + headers: { + ...createJsonHeaders(token), + // request single object item + 'Accept': 'application/vnd.pgrst.object+json' + } }) if (resp.status === 200) { - const data: MentionForSoftware[] = await resp.json() - // convert to MentionItem - const mentions: MentionItemProps[] = data.map(item => { - if (item?.mention_for_software) { - // remove mention_for_software - // because POST/PATCH on mention table - // requires only mention table props - delete item.mention_for_software - } - return item - }) + const json = await resp.json() + // extract mentions from software object + const mentions: MentionItemProps[] = json?.mention ?? [] return mentions } logger(`getMentionsForSoftware: [${resp.status}] [${url}]`, 'error') diff --git a/frontend/utils/getProjects.ts b/frontend/utils/getProjects.ts index 99c7f784b..f029cffa6 100644 --- a/frontend/utils/getProjects.ts +++ b/frontend/utils/getProjects.ts @@ -7,7 +7,7 @@ import {OrganisationRole} from '~/types/Organisation' import {TeamMemberProps} from '~/types/Contributor' -import {mentionColumns, MentionForProject, MentionItemProps} from '~/types/Mention' +import {mentionColumns, MentionItemProps} from '~/types/Mention' import { KeywordForProject, OrganisationsOfProject, Project, @@ -240,72 +240,33 @@ export async function getLinksForProject({project, token, frontend = false}: } } -export async function getOutputForProject({project, token, frontend}: - {project: string, token?: string, frontend?: boolean}) { +export async function getMentionsForProject({project, token, table}: + {project: string, token?: string, table: 'output_for_project'|'impact_for_project'}) { try { // build query url - const query = `mention?select=${mentionColumns},output_for_project!inner(project)&output_for_project.project=eq.${project}&order=mention_type.asc` - // base url - let url = `${process.env.POSTGREST_URL}/${query}` - if (frontend) { - url = `/api/v1/${query}` - } - + const query = `project?id=eq.${project}&select=id,slug,mention!${table}(${mentionColumns})&mention.order=mention_type.asc` + // construct url + const url = `${getBaseUrl()}/${query}` + // make request const resp = await fetch(url, { method: 'GET', - headers: createJsonHeaders(token) - }) - if (resp.status === 200) { - const data: MentionForProject[] = await resp.json() - // cover to plain mention - const mentions: MentionItemProps[] = data.map(item => { - if (item?.output_for_project) { - delete item.output_for_project - } - return item - }) - return mentions - } - logger(`getOutputForProject: [${resp.status}] [${url}]`, 'error') - // query not found - return [] - } catch (e: any) { - logger(`getOutputForProject: ${e?.message}`, 'error') - return [] - } -} - -export async function getImpactForProject({project, token, frontend}: - { project: string, token?: string, frontend?: boolean }) { - try { - // build query url - const query = `mention?select=${mentionColumns},impact_for_project!inner(project)&impact_for_project.project=eq.${project}&order=mention_type.asc` - // base url - let url = `${process.env.POSTGREST_URL}/${query}` - if (frontend) { - url = `/api/v1/${query}` - } - - const resp = await fetch(url, { - method: 'GET', - headers: createJsonHeaders(token) + headers: { + ...createJsonHeaders(token), + // request single object item + 'Accept': 'application/vnd.pgrst.object+json' + } }) if (resp.status === 200) { - const data: MentionForProject[] = await resp.json() - // cover to plain mention - const mentions: MentionItemProps[] = data.map(item => { - if (item?.impact_for_project) { - delete item.impact_for_project - } - return item - }) + const json = await resp.json() + // extract mentions from project object + const mentions: MentionItemProps[] = json?.mention ?? [] return mentions } - logger(`getImpactForProject: [${resp.status}] [${url}]`, 'error') + logger(`getMentionsForProject: [${resp.status}] [${url}]`, 'error') // query not found return [] } catch (e: any) { - logger(`getImpactForProject: ${e?.message}`, 'error') + logger(`getMentionsForProject: ${e?.message}`, 'error') return [] } }