Skip to content

Commit

Permalink
feat: admin can delete software from RSD
Browse files Browse the repository at this point in the history
feat: admin can delete organisation from RSD
feat: admin can delete community
feat: admin can delete project
  • Loading branch information
dmijatovic committed Aug 30, 2024
1 parent fa38eb8 commit 85dc89d
Show file tree
Hide file tree
Showing 33 changed files with 1,071 additions and 120 deletions.
1 change: 1 addition & 0 deletions database/104-software-views.sql
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ BEGIN
DELETE FROM mention_for_software WHERE mention_for_software.software = delete_software.id;
DELETE FROM package_manager WHERE package_manager.software = delete_software.id;
DELETE FROM reference_paper_for_software WHERE reference_paper_for_software.software = delete_software.id;
DELETE FROM release_version WHERE release_version.release_id = delete_software.id;
DELETE FROM release WHERE release.software = delete_software.id;
DELETE FROM repository_url WHERE repository_url.software = delete_software.id;
DELETE FROM software_for_community WHERE software_for_community.software = delete_software.id;
Expand Down
14 changes: 14 additions & 0 deletions frontend/components/admin/AdminNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import BugReportIcon from '@mui/icons-material/BugReport'
import ReceiptLongIcon from '@mui/icons-material/ReceiptLong'
import Diversity3Icon from '@mui/icons-material/Diversity3'
import CategoryIcon from '@mui/icons-material/Category'
import TerminalIcon from '@mui/icons-material/Terminal'
import ListAltIcon from '@mui/icons-material/ListAlt'

import {editMenuItemButtonSx} from '~/config/menuItems'

Expand Down Expand Up @@ -62,6 +64,18 @@ export const adminPages = {
icon: <AccountCircleIcon />,
path: '/admin/rsd-contributors',
},
software: {
title: 'Software',
subtitle: '',
icon: <TerminalIcon />,
path: '/admin/software',
},
projects: {
title: 'Projects',
subtitle: '',
icon: <ListAltIcon />,
path: '/admin/projects',
},
organisations: {
title: 'Organisations',
subtitle: '',
Expand Down
50 changes: 21 additions & 29 deletions frontend/components/admin/communities/CommunityList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@
import {useState} from 'react'
import List from '@mui/material/List'

import ConfirmDeleteModal from '~/components/layout/ConfirmDeleteModal'
import ContentLoader from '~/components/layout/ContentLoader'
import {CommunityListProps} from '~/components/communities/apiCommunities'
import CommunityListItem from './CommunityListItem'
import NoCommunityAlert from './NoCommunityAlert'
import RemoveCommunityModal, {CommunityModalProps} from './RemoveCommunityModal'

type DeleteOrganisationModal = {
open: boolean,
item?: CommunityListProps
}

type OrganisationsAdminListProps = {
communities: CommunityListProps[]
Expand All @@ -27,7 +23,7 @@ type OrganisationsAdminListProps = {
}

export default function CommunityList({communities,loading,page,onDeleteItem}:OrganisationsAdminListProps) {
const [modal, setModal] = useState<DeleteOrganisationModal>({
const [modal, setModal] = useState<CommunityModalProps>({
open: false
})

Expand Down Expand Up @@ -55,29 +51,25 @@ export default function CommunityList({communities,loading,page,onDeleteItem}:Or
})
}
</List>
<ConfirmDeleteModal
open={modal.open}
title="Remove community"
body={
<>
<p>
Are you sure you want to delete community <strong>{modal?.item?.name}</strong>?
</p>
</>
}
onCancel={() => {
setModal({
open: false
})
}}
onDelete={() => {
// call remove method if id present
if (modal.item && modal.item?.id) onDeleteItem(modal.item?.id,modal.item?.logo_id)
setModal({
open: false
})
}}
/>
{
modal.open ?
<RemoveCommunityModal
item = {modal.item}
onCancel={() => {
setModal({
open: false
})
}}
onDelete={() => {
// call remove method if id present
if (modal.item && modal.item?.id) onDeleteItem(modal.item?.id,modal.item?.logo_id)
setModal({
open: false
})
}}
/>
: null
}
</>
)
}
16 changes: 1 addition & 15 deletions frontend/components/admin/communities/CommunityListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
//
// SPDX-License-Identifier: Apache-2.0

import {useRouter} from 'next/router'

import IconButton from '@mui/material/IconButton'
import ListItem from '@mui/material/ListItem'
import DeleteIcon from '@mui/icons-material/Delete'
Expand All @@ -25,18 +23,6 @@ type OrganisationItemProps = {
}

export default function CommunityListItem({item, onDelete}: OrganisationItemProps) {

function isDeletedDisabled(){
// if any of software in community
if (item.software_cnt && item?.software_cnt > 0 ) return true
if (item.rejected_cnt && item?.rejected_cnt > 0 ) return true
if (item.pending_cnt && item?.pending_cnt > 0 ) return true
// if keywords are defined
if (item.keywords && item.keywords?.length > 0 ) return true
// otherwise it can be deleted
return false
}

return (
<ListItem
data-testid="admin-community-item"
Expand All @@ -53,7 +39,7 @@ export default function CommunityListItem({item, onDelete}: OrganisationItemProp
<EditIcon />
</IconButton>
<IconButton
disabled={isDeletedDisabled()}
// disabled={isDeletedDisabled()}
edge="end"
aria-label="delete"
onClick={() => {
Expand Down
4 changes: 1 addition & 3 deletions frontend/components/admin/communities/NoCommunityAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import AlertTitle from '@mui/material/AlertTitle'

export default function NoCommunityAlert() {
return (
<Alert severity="warning"
sx={{marginTop: '0.5rem'}}
>
<Alert severity="warning">
<AlertTitle sx={{fontWeight:500}}>No communities defined</AlertTitle>
To add community to RSD <strong>use Add button on the right</strong>.
</Alert>
Expand Down
59 changes: 59 additions & 0 deletions frontend/components/admin/communities/RemoveCommunityModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

import {useState} from 'react'
import TextField from '@mui/material/TextField'

import ConfirmDeleteModal from '~/components/layout/ConfirmDeleteModal'
import {CommunityListProps} from '~/components/communities/apiCommunities'

export type CommunityModalProps={
open: boolean
item?: CommunityListProps
}

type RemoveCommunityModalProps={
item?: CommunityListProps
onCancel: ()=>void
onDelete: ()=>void
}

export default function RemoveCommunityModal({item,onCancel,onDelete}:RemoveCommunityModalProps) {
const [confirmation,setConfirmation] = useState<string>('')
return (
<ConfirmDeleteModal
open={true}
title="Delete community"
removeDisabled={item?.name!==confirmation}
body={
<>
<p>
Are you sure you want to delete this community?
</p>
<p className="py-4">
<strong>{item?.name}</strong>
</p>
<TextField
label="Name of the community to delete"
helperText={
<span>Type the community name exactly as shown above.</span>
}
value = {confirmation}
onChange={({target})=>setConfirmation(target.value)}
sx={{
width: '100%',
margin: '1rem 0rem'
}}
/>
<p className="text-error text-base">
This will remove community from all related RSD entries too!
</p>
</>
}
onCancel={onCancel}
onDelete={onDelete}
/>
)
}
12 changes: 7 additions & 5 deletions frontend/components/admin/communities/apiCommunities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//
// SPDX-License-Identifier: Apache-2.0

import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers'
import logger from '~/utils/logger'
import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers'

export type Community={
id?:string,
Expand Down Expand Up @@ -76,14 +76,16 @@ export async function addCommunity({data,token}:{data:Community,token:string}) {

export async function deleteCommunityById({id,token}:{id:string,token:string}) {
try {
const query = `community?id=eq.${id}`
const url = `/api/v1/${query}`
const url = `${getBaseUrl()}/rpc/delete_community`

const resp = await fetch(url,{
method: 'DELETE',
method: 'POST',
headers: {
...createJsonHeaders(token)
}
},
body: JSON.stringify({
id
})
})
return extractReturnMessage(resp, '')
} catch (e: any) {
Expand Down
14 changes: 8 additions & 6 deletions frontend/components/admin/communities/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ export default function AdminCommunities() {
<Searchbox />
<Pagination />
</div>
<CommunityList
communities={communities}
loading={loading}
page={page}
onDeleteItem={deleteCommunity}
/>
<div className="pt-6">
<CommunityList
communities={communities}
loading={loading}
page={page}
onDeleteItem={deleteCommunity}
/>
</div>
</div>
<div className="flex items-start justify-end">
<Button
Expand Down
15 changes: 9 additions & 6 deletions frontend/components/admin/communities/useAdminCommunities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
//
// SPDX-License-Identifier: Apache-2.0

import {useCallback, useEffect, useState} from 'react'

import {useSession} from '~/auth'
import useSnackbar from '~/components/snackbar/useSnackbar'
import usePaginationWithSearch from '~/utils/usePaginationWithSearch'
import {addCommunity as addCommunityToRsd, deleteCommunityById} from './apiCommunities'
import {useCallback, useEffect, useState} from 'react'
import {EditCommunityProps} from './AddCommunityModal'
import {deleteImage, upsertImage} from '~/utils/editImage'
import useSnackbar from '~/components/snackbar/useSnackbar'
import {CommunityListProps, getCommunityList} from '~/components/communities/apiCommunities'
import {addCommunity as addCommunityToRsd, deleteCommunityById} from './apiCommunities'
import {EditCommunityProps} from './AddCommunityModal'

export function useAdminCommunities(){
const {token} = useSession()
const {showErrorMessage} = useSnackbar()
const {showErrorMessage, showSuccessMessage} = useSnackbar()
const {searchFor, page, rows, setCount} = usePaginationWithSearch('Find community by name')
const [communities, setCommunities] = useState<CommunityListProps[]>([])
const [loading, setLoading] = useState(true)

const loadCommunities = useCallback(async() => {
setLoading(true)
// setLoading(true)
const {communities, count} = await getCommunityList({
token,
searchFor,
Expand Down Expand Up @@ -105,6 +106,8 @@ export function useAdminCommunities(){
}
// reload list
loadCommunities()
// confirm deletion
showSuccessMessage('Community deleted from RSD')
}
// we do not include showErrorMessage in order to avoid loop
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -48,7 +48,7 @@ it('renders organisation item', () => {
})

it('can DELETE organisation with zero software and projects', () => {
// ensuren zero counts
// ensure zero counts
mockOrganisationItem.software_cnt = 0
mockOrganisationItem.project_cnt=0

Expand All @@ -69,10 +69,10 @@ it('can DELETE organisation with zero software and projects', () => {
// screen.debug(deleteBtn)
})

it('canNOT DELETE organisation with software or projects', () => {
it('can DELETE organisation with software or projects', () => {
// ensuren zero counts
mockOrganisationItem.software_cnt = 1
mockOrganisationItem.project_cnt = 0
mockOrganisationItem.project_cnt = 1

render(
<OrganisationItem
Expand All @@ -83,11 +83,11 @@ it('canNOT DELETE organisation with software or projects', () => {

// delete icon button
const deleteBtn = screen.getByRole('button',{name:'delete'})
expect(deleteBtn).toBeDisabled()
// expect(deleteBtn).toBeDisabled()

// check calling
fireEvent.click(deleteBtn)
expect(mockOnDelete).toBeCalledTimes(0)
expect(mockOnDelete).toBeCalledTimes(1)

// screen.debug(deleteBtn)
})
Expand Down
10 changes: 4 additions & 6 deletions frontend/components/admin/organisations/OrganisationItem.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 dv4all
//
// SPDX-License-Identifier: Apache-2.0

import {useRouter} from 'next/router'

import IconButton from '@mui/material/IconButton'
import ListItem from '@mui/material/ListItem'
import DeleteIcon from '@mui/icons-material/Delete'
Expand All @@ -24,7 +22,6 @@ type OrganisationItemProps = {
}

export default function OrganisationItem({item, onDelete}: OrganisationItemProps) {
const router = useRouter()
return (
<ListItem
data-testid="admin-organisation-item"
Expand All @@ -41,7 +38,7 @@ export default function OrganisationItem({item, onDelete}: OrganisationItemProps
<EditIcon />
</IconButton>
<IconButton
disabled={item.software_cnt > 0 || item.project_cnt > 0}
// disabled={item.software_cnt > 0 || item.project_cnt > 0}
edge="end"
aria-label="delete"
onClick={() => {
Expand All @@ -55,6 +52,7 @@ export default function OrganisationItem({item, onDelete}: OrganisationItemProps
sx={{
// this makes space for buttons
paddingRight:'6.5rem',
paddingLeft: '0'
}}
>
<ListItemAvatar>
Expand Down
Loading

0 comments on commit 85dc89d

Please sign in to comment.