mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +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