Skip to content

Commit

Permalink
refactor: split related topics into related software and projects sec…
Browse files Browse the repository at this point in the history
…tions on software and project edit pages
  • Loading branch information
dmijatovic committed Sep 19, 2023
1 parent 26273ad commit 198c4cf
Show file tree
Hide file tree
Showing 30 changed files with 441 additions and 617 deletions.
19 changes: 15 additions & 4 deletions frontend/components/projects/edit/editProjectPages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import OutboundIcon from '@mui/icons-material/Outbound'
import AccessibilityNewIcon from '@mui/icons-material/AccessibilityNew'
import ShareIcon from '@mui/icons-material/Share'
import PersonAddIcon from '@mui/icons-material/PersonAdd'
import TerminalIcon from '@mui/icons-material/Terminal'
import ContentLoader from '~/components/layout/ContentLoader'


Expand Down Expand Up @@ -42,7 +43,10 @@ const ProjectImpact = dynamic(() => import('./impact'),{
const ProjectOutput = dynamic(() => import('./output'),{
loading: ()=><ContentLoader />
})
const RelatedTopics = dynamic(() => import('./related'),{
const RelatedProjects = dynamic(() => import('./related-projects'),{
loading: ()=><ContentLoader />
})
const RelatedSoftware = dynamic(() => import('./related-software'),{
loading: ()=><ContentLoader />
})
const ProjectMaintainers = dynamic(() => import('./maintainers'),{
Expand Down Expand Up @@ -94,10 +98,17 @@ export const editProjectPage: EditProjectPageProps[] = [
status: 'Optional information'
},
{
id: 'related-topics',
label: 'Related topics',
id: 'related-projects',
label: 'Related projects',
icon: <ShareIcon />,
render: () => <RelatedTopics />,
render: () => <RelatedProjects />,
status: 'Optional information'
},
{
id: 'related-software',
label: 'Related software',
icon: <TerminalIcon />,
render: () => <RelatedSoftware />,
status: 'Optional information'
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all)
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 dv4all
//
// SPDX-License-Identifier: Apache-2.0
Expand All @@ -14,15 +16,12 @@ import RelatedItems from './index'
// MOCKS
import editProjectState from '../__mocks__/editProjectState'
import mockRelatedProjects from './__mocks__/relatedProjectsForProject.json'
import mockRelatedSoftware from './__mocks__/relatedSoftwareForProject.json'

// MOCK getRelatedProjectsForProject, getRelatedSoftwareForProject
const mockGetRelatedProjectsForProject = jest.fn(props => Promise.resolve([] as any))
const mockGetRelatedSoftwareForProject = jest.fn(props => Promise.resolve([] as any))
const mockSearchForRelatedProjectByTitle = jest.fn(props => Promise.resolve([] as any))
jest.mock('~/utils/getProjects', () => ({
getRelatedProjectsForProject: jest.fn(props => mockGetRelatedProjectsForProject(props)),
getRelatedSoftwareForProject: jest.fn(props => mockGetRelatedSoftwareForProject(props)),
searchForRelatedProjectByTitle: jest.fn(props => mockSearchForRelatedProjectByTitle(props)),
}))

Expand All @@ -44,15 +43,15 @@ jest.mock('~/utils/editRelatedSoftware', () => ({
}))


describe('frontend/components/projects/edit/related/index.tsx', () => {
describe('frontend/components/projects/edit/related-projects/index.tsx', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('renders related project and software items', async() => {
it('renders related project items', async() => {
// return mocked values
mockGetRelatedProjectsForProject.mockResolvedValueOnce(mockRelatedProjects)
mockGetRelatedSoftwareForProject.mockResolvedValueOnce(mockRelatedSoftware)
// mockGetRelatedSoftwareForProject.mockResolvedValueOnce(mockRelatedSoftware)

// render
render(
Expand All @@ -67,9 +66,6 @@ describe('frontend/components/projects/edit/related/index.tsx', () => {
const relatedProjects = await screen.findAllByTestId('related-project-item')
expect(relatedProjects.length).toEqual(mockRelatedProjects.length)


const relatedSoftware = await screen.findAllByTestId('related-software-item')
expect(relatedSoftware.length).toEqual(mockRelatedSoftware.length)
})

it('can add related project to project', async() => {
Expand All @@ -80,7 +76,6 @@ describe('frontend/components/projects/edit/related/index.tsx', () => {
]
// return mocked values
mockGetRelatedProjectsForProject.mockResolvedValueOnce([])
mockGetRelatedSoftwareForProject.mockResolvedValueOnce([])
mockSearchForRelatedProjectByTitle.mockResolvedValueOnce(relatedProjectsFound)
mockAddRelatedProject.mockResolvedValueOnce({
status: 200,
Expand Down Expand Up @@ -122,61 +117,9 @@ describe('frontend/components/projects/edit/related/index.tsx', () => {
expect(relatedProjects.length).toEqual(1)
})

it('can add related software to project', async() => {
const searchFor = 'Search for software'
const relatedSoftwareFound = [
{id:'test-id-1',slug:'test-slug-1',brand_name:'Test title 1',short_statement:'Test subtitle 1',status: 'approved'},
{id:'test-id-2',slug:'test-slug-2',brand_name:'Test title 2',short_statement:'Test subtitle 2',status:'approved'}
]
// return mocked values
mockGetRelatedProjectsForProject.mockResolvedValueOnce([])
mockGetRelatedSoftwareForProject.mockResolvedValueOnce([])
mockSearchForRelatedSoftware.mockResolvedValueOnce(relatedSoftwareFound)
mockAddRelatedSoftware.mockResolvedValueOnce({
status: 200,
message: 'OK'
})

// render
render(
<WithAppContext options={{session: mockSession}}>
<WithProjectContext state={editProjectState}>
<RelatedItems />
</WithProjectContext>
</WithAppContext>
)

// search for related project
const findRelated = screen.getByTestId('find-related-software')
const search = within(findRelated).getByRole('combobox')
fireEvent.change(search, {target: {value: searchFor}})

// validate mocked options returned
const options = await screen.findAllByTestId('related-software-option')
expect(options.length).toEqual(relatedSoftwareFound.length)

// add first item
fireEvent.click(options[0])

// validate api calls
expect(mockAddRelatedSoftware).toBeCalledTimes(1)
expect(mockAddRelatedSoftware).toBeCalledWith({
'project': editProjectState.project.id,
'software': relatedSoftwareFound[0].id,
'status': 'approved',
'token': mockSession.token,
})

// validate 1 item added to list
const relatedSoftware = await screen.findAllByTestId('related-software-item')
expect(relatedSoftware.length).toEqual(1)
})


it('can remove related project for project', async() => {
// return mocked values
mockGetRelatedProjectsForProject.mockResolvedValueOnce(mockRelatedProjects)
mockGetRelatedSoftwareForProject.mockResolvedValueOnce([])
mockDeleteRelatedProject
.mockResolvedValueOnce({
status: 200,
Expand Down Expand Up @@ -222,44 +165,4 @@ describe('frontend/components/projects/edit/related/index.tsx', () => {
})
})

it('can remove related software', async() => {
// return mocked values
mockGetRelatedProjectsForProject.mockResolvedValueOnce([])
mockGetRelatedSoftwareForProject.mockResolvedValueOnce(mockRelatedSoftware)
mockDeleteRelatedSoftware.mockResolvedValueOnce({
status: 200,
message:'OK'
})
// render
render(
<WithAppContext options={{session: mockSession}}>
<WithProjectContext state={editProjectState}>
<RelatedItems />
</WithProjectContext>
</WithAppContext>
)

// get list
const relatedSoftware = await screen.findAllByTestId('related-software-item')
// get delete btn of first item
const delBtn = within(relatedSoftware[0]).getByRole('button', {
name: 'delete'
})
// delete item
fireEvent.click(delBtn)

await waitFor(() => {
expect(mockDeleteRelatedSoftware).toBeCalledTimes(1)
expect(mockDeleteRelatedSoftware).toBeCalledWith({
'project': editProjectState.project.id,
'software': mockRelatedSoftware[0].id,
'token': mockSession.token,
})
// validate remaining items
const remainedSoftware = screen.getAllByTestId('related-software-item')
expect(remainedSoftware.length).toEqual(mockRelatedSoftware.length - 1)
})
})


})
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2022 - 2023 dv4all
// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) (dv4all)
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all)
SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all)
SPDX-FileCopyrightText: 2023 Netherlands eScience Center
SPDX-FileCopyrightText: 2023 dv4all

SPDX-License-Identifier: Apache-2.0
18 changes: 18 additions & 0 deletions frontend/components/projects/edit/related-projects/config.ts
Original file line number Diff line number Diff line change
@@ -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

export const relatedProject = {
title: 'Related projects',
subtitle: 'This project is related to these projects registered in RSD',
findTitle: 'Find project',
label: 'Find related projects in RSD',
help: 'Start typing for suggestions',
validation: {
//custom validation rule, not in used by react-hook-form
minLength: 1,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@
import {useEffect, useState} from 'react'

import {useSession} from '~/auth'
import {cfgRelatedItems as config} from './config'
import {getRelatedProjectsForProject} from '~/utils/getProjects'
import {addRelatedProject, deleteRelatedProject} from '~/utils/editProject'
import useSnackbar from '~/components/snackbar/useSnackbar'
import {sortOnStrProp} from '~/utils/sortFn'
import {extractErrorMessages} from '~/utils/fetchHelpers'
import {ProjectStatusKey, RelatedProjectForProject, SearchProject} from '~/types/Project'
import {Status} from '~/types/Organisation'
import useSnackbar from '~/components/snackbar/useSnackbar'
import EditSectionTitle from '~/components/layout/EditSectionTitle'
import EditSection from '~/components/layout/EditSection'
import {relatedProject as config} from './config'
import FindRelatedProject from './FindRelatedProject'
import useProjectContext from '../useProjectContext'
import RelatedProjectList from './RelatedProjectList'
import EditSectionTitle from '~/components/layout/EditSectionTitle'
import {Status} from '~/types/Organisation'
import {extractErrorMessages} from '~/utils/fetchHelpers'

export default function RelatedProjectsForProject() {
const {token} = useSession()
Expand Down Expand Up @@ -124,35 +125,41 @@ export default function RelatedProjectsForProject() {
}

return (
<>
<EditSectionTitle
title={config.relatedProject.title}
subtitle={config.relatedProject.subtitle}
>
{/* add count to title */}
{relatedProject && relatedProject.length > 0 ?
<div className="pl-4 text-2xl">{relatedProject.length}</div>
: null
}
</EditSectionTitle>
<FindRelatedProject
project={project.id}
token={token}
config={{
freeSolo: false,
minLength: config.relatedProject.validation.minLength,
label: config.relatedProject.label,
help: config.relatedProject.help,
reset: true
}}
onAdd={onAdd}
/>
<div className="py-8">
<EditSection className="flex-1 md:flex md:flex-col-reverse md:justify-end xl:grid xl:grid-cols-[3fr,2fr] xl:px-0 xl:gap-[3rem]">
<section className="py-4">
<EditSectionTitle
title={config.title}
// subtitle={config.subtitle}
>
{/* add count to title */}
{relatedProject && relatedProject.length > 0 ?
<div className="pl-4 text-2xl">{relatedProject.length}</div>
: null
}
</EditSectionTitle>
<RelatedProjectList
projects={relatedProject}
onRemove={onRemove}
/>
</div>
</>
</section>
<section className="py-4">
<EditSectionTitle
title={config.findTitle}
// subtitle={config.label}
/>
<FindRelatedProject
project={project.id}
token={token}
config={{
freeSolo: false,
minLength: config.validation.minLength,
label: config.label,
help: config.help,
reset: true
}}
onAdd={onAdd}
/>
</section>
</EditSection>
)
}
Loading

0 comments on commit 198c4cf

Please sign in to comment.