Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0b9ed57

Browse files
authored
feat: add delete custom role context menu button and modal (#14228)
* feat: delete custom role * fix: add doc comment
1 parent c648c54 commit 0b9ed57

File tree

4 files changed

+93
-38
lines changed

4 files changed

+93
-38
lines changed

site/src/api/api.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -600,21 +600,27 @@ class ApiMethods {
600600
return response.data;
601601
};
602602

603+
/**
604+
* @param organization Can be the organization's ID or name
605+
*/
603606
patchOrganizationRole = async (
604-
organizationId: string,
607+
organization: string,
605608
role: TypesGen.Role,
606609
): Promise<TypesGen.Role> => {
607610
const response = await this.axios.patch<TypesGen.Role>(
608-
`/api/v2/organizations/${organizationId}/members/roles`,
611+
`/api/v2/organizations/${organization}/members/roles`,
609612
role,
610613
);
611614

612615
return response.data;
613616
};
614617

615-
deleteOrganizationRole = async (organizationId: string, roleName: string) => {
618+
/**
619+
* @param organization Can be the organization's ID or name
620+
*/
621+
deleteOrganizationRole = async (organization: string, roleName: string) => {
616622
await this.axios.delete(
617-
`/api/v2/organizations/${organizationId}/members/roles/${roleName}`,
623+
`/api/v2/organizations/${organization}/members/roles/${roleName}`,
618624
);
619625
};
620626

site/src/api/queries/roles.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,37 @@ export const roles = () => {
1616
};
1717
};
1818

19-
export const organizationRoles = (organizationId: string) => {
19+
export const organizationRoles = (organization: string) => {
2020
return {
21-
queryKey: ["organization", organizationId, "roles"],
22-
queryFn: () => API.getOrganizationRoles(organizationId),
21+
queryKey: ["organization", organization, "roles"],
22+
queryFn: () => API.getOrganizationRoles(organization),
2323
};
2424
};
2525

2626
export const patchOrganizationRole = (
2727
queryClient: QueryClient,
28-
organizationId: string,
28+
organization: string,
2929
) => {
3030
return {
3131
mutationFn: (request: Role) =>
32-
API.patchOrganizationRole(organizationId, request),
32+
API.patchOrganizationRole(organization, request),
3333
onSuccess: async (updatedRole: Role) =>
3434
await queryClient.invalidateQueries(
35-
getRoleQueryKey(organizationId, updatedRole.name),
35+
getRoleQueryKey(organization, updatedRole.name),
3636
),
3737
};
3838
};
3939

40-
export const deleteRole = (
40+
export const deleteOrganizationRole = (
4141
queryClient: QueryClient,
42-
organizationId: string,
42+
organization: string,
4343
) => {
4444
return {
45-
mutationFn: API.deleteOrganizationRole,
45+
mutationFn: (roleName: string) =>
46+
API.deleteOrganizationRole(organization, roleName),
4647
onSuccess: async (_: void, roleName: string) =>
4748
await queryClient.invalidateQueries(
48-
getRoleQueryKey(organizationId, roleName),
49+
getRoleQueryKey(organization, roleName),
4950
),
5051
};
5152
};

site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage.tsx

+33-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import AddIcon from "@mui/icons-material/AddOutlined";
22
import Button from "@mui/material/Button";
3-
import { type FC, useEffect } from "react";
3+
import { type FC, useEffect, useState } from "react";
44
import { Helmet } from "react-helmet-async";
5-
import { useQuery } from "react-query";
5+
import { useMutation, useQuery, useQueryClient } from "react-query";
66
import { Link as RouterLink, useParams } from "react-router-dom";
77
import { getErrorMessage } from "api/errors";
88
import { organizationPermissions } from "api/queries/organizations";
9-
import { organizationRoles } from "api/queries/roles";
10-
import { displayError } from "components/GlobalSnackbar/utils";
9+
import { deleteOrganizationRole, organizationRoles } from "api/queries/roles";
10+
import type { Role } from "api/typesGenerated";
11+
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
12+
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
1113
import { Loader } from "components/Loader/Loader";
1214
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
1315
import { Stack } from "components/Stack/Stack";
@@ -17,13 +19,18 @@ import { useOrganizationSettings } from "../ManagementSettingsLayout";
1719
import CustomRolesPageView from "./CustomRolesPageView";
1820

1921
export const CustomRolesPage: FC = () => {
22+
const queryClient = useQueryClient();
2023
const { custom_roles: isCustomRolesEnabled } = useFeatureVisibility();
2124
const { organization: organizationName } = useParams() as {
2225
organization: string;
2326
};
2427
const { organizations } = useOrganizationSettings();
2528
const organization = organizations?.find((o) => o.name === organizationName);
2629
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
30+
const deleteRoleMutation = useMutation(
31+
deleteOrganizationRole(queryClient, organizationName),
32+
);
33+
const [roleToDelete, setRoleToDelete] = useState<Role>();
2734
const organizationRolesQuery = useQuery(organizationRoles(organizationName));
2835
const filteredRoleData = organizationRolesQuery.data?.filter(
2936
(role) => role.built_in === false,
@@ -69,9 +76,31 @@ export const CustomRolesPage: FC = () => {
6976

7077
<CustomRolesPageView
7178
roles={filteredRoleData}
79+
onDeleteRole={setRoleToDelete}
7280
canAssignOrgRole={permissions.assignOrgRole}
7381
isCustomRolesEnabled={isCustomRolesEnabled}
7482
/>
83+
84+
<DeleteDialog
85+
key={roleToDelete?.name}
86+
isOpen={roleToDelete !== undefined}
87+
confirmLoading={deleteRoleMutation.isLoading}
88+
name={roleToDelete?.name ?? ""}
89+
entity="role"
90+
onCancel={() => setRoleToDelete(undefined)}
91+
onConfirm={async () => {
92+
try {
93+
await deleteRoleMutation.mutateAsync(roleToDelete!.name);
94+
setRoleToDelete(undefined);
95+
await organizationRolesQuery.refetch();
96+
displaySuccess("Custom role deleted successfully!");
97+
} catch (error) {
98+
displayError(
99+
getErrorMessage(error, "Failed to delete custom role"),
100+
);
101+
}
102+
}}
103+
/>
75104
</>
76105
);
77106
};

site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.tsx

+39-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Interpolation, Theme } from "@emotion/react";
22
import AddOutlined from "@mui/icons-material/AddOutlined";
3-
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
43
import Button from "@mui/material/Button";
54
import Skeleton from "@mui/material/Skeleton";
65
import Table from "@mui/material/Table";
@@ -14,22 +13,30 @@ import { Link as RouterLink, useNavigate } from "react-router-dom";
1413
import type { Role } from "api/typesGenerated";
1514
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1615
import { EmptyState } from "components/EmptyState/EmptyState";
16+
import {
17+
MoreMenu,
18+
MoreMenuContent,
19+
MoreMenuItem,
20+
MoreMenuTrigger,
21+
ThreeDotsButton,
22+
} from "components/MoreMenu/MoreMenu";
1723
import { Paywall } from "components/Paywall/Paywall";
1824
import {
1925
TableLoaderSkeleton,
2026
TableRowSkeleton,
2127
} from "components/TableLoader/TableLoader";
22-
import { useClickableTableRow } from "hooks";
2328
import { docs } from "utils/docs";
2429

2530
export type CustomRolesPageViewProps = {
2631
roles: Role[] | undefined;
32+
onDeleteRole: (role: Role) => void;
2733
canAssignOrgRole: boolean;
2834
isCustomRolesEnabled: boolean;
2935
};
3036

3137
export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
3238
roles,
39+
onDeleteRole,
3340
canAssignOrgRole,
3441
isCustomRolesEnabled,
3542
}) => {
@@ -53,7 +60,7 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
5360
<TableRow>
5461
<TableCell width="50%">Name</TableCell>
5562
<TableCell width="49%">Permissions</TableCell>
56-
<TableCell width="1%"></TableCell>
63+
<TableCell width="1%" />
5764
</TableRow>
5865
</TableHead>
5966
<TableBody>
@@ -91,7 +98,12 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
9198

9299
<Cond>
93100
{roles?.map((role) => (
94-
<RoleRow key={role.name} role={role} />
101+
<RoleRow
102+
key={role.name}
103+
role={role}
104+
canAssignOrgRole={canAssignOrgRole}
105+
onDelete={() => onDeleteRole(role)}
106+
/>
95107
))}
96108
</Cond>
97109
</ChooseOne>
@@ -106,26 +118,41 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
106118

107119
interface RoleRowProps {
108120
role: Role;
121+
onDelete: () => void;
122+
canAssignOrgRole: boolean;
109123
}
110124

111-
const RoleRow: FC<RoleRowProps> = ({ role }) => {
125+
const RoleRow: FC<RoleRowProps> = ({ role, onDelete, canAssignOrgRole }) => {
112126
const navigate = useNavigate();
113-
const rowProps = useClickableTableRow({
114-
onClick: () => navigate(role.name),
115-
});
116127

117128
return (
118-
<TableRow data-testid={`role-${role.name}`} {...rowProps}>
129+
<TableRow data-testid={`role-${role.name}`}>
119130
<TableCell>{role.display_name || role.name}</TableCell>
120131

121132
<TableCell css={styles.secondary}>
122133
{role.organization_permissions.length}
123134
</TableCell>
124135

125136
<TableCell>
126-
<div css={styles.arrowCell}>
127-
<KeyboardArrowRight css={styles.arrowRight} />
128-
</div>
137+
<MoreMenu>
138+
<MoreMenuTrigger>
139+
<ThreeDotsButton />
140+
</MoreMenuTrigger>
141+
<MoreMenuContent>
142+
<MoreMenuItem
143+
onClick={() => {
144+
navigate(role.name);
145+
}}
146+
>
147+
Edit
148+
</MoreMenuItem>
149+
{canAssignOrgRole && (
150+
<MoreMenuItem danger onClick={onDelete}>
151+
Delete&hellip;
152+
</MoreMenuItem>
153+
)}
154+
</MoreMenuContent>
155+
</MoreMenu>
129156
</TableCell>
130157
</TableRow>
131158
);
@@ -150,14 +177,6 @@ const TableLoader = () => {
150177
};
151178

152179
const styles = {
153-
arrowRight: (theme) => ({
154-
color: theme.palette.text.secondary,
155-
width: 20,
156-
height: 20,
157-
}),
158-
arrowCell: {
159-
display: "flex",
160-
},
161180
secondary: (theme) => ({
162181
color: theme.palette.text.secondary,
163182
}),

0 commit comments

Comments
 (0)