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

Skip to content

Commit 093d243

Browse files
authored
feat: add resource-action pills to custom roles table (#14354)
* feat: add resource-action pills to custom roles table * fix: remove permission from theme and change name to colorRoles * fix: revert name from colorRoles to roles * fix: format * fix: custom role with no permissions * feat: extract permissions pull list component and add tests * chore: undo color roles name change * feat: add experimental pill colors * fix: format * chore: update experiment name * chore: cleanup
1 parent 4421063 commit 093d243

File tree

9 files changed

+254
-8
lines changed

9 files changed

+254
-8
lines changed

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

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { MockRoleWithOrgPermissions } from "testHelpers/entities";
2+
import {
3+
MockOrganizationAuditorRole,
4+
MockRoleWithOrgPermissions,
5+
} from "testHelpers/entities";
36
import { CustomRolesPageView } from "./CustomRolesPageView";
47

58
const meta: Meta<typeof CustomRolesPageView> = {
@@ -26,6 +29,14 @@ export const Enabled: Story = {
2629
},
2730
};
2831

32+
export const RoleWithoutPermissions: Story = {
33+
args: {
34+
roles: [MockOrganizationAuditorRole],
35+
canAssignOrgRole: true,
36+
isCustomRolesEnabled: true,
37+
},
38+
};
39+
2940
export const EmptyDisplayName: Story = {
3041
args: {
3142
roles: [
@@ -40,15 +51,15 @@ export const EmptyDisplayName: Story = {
4051
},
4152
};
4253

43-
export const EmptyRoleWithoutPermission: Story = {
54+
export const EmptyTableUserWithoutPermission: Story = {
4455
args: {
4556
roles: [],
4657
canAssignOrgRole: false,
4758
isCustomRolesEnabled: true,
4859
},
4960
};
5061

51-
export const EmptyRoleWithPermission: Story = {
62+
export const EmptyTableUserWithPermission: Story = {
5263
args: {
5364
roles: [],
5465
canAssignOrgRole: true,

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import type { FC } from "react";
2727
import { Link as RouterLink, useNavigate } from "react-router-dom";
2828
import { docs } from "utils/docs";
29+
import { PermissionPillsList } from "./PermissionPillsList";
2930

3031
export type CustomRolesPageViewProps = {
3132
roles: Role[] | undefined;
@@ -42,7 +43,6 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
4243
}) => {
4344
const isLoading = roles === undefined;
4445
const isEmpty = Boolean(roles && roles.length === 0);
45-
4646
return (
4747
<>
4848
<ChooseOne>
@@ -58,8 +58,8 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
5858
<Table>
5959
<TableHead>
6060
<TableRow>
61-
<TableCell width="50%">Name</TableCell>
62-
<TableCell width="49%">Permissions</TableCell>
61+
<TableCell width="40%">Name</TableCell>
62+
<TableCell width="59%">Permissions</TableCell>
6363
<TableCell width="1%" />
6464
</TableRow>
6565
</TableHead>
@@ -129,8 +129,8 @@ const RoleRow: FC<RoleRowProps> = ({ role, onDelete, canAssignOrgRole }) => {
129129
<TableRow data-testid={`role-${role.name}`}>
130130
<TableCell>{role.display_name || role.name}</TableCell>
131131

132-
<TableCell css={styles.secondary}>
133-
{role.organization_permissions.length}
132+
<TableCell>
133+
<PermissionPillsList permissions={role.organization_permissions} />
134134
</TableCell>
135135

136136
<TableCell>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { userEvent, within } from "@storybook/test";
3+
import { MockRoleWithOrgPermissions } from "testHelpers/entities";
4+
import { PermissionPillsList } from "./PermissionPillsList";
5+
6+
const meta: Meta<typeof PermissionPillsList> = {
7+
title: "pages/OrganizationCustomRolesPage/PermissionPillsList",
8+
component: PermissionPillsList,
9+
};
10+
11+
export default meta;
12+
type Story = StoryObj<typeof PermissionPillsList>;
13+
14+
export const Default: Story = {
15+
args: {
16+
permissions: MockRoleWithOrgPermissions.organization_permissions,
17+
},
18+
};
19+
20+
export const SinglePermission: Story = {
21+
args: {
22+
permissions: [
23+
{
24+
negate: false,
25+
resource_type: "organization_member",
26+
action: "create",
27+
},
28+
],
29+
},
30+
};
31+
32+
export const NoPermissions: Story = {
33+
args: {
34+
permissions: [],
35+
},
36+
};
37+
38+
export const HoverOverflowPill: Story = {
39+
args: {
40+
permissions: MockRoleWithOrgPermissions.organization_permissions,
41+
},
42+
play: async ({ canvasElement }) => {
43+
const canvas = within(canvasElement);
44+
await userEvent.hover(canvas.getByTestId("overflow-permissions-pill"));
45+
},
46+
};
47+
48+
export const ShowAllResources: Story = {
49+
args: {
50+
permissions: MockRoleWithOrgPermissions.organization_permissions,
51+
},
52+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
2+
import Stack from "@mui/material/Stack";
3+
import type { Permission } from "api/typesGenerated";
4+
import { Pill } from "components/Pill/Pill";
5+
import {
6+
Popover,
7+
PopoverContent,
8+
PopoverTrigger,
9+
} from "components/Popover/Popover";
10+
import type { FC } from "react";
11+
12+
function getUniqueResourceTypes(jsonObject: readonly Permission[]) {
13+
const resourceTypes = jsonObject.map((item) => item.resource_type);
14+
return [...new Set(resourceTypes)];
15+
}
16+
17+
interface PermissionPillsListProps {
18+
permissions: readonly Permission[];
19+
}
20+
21+
export const PermissionPillsList: FC<PermissionPillsListProps> = ({
22+
permissions,
23+
}) => {
24+
const resourceTypes = getUniqueResourceTypes(permissions);
25+
26+
return (
27+
<Stack direction="row" spacing={1}>
28+
{permissions.length > 0 ? (
29+
<PermissionsPill
30+
resource={resourceTypes[0]}
31+
permissions={permissions}
32+
/>
33+
) : (
34+
<p>None</p>
35+
)}
36+
37+
{resourceTypes.length > 1 && (
38+
<OverflowPermissionPill
39+
resources={resourceTypes.slice(1)}
40+
permissions={permissions.slice(1)}
41+
/>
42+
)}
43+
</Stack>
44+
);
45+
};
46+
47+
interface PermissionPillProps {
48+
resource: string;
49+
permissions: readonly Permission[];
50+
}
51+
52+
const PermissionsPill: FC<PermissionPillProps> = ({
53+
resource,
54+
permissions,
55+
}) => {
56+
const actions = permissions.filter(
57+
(p) => resource === p.resource_type && p.action,
58+
);
59+
60+
return (
61+
<Pill css={styles.permissionPill}>
62+
<b>{resource}</b>: {actions.map((p) => p.action).join(", ")}
63+
</Pill>
64+
);
65+
};
66+
67+
type OverflowPermissionPillProps = {
68+
resources: string[];
69+
permissions: readonly Permission[];
70+
};
71+
72+
const OverflowPermissionPill: FC<OverflowPermissionPillProps> = ({
73+
resources,
74+
permissions,
75+
}) => {
76+
const theme = useTheme();
77+
78+
return (
79+
<Popover mode="hover">
80+
<PopoverTrigger>
81+
<Pill
82+
css={{
83+
backgroundColor: theme.palette.background.paper,
84+
borderColor: theme.palette.divider,
85+
}}
86+
data-testid="overflow-permissions-pill"
87+
>
88+
+{resources.length} more
89+
</Pill>
90+
</PopoverTrigger>
91+
92+
<PopoverContent
93+
disableRestoreFocus
94+
disableScrollLock
95+
css={{
96+
".MuiPaper-root": {
97+
display: "flex",
98+
flexFlow: "column wrap",
99+
columnGap: 8,
100+
rowGap: 12,
101+
padding: "12px 16px",
102+
alignContent: "space-around",
103+
minWidth: "auto",
104+
backgroundColor: theme.palette.background.default,
105+
},
106+
}}
107+
anchorOrigin={{
108+
vertical: -4,
109+
horizontal: "center",
110+
}}
111+
transformOrigin={{
112+
vertical: "bottom",
113+
horizontal: "center",
114+
}}
115+
>
116+
{resources.map((resource) => (
117+
<PermissionsPill
118+
key={resource}
119+
resource={resource}
120+
permissions={permissions}
121+
/>
122+
))}
123+
</PopoverContent>
124+
</Popover>
125+
);
126+
};
127+
128+
const styles = {
129+
permissionPill: (theme) => ({
130+
backgroundColor: theme.experimental.pillDefault.background,
131+
borderColor: theme.experimental.pillDefault.outline,
132+
color: theme.experimental.pillDefault.text,
133+
width: "fit-content",
134+
}),
135+
} satisfies Record<string, Interpolation<Theme>>;

site/src/testHelpers/entities.ts

+25
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,31 @@ export const MockRoleWithOrgPermissions: TypesGen.Role = {
388388
resource_type: "audit_log",
389389
action: "read",
390390
},
391+
{
392+
negate: false,
393+
resource_type: "group",
394+
action: "create",
395+
},
396+
{
397+
negate: false,
398+
resource_type: "group",
399+
action: "delete",
400+
},
401+
{
402+
negate: false,
403+
resource_type: "group",
404+
action: "read",
405+
},
406+
{
407+
negate: false,
408+
resource_type: "group",
409+
action: "update",
410+
},
411+
{
412+
negate: false,
413+
resource_type: "provisioner_daemon",
414+
action: "create",
415+
},
391416
],
392417
user_permissions: [],
393418
};

site/src/theme/dark/experimental.ts

+6
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ export default {
4343
},
4444
},
4545
},
46+
47+
pillDefault: {
48+
background: colors.zinc[800],
49+
outline: colors.zinc[700],
50+
text: colors.zinc[200],
51+
},
4652
} satisfies NewTheme;

site/src/theme/darkBlue/experimental.ts

+6
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ export default {
4343
},
4444
},
4545
},
46+
47+
pillDefault: {
48+
background: colors.gray[800],
49+
outline: colors.gray[700],
50+
text: colors.gray[200],
51+
},
4652
} satisfies NewTheme;

site/src/theme/experimental.ts

+5
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,9 @@ import type { InteractiveRole, Role } from "./roles";
33
export interface NewTheme {
44
l1: Role; // page background, things which sit at the "root level"
55
l2: InteractiveRole; // sidebars, table headers, navigation
6+
pillDefault: {
7+
background: string;
8+
outline: string;
9+
text: string;
10+
};
611
}

site/src/theme/light/experimental.ts

+6
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ export default {
4343
},
4444
},
4545
},
46+
47+
pillDefault: {
48+
background: colors.zinc[200],
49+
outline: colors.zinc[300],
50+
text: colors.zinc[700],
51+
},
4652
} satisfies NewTheme;

0 commit comments

Comments
 (0)