mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: add usage info to project role deletion dialog (#4464)
## About the changes <!-- Describe the changes introduced. What are they and why are they being introduced? Feel free to also add screenshots or steps to view the changes if they're visual. --> Adds projects user and group -usage information to the dialog shown when user wants to delete a project role <img width="670" alt="Skjermbilde 2023-08-10 kl 08 28 40" src="https://github.com/Unleash/unleash/assets/707867/a1df961b-2d0f-419d-b9bf-fedef896a84e"> --------- Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
		
							parent
							
								
									da7829daca
								
							
						
					
					
						commit
						76d3cc59cf
					
				| @ -1,21 +1,7 @@ | ||||
| import { Alert, styled } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts'; | ||||
| import { useUsers } from 'hooks/api/getters/useUsers/useUsers'; | ||||
| import { IRole } from 'interfaces/role'; | ||||
| import { RoleDeleteDialogUsers } from './RoleDeleteDialogUsers/RoleDeleteDialogUsers'; | ||||
| import { RoleDeleteDialogServiceAccounts } from './RoleDeleteDialogServiceAccounts/RoleDeleteDialogServiceAccounts'; | ||||
| import { useGroups } from 'hooks/api/getters/useGroups/useGroups'; | ||||
| import { RoleDeleteDialogGroups } from './RoleDeleteDialogGroups/RoleDeleteDialogGroups'; | ||||
| 
 | ||||
| const StyledTableContainer = styled('div')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(1.5), | ||||
| })); | ||||
| 
 | ||||
| const StyledLabel = styled('p')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(3), | ||||
| })); | ||||
| import { RoleDeleteDialogRootRole } from './RoleDeleteDialogRootRole/RoleDeleteDialogRootRole'; | ||||
| import { RoleDeleteDialogProjectRole } from './RoleDeleteDialogProjectRole/RoleDeleteDialogProjectRole'; | ||||
| import { CUSTOM_PROJECT_ROLE_TYPE } from 'constants/roles'; | ||||
| 
 | ||||
| interface IRoleDeleteDialogProps { | ||||
|     role?: IRole; | ||||
| @ -30,98 +16,23 @@ export const RoleDeleteDialog = ({ | ||||
|     setOpen, | ||||
|     onConfirm, | ||||
| }: IRoleDeleteDialogProps) => { | ||||
|     const { users } = useUsers(); | ||||
|     const { serviceAccounts } = useServiceAccounts(); | ||||
|     const { groups } = useGroups(); | ||||
| 
 | ||||
|     const roleUsers = users.filter(({ rootRole }) => rootRole === role?.id); | ||||
|     const roleServiceAccounts = serviceAccounts.filter( | ||||
|         ({ rootRole }) => rootRole === role?.id | ||||
|     ); | ||||
|     const roleGroups = groups?.filter(({ rootRole }) => rootRole === role?.id); | ||||
| 
 | ||||
|     const entitiesWithRole = Boolean( | ||||
|         roleUsers.length || roleServiceAccounts.length || roleGroups?.length | ||||
|     ); | ||||
|     if (role?.type === CUSTOM_PROJECT_ROLE_TYPE) { | ||||
|         return ( | ||||
|             <RoleDeleteDialogProjectRole | ||||
|                 role={role} | ||||
|                 open={open} | ||||
|                 setOpen={setOpen} | ||||
|                 onConfirm={onConfirm} | ||||
|             /> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             title="Delete role?" | ||||
|         <RoleDeleteDialogRootRole | ||||
|             role={role} | ||||
|             open={open} | ||||
|             primaryButtonText="Delete role" | ||||
|             secondaryButtonText="Cancel" | ||||
|             disabledPrimaryButton={entitiesWithRole} | ||||
|             onClick={() => onConfirm(role!)} | ||||
|             onClose={() => { | ||||
|                 setOpen(false); | ||||
|             }} | ||||
|         > | ||||
|             <ConditionallyRender | ||||
|                 condition={entitiesWithRole} | ||||
|                 show={ | ||||
|                     <> | ||||
|                         <Alert severity="error"> | ||||
|                             You are not allowed to delete a role that is | ||||
|                             currently in use. Please change the role of the | ||||
|                             following entities first: | ||||
|                         </Alert> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(roleUsers.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Users ({roleUsers.length}): | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogUsers | ||||
|                                             users={roleUsers} | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(roleServiceAccounts.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Service accounts ( | ||||
|                                         {roleServiceAccounts.length}): | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogServiceAccounts | ||||
|                                             serviceAccounts={ | ||||
|                                                 roleServiceAccounts | ||||
|                                             } | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(roleGroups?.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Groups ({roleGroups?.length}): | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogGroups | ||||
|                                             groups={roleGroups!} | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                     </> | ||||
|                 } | ||||
|                 elseShow={ | ||||
|                     <p> | ||||
|                         You are about to delete role:{' '} | ||||
|                         <strong>{role?.name}</strong> | ||||
|                     </p> | ||||
|                 } | ||||
|             /> | ||||
|         </Dialogue> | ||||
|             setOpen={setOpen} | ||||
|             onConfirm={onConfirm} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,82 @@ | ||||
| import { Alert, styled } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { IRole } from 'interfaces/role'; | ||||
| import { useProjectRoleAccessUsage } from 'hooks/api/getters/useProjectRoleAccessUsage/useProjectRoleAccessUsage'; | ||||
| import { RoleDeleteDialogProjectRoleTable } from './RoleDeleteDialogProjectRoleTable'; | ||||
| 
 | ||||
| const StyledTableContainer = styled('div')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(1.5), | ||||
| })); | ||||
| 
 | ||||
| const StyledLabel = styled('p')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(3), | ||||
| })); | ||||
| 
 | ||||
| interface IRoleDeleteDialogProps { | ||||
|     role?: IRole; | ||||
|     open: boolean; | ||||
|     setOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     onConfirm: (role: IRole) => void; | ||||
| } | ||||
| 
 | ||||
| export const RoleDeleteDialogProjectRole = ({ | ||||
|     role, | ||||
|     open, | ||||
|     setOpen, | ||||
|     onConfirm, | ||||
| }: IRoleDeleteDialogProps) => { | ||||
|     const { projects } = useProjectRoleAccessUsage(role?.id); | ||||
| 
 | ||||
|     const entitiesWithRole = Boolean(projects?.length); | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             title="Delete project role?" | ||||
|             open={open} | ||||
|             primaryButtonText="Delete role" | ||||
|             secondaryButtonText="Cancel" | ||||
|             disabledPrimaryButton={entitiesWithRole} | ||||
|             onClick={() => onConfirm(role!)} | ||||
|             onClose={() => { | ||||
|                 setOpen(false); | ||||
|             }} | ||||
|             maxWidth="md" | ||||
|         > | ||||
|             <ConditionallyRender | ||||
|                 condition={entitiesWithRole} | ||||
|                 show={ | ||||
|                     <> | ||||
|                         <Alert severity="error"> | ||||
|                             You are not allowed to delete a role that is | ||||
|                             currently in use. Please change the role of the | ||||
|                             following entities first: | ||||
|                         </Alert> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(projects?.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Role assigned in {projects?.length}{' '} | ||||
|                                         projects: | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogProjectRoleTable | ||||
|                                             projects={projects} | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                     </> | ||||
|                 } | ||||
|                 elseShow={ | ||||
|                     <p> | ||||
|                         You are about to delete role:{' '} | ||||
|                         <strong>{role?.name}</strong> | ||||
|                     </p> | ||||
|                 } | ||||
|             /> | ||||
|         </Dialogue> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,92 @@ | ||||
| import { VirtualizedTable } from 'component/common/Table'; | ||||
| import { useMemo, useState } from 'react'; | ||||
| import { useTable, useSortBy, useFlexLayout, Column } from 'react-table'; | ||||
| import { sortTypes } from 'utils/sortTypes'; | ||||
| import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; | ||||
| import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; | ||||
| import { IProjectRoleUsageCount } from 'interfaces/project'; | ||||
| import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; | ||||
| 
 | ||||
| interface IRoleDeleteDialogProjectRoleTableProps { | ||||
|     projects: IProjectRoleUsageCount[]; | ||||
| } | ||||
| 
 | ||||
| export const RoleDeleteDialogProjectRoleTable = ({ | ||||
|     projects, | ||||
| }: IRoleDeleteDialogProjectRoleTableProps) => { | ||||
|     const [initialState] = useState(() => ({ | ||||
|         sortBy: [{ id: 'name' }], | ||||
|     })); | ||||
| 
 | ||||
|     const columns = useMemo( | ||||
|         () => | ||||
|             [ | ||||
|                 { | ||||
|                     id: 'name', | ||||
|                     Header: 'Project name', | ||||
|                     accessor: (row: any) => row.project || '', | ||||
|                     minWidth: 200, | ||||
|                     Cell: ({ row: { original: item } }: any) => ( | ||||
|                         <LinkCell | ||||
|                             title={item.project} | ||||
|                             to={`/projects/${item.project}`} | ||||
|                         /> | ||||
|                     ), | ||||
|                 }, | ||||
|                 { | ||||
|                     id: 'users', | ||||
|                     Header: 'Assigned users', | ||||
|                     accessor: (row: any) => | ||||
|                         row.userCount === 1 | ||||
|                             ? '1 user' | ||||
|                             : `${row.userCount} users`, | ||||
|                     Cell: TextCell, | ||||
|                     maxWidth: 150, | ||||
|                 }, | ||||
|                 { | ||||
|                     id: 'serviceAccounts', | ||||
|                     Header: 'Service accounts', | ||||
|                     accessor: (row: any) => | ||||
|                         row.serviceAccountCount === 1 | ||||
|                             ? '1 account' | ||||
|                             : `${row.serviceAccountCount} accounts`, | ||||
|                     Cell: TextCell, | ||||
|                     maxWidth: 150, | ||||
|                 }, | ||||
|                 { | ||||
|                     id: 'groups', | ||||
|                     Header: 'Assigned groups', | ||||
|                     accessor: (row: any) => | ||||
|                         row.groupCount === 1 | ||||
|                             ? '1 group' | ||||
|                             : `${row.groupCount} groups`, | ||||
|                     Cell: TextCell, | ||||
|                     maxWidth: 150, | ||||
|                 }, | ||||
|             ] as Column<IProjectRoleUsageCount>[], | ||||
|         [] | ||||
|     ); | ||||
| 
 | ||||
|     const { headerGroups, rows, prepareRow } = useTable( | ||||
|         { | ||||
|             columns, | ||||
|             data: projects, | ||||
|             initialState, | ||||
|             sortTypes, | ||||
|             autoResetHiddenColumns: false, | ||||
|             autoResetSortBy: false, | ||||
|             disableSortRemove: true, | ||||
|             disableMultiSort: true, | ||||
|         }, | ||||
|         useSortBy, | ||||
|         useFlexLayout | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|         <VirtualizedTable | ||||
|             rows={rows} | ||||
|             headerGroups={headerGroups} | ||||
|             prepareRow={prepareRow} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,127 @@ | ||||
| import { Alert, styled } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts'; | ||||
| import { useUsers } from 'hooks/api/getters/useUsers/useUsers'; | ||||
| import { IRole } from 'interfaces/role'; | ||||
| import { RoleDeleteDialogUsers } from './RoleDeleteDialogUsers'; | ||||
| import { RoleDeleteDialogServiceAccounts } from './RoleDeleteDialogServiceAccounts'; | ||||
| import { useGroups } from 'hooks/api/getters/useGroups/useGroups'; | ||||
| import { RoleDeleteDialogGroups } from './RoleDeleteDialogGroups'; | ||||
| 
 | ||||
| const StyledTableContainer = styled('div')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(1.5), | ||||
| })); | ||||
| 
 | ||||
| const StyledLabel = styled('p')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(3), | ||||
| })); | ||||
| 
 | ||||
| interface IRoleDeleteDialogRootRoleProps { | ||||
|     role?: IRole; | ||||
|     open: boolean; | ||||
|     setOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     onConfirm: (role: IRole) => void; | ||||
| } | ||||
| 
 | ||||
| export const RoleDeleteDialogRootRole = ({ | ||||
|     role, | ||||
|     open, | ||||
|     setOpen, | ||||
|     onConfirm, | ||||
| }: IRoleDeleteDialogRootRoleProps) => { | ||||
|     const { users } = useUsers(); | ||||
|     const { serviceAccounts } = useServiceAccounts(); | ||||
|     const { groups } = useGroups(); | ||||
| 
 | ||||
|     const roleUsers = users.filter(({ rootRole }) => rootRole === role?.id); | ||||
|     const roleServiceAccounts = serviceAccounts.filter( | ||||
|         ({ rootRole }) => rootRole === role?.id | ||||
|     ); | ||||
|     const roleGroups = groups?.filter(({ rootRole }) => rootRole === role?.id); | ||||
| 
 | ||||
|     const entitiesWithRole = Boolean( | ||||
|         roleUsers.length || roleServiceAccounts.length || roleGroups?.length | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             title="Delete root role?" | ||||
|             open={open} | ||||
|             primaryButtonText="Delete role" | ||||
|             secondaryButtonText="Cancel" | ||||
|             disabledPrimaryButton={entitiesWithRole} | ||||
|             onClick={() => onConfirm(role!)} | ||||
|             onClose={() => { | ||||
|                 setOpen(false); | ||||
|             }} | ||||
|         > | ||||
|             <ConditionallyRender | ||||
|                 condition={entitiesWithRole} | ||||
|                 show={ | ||||
|                     <> | ||||
|                         <Alert severity="error"> | ||||
|                             You are not allowed to delete a role that is | ||||
|                             currently in use. Please change the role of the | ||||
|                             following entities first: | ||||
|                         </Alert> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(roleUsers.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Users ({roleUsers.length}): | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogUsers | ||||
|                                             users={roleUsers} | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(roleServiceAccounts.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Service accounts ( | ||||
|                                         {roleServiceAccounts.length}): | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogServiceAccounts | ||||
|                                             serviceAccounts={ | ||||
|                                                 roleServiceAccounts | ||||
|                                             } | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                         <ConditionallyRender | ||||
|                             condition={Boolean(roleGroups?.length)} | ||||
|                             show={ | ||||
|                                 <> | ||||
|                                     <StyledLabel> | ||||
|                                         Groups ({roleGroups?.length}): | ||||
|                                     </StyledLabel> | ||||
|                                     <StyledTableContainer> | ||||
|                                         <RoleDeleteDialogGroups | ||||
|                                             groups={roleGroups!} | ||||
|                                         /> | ||||
|                                     </StyledTableContainer> | ||||
|                                 </> | ||||
|                             } | ||||
|                         /> | ||||
|                     </> | ||||
|                 } | ||||
|                 elseShow={ | ||||
|                     <p> | ||||
|                         You are about to delete role:{' '} | ||||
|                         <strong>{role?.name}</strong> | ||||
|                     </p> | ||||
|                 } | ||||
|             /> | ||||
|         </Dialogue> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										2
									
								
								frontend/src/constants/roles.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								frontend/src/constants/roles.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| export const CUSTOM_ROOT_ROLE_TYPE = 'root-custom'; | ||||
| export const CUSTOM_PROJECT_ROLE_TYPE = 'custom'; | ||||
| @ -0,0 +1,33 @@ | ||||
| import { formatApiPath } from 'utils/formatPath'; | ||||
| import { useMemo } from 'react'; | ||||
| import handleErrorResponses from '../httpErrorResponseHandler'; | ||||
| import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; | ||||
| import useUiConfig from '../useUiConfig/useUiConfig'; | ||||
| import { IProjectRoleUsageCount } from 'interfaces/project'; | ||||
| 
 | ||||
| export const useProjectRoleAccessUsage = (roleId?: number) => { | ||||
|     const { isEnterprise } = useUiConfig(); | ||||
| 
 | ||||
|     const { data, error, mutate } = useConditionalSWR( | ||||
|         isEnterprise() && roleId, | ||||
|         { projects: [] }, | ||||
|         formatApiPath(`api/admin/projects/roles/${roleId}/access`), | ||||
|         fetcher | ||||
|     ); | ||||
| 
 | ||||
|     return useMemo( | ||||
|         () => ({ | ||||
|             projects: (data?.projects ?? []) as IProjectRoleUsageCount[], | ||||
|             loading: !error && !data, | ||||
|             refetch: () => mutate(), | ||||
|             error, | ||||
|         }), | ||||
|         [data, error, mutate] | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const fetcher = (path: string) => { | ||||
|     return fetch(path) | ||||
|         .then(handleErrorResponses('Project role usage')) | ||||
|         .then(res => res.json()); | ||||
| }; | ||||
| @ -35,3 +35,11 @@ export interface IProjectHealthReport extends IProject { | ||||
|     activeCount: number; | ||||
|     updatedAt: string; | ||||
| } | ||||
| 
 | ||||
| export interface IProjectRoleUsageCount { | ||||
|     project: string; | ||||
|     role: number; | ||||
|     userCount: number; | ||||
|     groupCount: number; | ||||
|     serviceAccountCount: number; | ||||
| } | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { Logger } from '../logger'; | ||||
| import { | ||||
|     IAccessInfo, | ||||
|     IAccessStore, | ||||
|     IProjectRoleUsage, | ||||
|     IRole, | ||||
|     IRoleWithProject, | ||||
|     IUserPermission, | ||||
| @ -304,6 +305,59 @@ export class AccessStore implements IAccessStore { | ||||
|         return rows.map((r) => r.group_id); | ||||
|     } | ||||
| 
 | ||||
|     async getProjectUserAndGroupCountsForRole( | ||||
|         roleId: number, | ||||
|     ): Promise<IProjectRoleUsage[]> { | ||||
|         const query = await this.db.raw( | ||||
|             ` | ||||
|             SELECT  | ||||
|                 uq.project, | ||||
|                 sum(uq.user_count) AS user_count, | ||||
|                 sum(uq.svc_account_count) AS svc_account_count, | ||||
|                 sum(uq.group_count) AS group_count | ||||
|             FROM ( | ||||
|                 SELECT  | ||||
|                     project, | ||||
|                     0 AS user_count, | ||||
|                     0 AS svc_account_count, | ||||
|                     count(project) AS group_count | ||||
|                 FROM group_role | ||||
|                 WHERE role_id = ? | ||||
|                 GROUP BY project | ||||
| 
 | ||||
|                 UNION SELECT | ||||
|                     project, | ||||
|                     count(us.id) AS user_count, | ||||
|                     count(svc.id) AS svc_account_count, | ||||
|                     0 AS group_count | ||||
|                 FROM role_user AS usr_r | ||||
|                 LEFT OUTER JOIN public.users AS us ON us.id = usr_r.user_id AND us.is_service = 'false' | ||||
|                 LEFT OUTER JOIN public.users AS svc ON svc.id = usr_r.user_id AND svc.is_service = 'true' | ||||
|                 WHERE usr_r.role_id = ? | ||||
|                 GROUP BY usr_r.project | ||||
|             ) AS uq | ||||
|             GROUP BY uq.project | ||||
|         `,
 | ||||
|             [roleId, roleId], | ||||
|         ); | ||||
| 
 | ||||
|         /* | ||||
|         const rows2 = await this.db(T.ROLE_USER) | ||||
|             .select('project', this.db.raw('count(project) as user_count')) | ||||
|             .where('role_id', roleId) | ||||
|             .groupBy('project'); | ||||
|             */ | ||||
|         return query.rows.map((r) => { | ||||
|             return { | ||||
|                 project: r.project, | ||||
|                 role: roleId, | ||||
|                 userCount: Number(r.user_count), | ||||
|                 groupCount: Number(r.group_count), | ||||
|                 serviceAccountCount: Number(r.svc_account_count), | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     async addUserToRole( | ||||
|         userId: number, | ||||
|         roleId: number, | ||||
|  | ||||
| @ -3,6 +3,7 @@ import User, { IProjectUser, IUser } from '../types/user'; | ||||
| import { | ||||
|     IAccessInfo, | ||||
|     IAccessStore, | ||||
|     IProjectRoleUsage, | ||||
|     IRole, | ||||
|     IRoleWithPermissions, | ||||
|     IRoleWithProject, | ||||
| @ -453,6 +454,10 @@ export class AccessService { | ||||
|         return [roles, users.flat(), groups]; | ||||
|     } | ||||
| 
 | ||||
|     async getProjectRoleUsage(roleId: number): Promise<IProjectRoleUsage[]> { | ||||
|         return this.store.getProjectUserAndGroupCountsForRole(roleId); | ||||
|     } | ||||
| 
 | ||||
|     async createDefaultProjectRoles( | ||||
|         owner: IUser, | ||||
|         projectId: string, | ||||
|  | ||||
| @ -35,6 +35,7 @@ import { | ||||
|     RoleName, | ||||
|     IFlagResolver, | ||||
|     ProjectAccessAddedEvent, | ||||
|     IProjectRoleUsage, | ||||
| } from '../types'; | ||||
| import { IProjectQuery, IProjectStore } from '../types/stores/project-store'; | ||||
| import { | ||||
| @ -697,6 +698,10 @@ export default class ProjectService { | ||||
|         return this.store.getProjectsByUser(userId); | ||||
|     } | ||||
| 
 | ||||
|     async getProjectRoleUsage(roleId: number): Promise<IProjectRoleUsage[]> { | ||||
|         return this.accessService.getProjectRoleUsage(roleId); | ||||
|     } | ||||
| 
 | ||||
|     async statusJob(): Promise<void> { | ||||
|         const projects = await this.store.getAll(); | ||||
| 
 | ||||
|  | ||||
| @ -1 +1,9 @@ | ||||
| export const DEFAULT_PROJECT = 'default'; | ||||
| 
 | ||||
| export interface IProjectRoleUsage { | ||||
|     project: string; | ||||
|     role: number; | ||||
|     userCount: number; | ||||
|     groupCount: number; | ||||
|     serviceAccountCount: number; | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,14 @@ export interface IRole { | ||||
|     type: string; | ||||
| } | ||||
| 
 | ||||
| export interface IProjectRoleUsage { | ||||
|     project: string; | ||||
|     role: number; | ||||
|     userCount: number; | ||||
|     groupCount: number; | ||||
|     serviceAccountCount: number; | ||||
| } | ||||
| 
 | ||||
| export interface IRoleWithProject extends IRole { | ||||
|     project: string; | ||||
| } | ||||
| @ -70,6 +78,10 @@ export interface IAccessStore extends Store<IRole, number> { | ||||
| 
 | ||||
|     getGroupIdsForRole(roleId: number, projectId?: string): Promise<number[]>; | ||||
| 
 | ||||
|     getProjectUserAndGroupCountsForRole( | ||||
|         roleId: number, | ||||
|     ): Promise<IProjectRoleUsage[]>; | ||||
| 
 | ||||
|     wipePermissionsFromRole(role_id: number): Promise<void>; | ||||
| 
 | ||||
|     addEnvironmentPermissionsToRole( | ||||
|  | ||||
							
								
								
									
										7
									
								
								src/test/fixtures/fake-access-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/test/fixtures/fake-access-store.ts
									
									
									
									
										vendored
									
									
								
							| @ -2,6 +2,7 @@ | ||||
| import { | ||||
|     IAccessInfo, | ||||
|     IAccessStore, | ||||
|     IProjectRoleUsage, | ||||
|     IRole, | ||||
|     IRoleWithProject, | ||||
|     IUserPermission, | ||||
| @ -20,6 +21,12 @@ class AccessStoreMock implements IAccessStore { | ||||
|         this.fakeRolesStore = roleStore ?? new FakeRoleStore(); | ||||
|     } | ||||
| 
 | ||||
|     getProjectUserAndGroupCountsForRole( | ||||
|         roleId: number, | ||||
|     ): Promise<IProjectRoleUsage[]> { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| 
 | ||||
|     addAccessToProject( | ||||
|         users: IAccessInfo[], | ||||
|         groups: IAccessInfo[], | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user