mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 19:44:35 +00:00
fix: improve user experience for adding permissions
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Paper,
|
Paper,
|
||||||
@@ -16,19 +16,92 @@ import { BootstrapDialog } from '../../components/modal'
|
|||||||
import { BootstrapDialogTitle } from '../../components/dialogTitle'
|
import { BootstrapDialogTitle } from '../../components/dialogTitle'
|
||||||
import { PermissionResponse } from '../../utils/types'
|
import { PermissionResponse } from '../../utils/types'
|
||||||
|
|
||||||
|
export interface PermissionResponsePayload {
|
||||||
|
existingPermissions: PermissionResponse[]
|
||||||
|
newAddedPermissions: PermissionResponse[]
|
||||||
|
updatedPermissions: PermissionResponse[]
|
||||||
|
errorPaths: string[]
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean
|
open: boolean
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
permissionResponses: PermissionResponse[]
|
payload: PermissionResponsePayload
|
||||||
errorResponses: any[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionResponseModal = ({
|
const PermissionResponseModal = ({ open, setOpen, payload }: Props) => {
|
||||||
open,
|
const rows = useMemo(() => {
|
||||||
setOpen,
|
const paths: any = []
|
||||||
permissionResponses,
|
|
||||||
errorResponses
|
const existingPermissionsLength = payload.existingPermissions.length
|
||||||
}: Props) => {
|
const newAddedPermissionsLength = payload.newAddedPermissions.length
|
||||||
|
const updatedPermissionsLength = payload.updatedPermissions.length
|
||||||
|
if (
|
||||||
|
existingPermissionsLength >= newAddedPermissionsLength &&
|
||||||
|
existingPermissionsLength >= updatedPermissionsLength
|
||||||
|
) {
|
||||||
|
payload.existingPermissions.forEach((permission, index) => {
|
||||||
|
const obj = {
|
||||||
|
existing: permission.path,
|
||||||
|
newAdded:
|
||||||
|
index < newAddedPermissionsLength
|
||||||
|
? payload.newAddedPermissions[index].path
|
||||||
|
: '-',
|
||||||
|
updated:
|
||||||
|
index < updatedPermissionsLength
|
||||||
|
? payload.updatedPermissions[index].path
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
paths.push(obj)
|
||||||
|
})
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
newAddedPermissionsLength >= existingPermissionsLength &&
|
||||||
|
newAddedPermissionsLength >= updatedPermissionsLength
|
||||||
|
) {
|
||||||
|
payload.newAddedPermissions.forEach((permission, index) => {
|
||||||
|
const obj = {
|
||||||
|
newAdded: permission.path,
|
||||||
|
existing:
|
||||||
|
index < existingPermissionsLength
|
||||||
|
? payload.existingPermissions[index].path
|
||||||
|
: '-',
|
||||||
|
updated:
|
||||||
|
index < updatedPermissionsLength
|
||||||
|
? payload.updatedPermissions[index].path
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
paths.push(obj)
|
||||||
|
})
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
updatedPermissionsLength >= existingPermissionsLength &&
|
||||||
|
updatedPermissionsLength >= newAddedPermissionsLength
|
||||||
|
) {
|
||||||
|
payload.updatedPermissions.forEach((permission, index) => {
|
||||||
|
const obj = {
|
||||||
|
updated: permission.path,
|
||||||
|
existing:
|
||||||
|
index < existingPermissionsLength
|
||||||
|
? payload.existingPermissions[index].path
|
||||||
|
: '-',
|
||||||
|
newAdded:
|
||||||
|
index < newAddedPermissionsLength
|
||||||
|
? payload.newAddedPermissions[index].path
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
paths.push(obj)
|
||||||
|
})
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}, [payload])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<BootstrapDialog onClose={() => setOpen(false)} open={open}>
|
<BootstrapDialog onClose={() => setOpen(false)} open={open}>
|
||||||
@@ -39,52 +112,38 @@ const PermissionResponseModal = ({
|
|||||||
Permission Response
|
Permission Response
|
||||||
</BootstrapDialogTitle>
|
</BootstrapDialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
{permissionResponses.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Typography gutterBottom>Added Permissions</Typography>
|
|
||||||
{permissionResponses.length > 0 && (
|
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead>
|
<TableHead sx={{ background: 'rgb(0,0,0, 0.3)' }}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Path</TableCell>
|
<TableCell>New</TableCell>
|
||||||
<TableCell>Type</TableCell>
|
<TableCell>Updated</TableCell>
|
||||||
<TableCell>Setting</TableCell>
|
<TableCell>Unchanged</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{permissionResponses.map((permission, index) => {
|
{rows.map((obj: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell>{permission.path}</TableCell>
|
<TableCell>{obj.newAdded}</TableCell>
|
||||||
<TableCell>{permission.type}</TableCell>
|
<TableCell>{obj.updated}</TableCell>
|
||||||
<TableCell>{permission.setting}</TableCell>
|
<TableCell>{obj.existing}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{errorResponses.length > 0 && (
|
{payload.errorPaths.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Typography style={{ color: 'red', marginTop: '10px' }}>
|
<Typography style={{ color: 'red', marginTop: '10px' }}>
|
||||||
Errors
|
Errors occurred for following paths:
|
||||||
</Typography>
|
</Typography>
|
||||||
<ul>
|
<ul>
|
||||||
{errorResponses.map((err, index) => (
|
{payload.errorPaths.map((path, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<Typography>
|
<Typography>{path}</Typography>
|
||||||
Error occurred for Path: {err.permission.path}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{typeof err.error.response.data === 'object'
|
|
||||||
? JSON.stringify(err.error.response.data)
|
|
||||||
: err.error.response.data}
|
|
||||||
</Typography>
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const Settings = () => {
|
|||||||
>
|
>
|
||||||
<StyledTab label="Profile" value="profile" />
|
<StyledTab label="Profile" value="profile" />
|
||||||
{appContext.mode === ModeType.Server && (
|
{appContext.mode === ModeType.Server && (
|
||||||
<StyledTab label="Permission" value="permission" />
|
<StyledTab label="Permissions" value="permission" />
|
||||||
)}
|
)}
|
||||||
</TabList>
|
</TabList>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ import { styled } from '@mui/material/styles'
|
|||||||
import Modal from '../../components/modal'
|
import Modal from '../../components/modal'
|
||||||
import PermissionFilterModal from './permissionFilterModal'
|
import PermissionFilterModal from './permissionFilterModal'
|
||||||
import AddPermissionModal from './addPermissionModal'
|
import AddPermissionModal from './addPermissionModal'
|
||||||
import PermissionResponseModal from './addPermissionResponseModal'
|
import PermissionResponseModal, {
|
||||||
|
PermissionResponsePayload
|
||||||
|
} from './addPermissionResponseModal'
|
||||||
import UpdatePermissionModal from './updatePermissionModal'
|
import UpdatePermissionModal from './updatePermissionModal'
|
||||||
import DeleteConfirmationModal from '../../components/deleteConfirmationModal'
|
import DeleteConfirmationModal from '../../components/deleteConfirmationModal'
|
||||||
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
|
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
|
||||||
@@ -37,6 +39,11 @@ import {
|
|||||||
PermissionResponse,
|
PermissionResponse,
|
||||||
RegisterPermissionPayload
|
RegisterPermissionPayload
|
||||||
} from '../../utils/types'
|
} from '../../utils/types'
|
||||||
|
import {
|
||||||
|
findExistingPermission,
|
||||||
|
findUpdatingPermission
|
||||||
|
} from '../../utils/helper'
|
||||||
|
|
||||||
import { AppContext } from '../../context/appContext'
|
import { AppContext } from '../../context/appContext'
|
||||||
|
|
||||||
const BootstrapTableCell = styled(TableCell)({
|
const BootstrapTableCell = styled(TableCell)({
|
||||||
@@ -62,10 +69,13 @@ const Permission = () => {
|
|||||||
const [addPermissionModalOpen, setAddPermissionModalOpen] = useState(false)
|
const [addPermissionModalOpen, setAddPermissionModalOpen] = useState(false)
|
||||||
const [openPermissionResponseModal, setOpenPermissionResponseModal] =
|
const [openPermissionResponseModal, setOpenPermissionResponseModal] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [addedPermissions, setAddedPermission] = useState<PermissionResponse[]>(
|
const [permissionResponsePayload, setPermissionResponsePayload] =
|
||||||
[]
|
useState<PermissionResponsePayload>({
|
||||||
)
|
existingPermissions: [],
|
||||||
const [errorResponses, setErrorResponses] = useState<any[]>([])
|
newAddedPermissions: [],
|
||||||
|
updatedPermissions: [],
|
||||||
|
errorPaths: []
|
||||||
|
})
|
||||||
|
|
||||||
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] =
|
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] =
|
||||||
useState(false)
|
useState(false)
|
||||||
@@ -189,31 +199,69 @@ const Permission = () => {
|
|||||||
setFilterApplied(false)
|
setFilterApplied(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addPermission = (permissions: RegisterPermissionPayload[]) => {
|
const addPermission = async (
|
||||||
|
permissionsToAdd: RegisterPermissionPayload[]
|
||||||
|
) => {
|
||||||
setAddPermissionModalOpen(false)
|
setAddPermissionModalOpen(false)
|
||||||
setAddedPermission([])
|
|
||||||
setErrorResponses([])
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
const permissionResponses: PermissionResponse[] = []
|
const newAddedPermissions: PermissionResponse[] = []
|
||||||
const errors: any = []
|
const updatedPermissions: PermissionResponse[] = []
|
||||||
|
const errorPaths: string[] = []
|
||||||
|
|
||||||
permissions.forEach(async (permission) => {
|
const existingPermissions: PermissionResponse[] = []
|
||||||
|
const updatingPermissions: PermissionResponse[] = []
|
||||||
|
const newPermissions: RegisterPermissionPayload[] = []
|
||||||
|
|
||||||
|
permissionsToAdd.forEach((permission) => {
|
||||||
|
const existingPermission = findExistingPermission(permissions, permission)
|
||||||
|
if (existingPermission) {
|
||||||
|
existingPermissions.push(existingPermission)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatingPermission = findUpdatingPermission(permissions, permission)
|
||||||
|
if (updatingPermission) {
|
||||||
|
updatingPermissions.push(updatingPermission)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPermissions.push(permission)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const permission of newPermissions) {
|
||||||
await axios
|
await axios
|
||||||
.post('/SASjsApi/permission', permission)
|
.post('/SASjsApi/permission', permission)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
permissionResponses.push(res.data)
|
newAddedPermissions.push(res.data)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
errors.push({ error, permission })
|
errorPaths.push(permission.path)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const permission of updatingPermissions) {
|
||||||
|
await axios
|
||||||
|
.patch(`/SASjsApi/permission/${permission.permissionId}`, {
|
||||||
|
setting: permission.setting === 'Grant' ? 'Deny' : 'Grant'
|
||||||
})
|
})
|
||||||
|
.then((res) => {
|
||||||
|
updatedPermissions.push(res.data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
errorPaths.push(permission.path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fetchPermissions()
|
fetchPermissions()
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
setPermissionResponsePayload({
|
||||||
|
existingPermissions,
|
||||||
|
updatedPermissions,
|
||||||
|
newAddedPermissions,
|
||||||
|
errorPaths
|
||||||
|
})
|
||||||
setOpenPermissionResponseModal(true)
|
setOpenPermissionResponseModal(true)
|
||||||
setAddedPermission(permissionResponses)
|
|
||||||
setErrorResponses(errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
|
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
|
||||||
@@ -353,8 +401,7 @@ const Permission = () => {
|
|||||||
<PermissionResponseModal
|
<PermissionResponseModal
|
||||||
open={openPermissionResponseModal}
|
open={openPermissionResponseModal}
|
||||||
setOpen={setOpenPermissionResponseModal}
|
setOpen={setOpenPermissionResponseModal}
|
||||||
permissionResponses={addedPermissions}
|
payload={permissionResponsePayload}
|
||||||
errorResponses={errorResponses}
|
|
||||||
/>
|
/>
|
||||||
<UpdatePermissionModal
|
<UpdatePermissionModal
|
||||||
open={updatePermissionModalOpen}
|
open={updatePermissionModalOpen}
|
||||||
|
|||||||
59
web/src/utils/helper.ts
Normal file
59
web/src/utils/helper.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { PermissionResponse, RegisterPermissionPayload } from './types'
|
||||||
|
|
||||||
|
export const findExistingPermission = (
|
||||||
|
existingPermissions: PermissionResponse[],
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) => {
|
||||||
|
for (const permission of existingPermissions) {
|
||||||
|
if (
|
||||||
|
permission.user?.id === newPermission.principalId &&
|
||||||
|
hasSameCombination(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
|
||||||
|
if (
|
||||||
|
permission.group?.groupId === newPermission.principalId &&
|
||||||
|
hasSameCombination(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findUpdatingPermission = (
|
||||||
|
existingPermissions: PermissionResponse[],
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) => {
|
||||||
|
for (const permission of existingPermissions) {
|
||||||
|
if (
|
||||||
|
permission.user?.id === newPermission.principalId &&
|
||||||
|
hasDifferentSetting(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
|
||||||
|
if (
|
||||||
|
permission.group?.groupId === newPermission.principalId &&
|
||||||
|
hasDifferentSetting(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSameCombination = (
|
||||||
|
existingPermission: PermissionResponse,
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) =>
|
||||||
|
existingPermission.path === newPermission.path &&
|
||||||
|
existingPermission.type === newPermission.type &&
|
||||||
|
existingPermission.setting === newPermission.setting
|
||||||
|
|
||||||
|
const hasDifferentSetting = (
|
||||||
|
existingPermission: PermissionResponse,
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) =>
|
||||||
|
existingPermission.path === newPermission.path &&
|
||||||
|
existingPermission.type === newPermission.type &&
|
||||||
|
existingPermission.setting !== newPermission.setting
|
||||||
Reference in New Issue
Block a user