Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat. Add Option to delete multiple users #114

Merged
merged 6 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions nomad-front-end/src/containers/Users/Users.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { connect } from 'react-redux'
import { Button, Table, Drawer, Tag, Space, Pagination, Tooltip } from 'antd'
import { Button, Table, Drawer, Flex, Modal, Tag, Space, Pagination, Tooltip } from 'antd'
import { CheckCircleOutlined, StopOutlined } from '@ant-design/icons'
const {confirm} = Modal

import UserForm from '../../components/Forms/UserForm/UserForm'
import {
Expand All @@ -12,7 +13,8 @@ import {
toggleUserForm,
toggleActive,
fetchGroupList,
resetUserSearch
resetUserSearch,
usersDeleteHandler
} from '../../store/actions/index'
import { renderDataAccess } from '../../utils/tableUtils'

Expand All @@ -27,8 +29,10 @@ const Users = props => {
authToken,
showInactive,
grpList,
deleting,
searchUserValue,
resetUsrSearch
resetUsrSearch,
deleteUsers,
} = props

const formRef = useRef({})
Expand All @@ -39,7 +43,10 @@ const Users = props => {
const [pageSize, setPageSize] = useState(15)
const [currentPage, setCurrentPage] = useState(1)
const [filters, setFilters] = useState({})
const [sorter, setSorter] = useState({})
const [sorter, setSorter] = useState({});
const [selectedRows, setSelectedRows] = useState([])
const [actionDisabled, setActionDisabled] = useState(true)
const [submitConfirmed, setsubmitConfirmed] = useState(false)

//Hook to fetch users according using search params stored in the state
useEffect(() => {
Expand All @@ -60,6 +67,23 @@ const Users = props => {
fetchGrpList(authToken, showInactive)
}, [fetchGrpList, authToken, showInactive])

//FOR SELECTION & ACTION
useEffect(()=>{
//now enable the button once state has been changed, and atleast one row has been selected
if(selectedRows.length > 0 && actionDisabled){
setActionDisabled(false)
}
//proceed with the API request if confirmed
if(submitConfirmed){
deleteUsers(selectedRows, authToken, showInactive)
setsubmitConfirmed(false);
setSelectedRows([]);
setActionDisabled(true)
}

}, [selectedRows, submitConfirmed])


//Hook to set group filters in the local state.
useEffect(() => {
const grpFilters = grpList.map(grp => ({ text: grp.name, value: grp.id }))
Expand Down Expand Up @@ -207,10 +231,44 @@ const Users = props => {
)
}
]
const rowSelection = {
selectedRowKeys: selectedRows,
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRowKeys)
setActionDisabled(true)
},
getCheckboxProps: (record) => ({
disabled: record.name === 'Disabled User',
// Column configuration not to be checked
name: record.name,
}),
};
const confirmDeletion = () => {
confirm({
content: `are you sure you want to delete ${selectedRows.length} users ?`,
onOk() {
setsubmitConfirmed(true)
},
onCancel() {
},
okText: 'Delete',
okButtonProps: {danger: true}
});
}

return (
<div style={{ margin: '30px 20px 20px 20px' }}>
<div style={{float: 'left', padding: 4, borderRadius: 5, display: 'flex', gridAutoFlow: 'column', alignItems: 'center', justifyItems: 'center', gap: 10, padding: '10px 10px', width: '100%', margin: '5px 0', background: '#fafafa'}}>
<h1 style={{fontWeight: 900}}>Action</h1>
<br />
<Button loading={deleting} onClick={confirmDeletion} danger disabled={actionDisabled || deleting}>Delete</Button>
</div>
<Table
rowSelection={{
type: 'checkbox',
...rowSelection,
}}
rowKey={(record)=>(record._id)}
size='small'
dataSource={props.tabData}
columns={columns}
Expand Down Expand Up @@ -262,6 +320,7 @@ const mapStateToProps = state => {
tabData: state.users.usersTableData,
totalUsers: state.users.total,
tabLoading: state.users.tableIsLoading,
deleting: state.users.deleteInProgress,
authToken: state.auth.token,
usrDrawerVisible: state.users.showForm,
formEditing: state.users.editing,
Expand All @@ -282,7 +341,8 @@ const mapDispatchToProps = dispatch => {
updateUsrHandler: (formData, token) => dispatch(updateUser(formData, token)),
toggleActive: (id, token) => dispatch(toggleActive(id, token)),
fetchGrpList: (token, showInactive) => dispatch(fetchGroupList(token, showInactive)),
resetUsrSearch: () => dispatch(resetUserSearch())
resetUsrSearch: () => dispatch(resetUserSearch()),
deleteUsers: (users, token, showInactive)=>(dispatch(usersDeleteHandler(users, token, showInactive)))
}
}

Expand Down
2 changes: 1 addition & 1 deletion nomad-front-end/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import authReducer from './store/reducers/auth'
import dashReducer from './store/reducers/dashboard'
import instrumentsReducer from './store/reducers/instruments'
import errorHandlerReducer from './store/reducers/errorHandler'
import usersReducer from './store/reducers/users'
import usersReducer from './store/reducers/users.jsx'
import groupsReducer from './store/reducers/groups'
import expHistoryReducer from './store/reducers/expHistory'
import paramSetsReducer from './store/reducers/paramSets'
Expand Down
2 changes: 2 additions & 0 deletions nomad-front-end/src/store/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export const SEARCH_USER = 'SEARCH_USER'
export const FETCH_USER_LIST_SUCCESS = 'FETCH_USER_LIST_SUCCESS'
export const RESET_USER_LIST = 'RESET_USER_LIST'
export const RESET_USER_SEARCH = 'RESET_USER_SEARCH'
export const DELETE_USERS_START = 'DELETE_USERS_START'
export const DELETE_USERS_COMPLETED = 'DELETE_USERS_COMPLETED'

//GROUPS
export const FETCH_GROUPS_TABLE_START = 'FETCH_GROUPS_TABLE_START'
Expand Down
1 change: 1 addition & 0 deletions nomad-front-end/src/store/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export {
addUser,
updateUser,
toggleActive,
usersDeleteHandler,
toggleShowInactive,
searchUser,
resetUserList,
Expand Down
30 changes: 30 additions & 0 deletions nomad-front-end/src/store/actions/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,36 @@ export const addUserFailed = () => {
}
}

export const usersDeletionStart=() => {
return{
type: actionTypes.DELETE_USERS_START
}
}

export const usersDeletionCompletion = (response, success) => {
return{
type: actionTypes.DELETE_USERS_COMPLETED,
data: {response, success}
}
}

export const usersDeleteHandler = (users, token, showInactive) => {
const data = {notFound: 0, deleted: 0, inactivated: 0}
return dispatch => {
dispatch(usersDeletionStart());
axios
.post('/admin/users/delete-users', {users}, { headers: { Authorization: 'Bearer ' + token } })
.then(res=>{
let success = res.status == '200'
dispatch(usersDeletionCompletion(res.data, success));
dispatch(fetchUsers(token, {showInactive}))
})
.catch(err=>{
dispatch(usersDeletionCompletion('an error occured', false));
})
}
}

export const addUser = (formData, token) => {
return dispatch => {
dispatch(fetchUsersStart())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { addKey, updateTableSwitch } from '../../utils/tableUtils'
import * as actionTypes from '../actions/actionTypes'
import { message } from 'antd'

import { message, Modal } from 'antd'
const initialState = {
usersTableData: [],
userList: [],
Expand All @@ -13,7 +12,9 @@ const initialState = {
tableIsLoading: false,
showForm: false,
editing: false,
lastLoginOrder: undefined
lastLoginOrder: undefined,
deleteInProgress: false,
deleteSummary: {}
}

const reducer = (state = initialState, action) => {
Expand All @@ -24,6 +25,34 @@ const reducer = (state = initialState, action) => {
tableIsLoading: true
}

case actionTypes.DELETE_USERS_START:
return{
...state,
deleteInProgress: true
}

case actionTypes.DELETE_USERS_COMPLETED:
console.log(action.data)
const {response, success} = action.data
console.log(response)
const {notFoundUsers, deletedUsers, inactivatedUsers} = response
success ? Modal.info({title: 'Users Deleted', content: (
<>
<p>users deleted: <strong>{deletedUsers}</strong></p>
<p>users inactivated: <strong>{inactivatedUsers}</strong></p>
{
notFoundUsers > 0 ?
( <p><strong>{notFoundUsers}</strong> of the users in your selection were not found</p>) : ('')
}
</>
)}) : Modal.error({title: 'Action Failed', content: 'failed to delete the users ' + action.data});
return{
...state,
deleteInProgress: false
}



case actionTypes.FETCH_USERS_TABLE_SUCCESS:
const users = action.data.users.map(usr => {
usr.groupName = usr.group.groupName
Expand Down
45 changes: 45 additions & 0 deletions nomad-rest-api/controllers/admin/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import moment from 'moment'

import User from '../../models/user.js'
import Group from '../../models/group.js'
import experiment from '../../models/experiment.js'

export async function getUsers(req, res) {
//setting search parameters according to showInactive settings
Expand Down Expand Up @@ -150,6 +151,50 @@ export async function postUser(req, res) {
}
}

export async function deleteUsers(req, res) {
//look for errors
const errors = validationResult(req)
try{
if (!errors.isEmpty()) {
return res.status(422).send(errors)
}
//now begins the specific logic
const allUsers = req.body.users;
let deletedUsers = 0
let inactivatedUsers = 0
let notFoundUsers = 0
await Promise.all(allUsers.map(async userid => {
let user = await User.findById(userid)
console.log(user)
if(!user){
notFoundUsers++
}else {
//check experiments
let experiments = await experiment.find({
'user.id' : userid
});
if(experiments.length > 0){
//he has some experinemts
inactivatedUsers++
user.isActive = false
await user.save()
}else {
deletedUsers++
let deletedCount = await user.deleteOne({
_id : userid
})
}
}
}))
//now send the response
res.status(200).send({deletedUsers, inactivatedUsers, notFoundUsers})

}catch(error){
console.log('errors encountered while deleting users: ', error);
res.status(500).send({ error: 'API error' })
}
}

export async function updateUser(req, res) {
const errors = validationResult(req)

Expand Down
14 changes: 13 additions & 1 deletion nomad-rest-api/routes/admin/users.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Router } from 'express'
import { body } from 'express-validator'

import { getUsers, postUser, updateUser, toggleActive } from '../../controllers/admin/users.js'
import { getUsers, postUser, updateUser, toggleActive, deleteUsers } from '../../controllers/admin/users.js'
import auth from '../../middleware/auth.js'
import authAdmin from '../../middleware/auth-admin.js'
import User from '../../models/user.js'
Expand All @@ -10,6 +10,18 @@ const router = Router()

router.get('/', auth, getUsers)

//delete users route
router.post(
'/delete-users',
[
body('users')
.custom(value=> Array.isArray(value) && value.length >= 1)
],
auth,
authAdmin,
deleteUsers
)

router.post(
'/',
[
Expand Down