Skip to content

Commit d21771f

Browse files
committed
feat: add resource-action pills to custom roles table
1 parent c3ef7dc commit d21771f

File tree

9 files changed

+158
-7
lines changed

9 files changed

+158
-7
lines changed

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

+121-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type { Interpolation, Theme } from "@emotion/react";
1+
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
22
import AddOutlined from "@mui/icons-material/AddOutlined";
33
import Button from "@mui/material/Button";
44
import Skeleton from "@mui/material/Skeleton";
5+
import Stack from "@mui/material/Stack";
56
import Table from "@mui/material/Table";
67
import TableBody from "@mui/material/TableBody";
78
import TableCell from "@mui/material/TableCell";
89
import TableContainer from "@mui/material/TableContainer";
910
import TableHead from "@mui/material/TableHead";
1011
import TableRow from "@mui/material/TableRow";
11-
import type { Role } from "api/typesGenerated";
12+
import type { Permission, Role } from "api/typesGenerated";
1213
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1314
import { EmptyState } from "components/EmptyState/EmptyState";
1415
import {
@@ -19,6 +20,12 @@ import {
1920
ThreeDotsButton,
2021
} from "components/MoreMenu/MoreMenu";
2122
import { Paywall } from "components/Paywall/Paywall";
23+
import { Pill } from "components/Pill/Pill";
24+
import {
25+
Popover,
26+
PopoverContent,
27+
PopoverTrigger,
28+
} from "components/Popover/Popover";
2229
import {
2330
TableLoaderSkeleton,
2431
TableRowSkeleton,
@@ -42,7 +49,6 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
4249
}) => {
4350
const isLoading = roles === undefined;
4451
const isEmpty = Boolean(roles && roles.length === 0);
45-
4652
return (
4753
<>
4854
<ChooseOne>
@@ -58,8 +64,8 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
5864
<Table>
5965
<TableHead>
6066
<TableRow>
61-
<TableCell width="50%">Name</TableCell>
62-
<TableCell width="49%">Permissions</TableCell>
67+
<TableCell width="40%">Name</TableCell>
68+
<TableCell width="59%">Permissions</TableCell>
6369
<TableCell width="1%" />
6470
</TableRow>
6571
</TableHead>
@@ -116,6 +122,10 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
116122
);
117123
};
118124

125+
function getUniqueResourceTypes(jsonObject: readonly Permission[]) {
126+
const resourceTypes = jsonObject.map((item) => item.resource_type);
127+
return [...new Set(resourceTypes)];
128+
}
119129
interface RoleRowProps {
120130
role: Role;
121131
onDelete: () => void;
@@ -125,12 +135,28 @@ interface RoleRowProps {
125135
const RoleRow: FC<RoleRowProps> = ({ role, onDelete, canAssignOrgRole }) => {
126136
const navigate = useNavigate();
127137

138+
const resourceTypes: string[] = getUniqueResourceTypes(
139+
role.organization_permissions,
140+
);
141+
128142
return (
129143
<TableRow data-testid={`role-${role.name}`}>
130144
<TableCell>{role.display_name || role.name}</TableCell>
131145

132-
<TableCell css={styles.secondary}>
133-
{role.organization_permissions.length}
146+
<TableCell>
147+
<Stack direction="row" spacing={1}>
148+
<PermissionsPill
149+
resource={resourceTypes[0]}
150+
permissions={role.organization_permissions}
151+
/>
152+
153+
{resourceTypes.length > 1 && (
154+
<OverflowPermissionPill
155+
resources={resourceTypes.slice(1)}
156+
permissions={role.organization_permissions.slice(1)}
157+
/>
158+
)}
159+
</Stack>
134160
</TableCell>
135161

136162
<TableCell>
@@ -176,10 +202,98 @@ const TableLoader = () => {
176202
);
177203
};
178204

205+
interface PermissionPillProps {
206+
resource: string;
207+
permissions: readonly Permission[];
208+
}
209+
210+
const PermissionsPill: FC<PermissionPillProps> = ({
211+
resource,
212+
permissions,
213+
}) => {
214+
const actions = permissions.filter((p) => {
215+
if (resource === p.resource_type) {
216+
return p.action;
217+
}
218+
});
219+
220+
return (
221+
<Pill css={styles.permissionPill}>
222+
<b>{resource}</b>: {actions.map((p) => p.action).join(", ")}
223+
</Pill>
224+
);
225+
};
226+
227+
type OverflowPermissionPillProps = {
228+
resources: string[];
229+
permissions: readonly Permission[];
230+
};
231+
232+
const OverflowPermissionPill: FC<OverflowPermissionPillProps> = ({
233+
resources,
234+
permissions,
235+
}) => {
236+
const theme = useTheme();
237+
238+
return (
239+
<Popover mode="hover">
240+
<PopoverTrigger>
241+
<Pill
242+
css={{
243+
backgroundColor: theme.palette.background.paper,
244+
borderColor: theme.palette.divider,
245+
}}
246+
>
247+
+{resources.length} more
248+
</Pill>
249+
</PopoverTrigger>
250+
251+
<PopoverContent
252+
disableRestoreFocus
253+
disableScrollLock
254+
css={{
255+
".MuiPaper-root": {
256+
display: "flex",
257+
flexFlow: "column wrap",
258+
columnGap: 8,
259+
rowGap: 12,
260+
padding: "12px 16px",
261+
alignContent: "space-around",
262+
minWidth: "auto",
263+
backgroundColor: theme.palette.background.default,
264+
},
265+
}}
266+
anchorOrigin={{
267+
vertical: -4,
268+
horizontal: "center",
269+
}}
270+
transformOrigin={{
271+
vertical: "bottom",
272+
horizontal: "center",
273+
}}
274+
>
275+
{resources.map((resource) => (
276+
<PermissionsPill
277+
key={resource}
278+
resource={resource}
279+
permissions={permissions}
280+
/>
281+
))}
282+
</PopoverContent>
283+
</Popover>
284+
);
285+
};
286+
179287
const styles = {
180288
secondary: (theme) => ({
181289
color: theme.palette.text.secondary,
182290
}),
291+
permissionPill: (theme) => ({
292+
backgroundColor: theme.permission.background,
293+
borderColor: theme.permission.outline,
294+
color: theme.permission.text,
295+
width: "fit-content",
296+
}),
183297
} satisfies Record<string, Interpolation<Theme>>;
184298

185299
export default CustomRolesPageView;

site/src/theme/dark/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forDarkThemes } from "../externalImages";
22
import experimental from "./experimental";
33
import monaco from "./monaco";
44
import muiTheme from "./mui";
5+
import permission from "./permission";
56
import roles from "./roles";
67

78
export default {
@@ -10,4 +11,5 @@ export default {
1011
experimental,
1112
monaco,
1213
roles,
14+
permission,
1315
};

site/src/theme/dark/permission.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Permission } from "../permission";
2+
import colors from "../tailwindColors";
3+
4+
export default {
5+
background: colors.zinc[800],
6+
outline: colors.zinc[700],
7+
text: colors.zinc[200],
8+
} satisfies Permission;

site/src/theme/darkBlue/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forDarkThemes } from "../externalImages";
22
import experimental from "./experimental";
33
import monaco from "./monaco";
44
import muiTheme from "./mui";
5+
import permission from "./permission";
56
import roles from "./roles";
67

78
export default {
@@ -10,4 +11,5 @@ export default {
1011
experimental,
1112
monaco,
1213
roles,
14+
permission,
1315
};

site/src/theme/darkBlue/permission.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Permission } from "../permission";
2+
import colors from "../tailwindColors";
3+
4+
export default {
5+
background: colors.gray[800],
6+
outline: colors.gray[700],
7+
text: colors.gray[200],
8+
} satisfies Permission;

site/src/theme/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import darkBlue from "./darkBlue";
66
import type { NewTheme } from "./experimental";
77
import type { ExternalImageModeStyles } from "./externalImages";
88
import light from "./light";
9+
import type { Permission } from "./permission";
910
import type { Roles } from "./roles";
1011

1112
export interface Theme extends MuiTheme {
1213
experimental: NewTheme;
1314
roles: Roles;
15+
permission: Permission;
1416
monaco: monaco.editor.IStandaloneThemeData;
1517
externalImages: ExternalImageModeStyles;
1618
}

site/src/theme/light/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forLightThemes } from "../externalImages";
22
import experimental from "./experimental";
33
import monaco from "./monaco";
44
import muiTheme from "./mui";
5+
import permission from "./permission";
56
import roles from "./roles";
67

78
export default {
@@ -10,4 +11,5 @@ export default {
1011
experimental,
1112
monaco,
1213
roles,
14+
permission,
1315
};

site/src/theme/light/permission.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Permission } from "../permission";
2+
import colors from "../tailwindColors";
3+
4+
export default {
5+
background: colors.zinc[200],
6+
outline: colors.zinc[300],
7+
text: colors.zinc[700],
8+
} satisfies Permission;

site/src/theme/permission.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface Permission {
2+
background: string;
3+
outline: string;
4+
text: string;
5+
}

0 commit comments

Comments
 (0)