Skip to content

Commit ef7239a

Browse files
committed
fix: hookup backend data for groups and roles
1 parent 6790f98 commit ef7239a

File tree

5 files changed

+206
-109
lines changed

5 files changed

+206
-109
lines changed

site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPage.tsx

+16-42
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,26 @@
11
import AddIcon from "@mui/icons-material/AddOutlined";
22
import LaunchOutlined from "@mui/icons-material/LaunchOutlined";
33
import Button from "@mui/material/Button";
4+
import { groupsByOrganization } from "api/queries/groups";
5+
import {
6+
groupIdpSyncSettings,
7+
organizationsPermissions,
8+
roleIdpSyncSettings,
9+
} from "api/queries/organizations";
10+
import { EmptyState } from "components/EmptyState/EmptyState";
11+
import { Loader } from "components/Loader/Loader";
412
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
513
import { Stack } from "components/Stack/Stack";
14+
import { useDashboard } from "modules/dashboard/useDashboard";
615
import type { FC } from "react";
716
import { Helmet } from "react-helmet-async";
17+
import { useQuery } from "react-query";
818
import { Link as RouterLink, useParams } from "react-router-dom";
919
import { docs } from "utils/docs";
1020
import { pageTitle } from "utils/page";
21+
import { useOrganizationSettings } from "../ManagementSettingsLayout";
1122
import { IdpSyncHelpTooltip } from "./IdpSyncHelpTooltip";
1223
import IdpSyncPageView from "./IdpSyncPageView";
13-
import {
14-
organizationsPermissions,
15-
groupIdpSyncSettings,
16-
roleIdpSyncSettings,
17-
} from "api/queries/organizations";
18-
import { useQuery } from "react-query";
19-
import { useOrganizationSettings } from "../ManagementSettingsLayout";
20-
import { Loader } from "components/Loader/Loader";
21-
import { EmptyState } from "components/EmptyState/EmptyState";
22-
23-
const mockOIDCConfig = {
24-
allow_signups: true,
25-
client_id: "test",
26-
client_secret: "test",
27-
client_key_file: "test",
28-
client_cert_file: "test",
29-
email_domain: [],
30-
issuer_url: "test",
31-
scopes: [],
32-
ignore_email_verified: true,
33-
username_field: "",
34-
name_field: "",
35-
email_field: "",
36-
auth_url_params: {},
37-
ignore_user_info: true,
38-
organization_field: "",
39-
organization_mapping: {},
40-
organization_assign_default: true,
41-
group_auto_create: false,
42-
group_regex_filter: "^Coder-.*$",
43-
group_allow_list: [],
44-
groups_field: "groups",
45-
group_mapping: { group1: "developers", group2: "admin", group3: "auditors" },
46-
user_role_field: "roles",
47-
user_role_mapping: { role1: ["role1", "role2"] },
48-
user_roles_default: [],
49-
sign_in_text: "",
50-
icon_url: "",
51-
signups_disabled_text: "string",
52-
skip_issuer_checks: true,
53-
};
5424

5525
export const IdpSyncPage: FC = () => {
5626
const { organization: organizationName } = useParams() as {
@@ -63,16 +33,20 @@ export const IdpSyncPage: FC = () => {
6333
// organization: string;
6434
// };
6535
const { organizations } = useOrganizationSettings();
36+
6637
const organization = organizations?.find((o) => o.name === organizationName);
6738
const permissionsQuery = useQuery(
6839
organizationsPermissions(organizations?.map((o) => o.id)),
6940
);
7041
const groupIdpSyncSettingsQuery = useQuery(
7142
groupIdpSyncSettings(organizationName),
7243
);
44+
45+
const groupsQuery = useQuery(groupsByOrganization(organizationName));
7346
const roleIdpSyncSettingsQuery = useQuery(
7447
roleIdpSyncSettings(organizationName),
7548
);
49+
7650
// const permissions = permissionsQuery.data;
7751

7852
if (!organization) {
@@ -119,9 +93,9 @@ export const IdpSyncPage: FC = () => {
11993
</Stack>
12094

12195
<IdpSyncPageView
122-
oidcConfig={mockOIDCConfig}
12396
groupSyncSettings={groupIdpSyncSettingsQuery.data}
12497
roleSyncSettings={roleIdpSyncSettingsQuery.data}
98+
groups={groupsQuery.data}
12599
/>
126100
</>
127101
);

site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPageView.stories.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { MockOIDCConfig } from "testHelpers/entities";
2+
import {
3+
MockGroupSyncSettings,
4+
MockRoleSyncSettings,
5+
MockGroup,
6+
MockGroup2,
7+
} from "testHelpers/entities";
38
import { IdpSyncPageView } from "./IdpSyncPageView";
49

510
const meta: Meta<typeof IdpSyncPageView> = {
@@ -11,9 +16,17 @@ export default meta;
1116
type Story = StoryObj<typeof IdpSyncPageView>;
1217

1318
export const Empty: Story = {
14-
args: { oidcConfig: undefined },
19+
args: {
20+
groupSyncSettings: undefined,
21+
roleSyncSettings: undefined,
22+
groups: [MockGroup, MockGroup2],
23+
},
1524
};
1625

1726
export const Default: Story = {
18-
args: { oidcConfig: MockOIDCConfig },
27+
args: {
28+
groupSyncSettings: MockGroupSyncSettings,
29+
roleSyncSettings: MockRoleSyncSettings,
30+
groups: [MockGroup, MockGroup2],
31+
},
1932
};

site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPageView.tsx

+48-32
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import TableContainer from "@mui/material/TableContainer";
1010
import TableHead from "@mui/material/TableHead";
1111
import TableRow from "@mui/material/TableRow";
1212
import type {
13-
OIDCConfig,
13+
Group,
1414
GroupSyncSettings,
1515
RoleSyncSettings,
1616
} from "api/typesGenerated";
@@ -26,20 +26,32 @@ import {
2626
import type { FC } from "react";
2727
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
2828
import { docs } from "utils/docs";
29+
import { PillList } from "./PillList";
2930

3031
export type IdpSyncPageViewProps = {
31-
oidcConfig: OIDCConfig | undefined;
3232
groupSyncSettings: GroupSyncSettings | undefined;
3333
roleSyncSettings: RoleSyncSettings | undefined;
34+
groups: Group[] | undefined;
3435
};
3536

3637
export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
37-
oidcConfig,
3838
groupSyncSettings,
3939
roleSyncSettings,
40+
groups,
4041
}) => {
41-
const theme = useTheme();
42-
const { user_role_field } = oidcConfig || {};
42+
// const theme = useTheme();
43+
44+
const groupsMap = new Map<string, string>();
45+
if (groups) {
46+
for (const group of groups) {
47+
groupsMap.set(group.id, group.display_name || group.name);
48+
}
49+
}
50+
51+
const getGroupNames = (groupIds: readonly string[]) => {
52+
return groupIds.map((groupId) => groupsMap.get(groupId) || groupId);
53+
};
54+
4355
return (
4456
<>
4557
<ChooseOne>
@@ -67,13 +79,13 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
6779
fieldText={
6880
typeof groupSyncSettings?.regex_filter === "string"
6981
? groupSyncSettings?.regex_filter
70-
: ""
82+
: "none"
7183
}
7284
/>
7385
<IdpField
7486
name={"Auto Create"}
7587
fieldText={String(
76-
groupSyncSettings?.auto_create_missing_groups,
88+
groupSyncSettings?.auto_create_missing_groups || "n/a",
7789
)}
7890
/>
7991
</Stack>
@@ -83,46 +95,46 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
8395
<Stack direction={"row"} alignItems={"center"} spacing={3}>
8496
<IdpField
8597
name={"Sync Field"}
86-
fieldText={user_role_field}
98+
fieldText={roleSyncSettings?.field}
8799
showStatusIndicator
88100
/>
89101
</Stack>
90102
</fieldset>
91103
</Stack>
92104
<Stack spacing={6}>
93105
<IdpMappingTable
94-
type="Role"
106+
type="Group"
95107
isEmpty={Boolean(
96-
!oidcConfig?.user_role_mapping ||
97-
Object.entries(oidcConfig?.user_role_mapping).length === 0,
108+
!groupSyncSettings?.mapping ||
109+
Object.entries(groupSyncSettings?.mapping).length === 0,
98110
)}
99111
>
100-
{oidcConfig?.user_role_mapping &&
101-
Object.entries(oidcConfig.user_role_mapping)
112+
{groupSyncSettings?.mapping &&
113+
Object.entries(groupSyncSettings.mapping)
102114
.sort()
103-
.map(([idpRole, roles]) => (
104-
<RoleRow
105-
key={idpRole}
106-
idpRole={idpRole}
107-
coderRoles={roles}
115+
.map(([idpGroup, groups]) => (
116+
<GroupRow
117+
key={idpGroup}
118+
idpGroup={idpGroup}
119+
coderGroup={getGroupNames(groups)}
108120
/>
109121
))}
110122
</IdpMappingTable>
111123
<IdpMappingTable
112-
type="Group"
124+
type="Role"
113125
isEmpty={Boolean(
114-
!oidcConfig?.group_mapping ||
115-
Object.entries(oidcConfig?.group_mapping).length === 0,
126+
!roleSyncSettings?.mapping ||
127+
Object.entries(roleSyncSettings?.mapping).length === 0,
116128
)}
117129
>
118-
{oidcConfig?.user_role_mapping &&
119-
Object.entries(oidcConfig.group_mapping)
130+
{roleSyncSettings?.mapping &&
131+
Object.entries(roleSyncSettings.mapping)
120132
.sort()
121-
.map(([idpGroup, group]) => (
122-
<GroupRow
123-
key={idpGroup}
124-
idpGroup={idpGroup}
125-
coderGroup={group}
133+
.map(([idpRole, roles]) => (
134+
<RoleRow
135+
key={idpRole}
136+
idpRole={idpRole}
137+
coderRoles={roles}
126138
/>
127139
))}
128140
</IdpMappingTable>
@@ -226,28 +238,32 @@ const IdpMappingTable: FC<IdpMappingTableProps> = ({
226238

227239
interface GroupRowProps {
228240
idpGroup: string;
229-
coderGroup: string;
241+
coderGroup: readonly string[];
230242
}
231243

232244
const GroupRow: FC<GroupRowProps> = ({ idpGroup, coderGroup }) => {
233245
return (
234246
<TableRow data-testid={`group-${idpGroup}`}>
235247
<TableCell>{idpGroup}</TableCell>
236-
<TableCell>{coderGroup}</TableCell>
248+
<TableCell>
249+
<PillList roles={coderGroup} />
250+
</TableCell>
237251
</TableRow>
238252
);
239253
};
240254

241255
interface RoleRowProps {
242256
idpRole: string;
243-
coderRoles: ReadonlyArray<string>;
257+
coderRoles: readonly string[];
244258
}
245259

246260
const RoleRow: FC<RoleRowProps> = ({ idpRole, coderRoles }) => {
247261
return (
248262
<TableRow data-testid={`role-${idpRole}`}>
249263
<TableCell>{idpRole}</TableCell>
250-
<TableCell>coderRoles Placeholder</TableCell>
264+
<TableCell>
265+
<PillList roles={coderRoles} />
266+
</TableCell>
251267
</TableRow>
252268
);
253269
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
2+
import Stack from "@mui/material/Stack";
3+
import { Pill } from "components/Pill/Pill";
4+
import {
5+
Popover,
6+
PopoverContent,
7+
PopoverTrigger,
8+
} from "components/Popover/Popover";
9+
import type { FC } from "react";
10+
11+
interface PillListProps {
12+
roles: readonly string[];
13+
}
14+
15+
export const PillList: FC<PillListProps> = ({ roles }) => {
16+
return (
17+
<Stack direction="row" spacing={1}>
18+
{roles.length > 0 ? (
19+
<Pill css={styles.pill}>{roles[0]}</Pill>
20+
) : (
21+
<p>None</p>
22+
)}
23+
24+
{roles.length > 1 && <OverflowPill roles={roles.slice(1)} />}
25+
</Stack>
26+
);
27+
};
28+
29+
type OverflowPillProps = {
30+
roles: string[];
31+
};
32+
33+
const OverflowPill: FC<OverflowPillProps> = ({ roles }) => {
34+
const theme = useTheme();
35+
36+
return (
37+
<Popover mode="hover">
38+
<PopoverTrigger>
39+
<Pill
40+
css={{
41+
backgroundColor: theme.palette.background.paper,
42+
borderColor: theme.palette.divider,
43+
}}
44+
data-testid="overflow-pill"
45+
>
46+
+{roles.length} more
47+
</Pill>
48+
</PopoverTrigger>
49+
50+
<PopoverContent
51+
disableRestoreFocus
52+
disableScrollLock
53+
css={{
54+
".MuiPaper-root": {
55+
display: "flex",
56+
flexFlow: "column wrap",
57+
columnGap: 8,
58+
rowGap: 12,
59+
padding: "12px 16px",
60+
alignContent: "space-around",
61+
minWidth: "auto",
62+
backgroundColor: theme.palette.background.default,
63+
},
64+
}}
65+
anchorOrigin={{
66+
vertical: -4,
67+
horizontal: "center",
68+
}}
69+
transformOrigin={{
70+
vertical: "bottom",
71+
horizontal: "center",
72+
}}
73+
>
74+
{roles.map((role) => (
75+
<Pill key={role} css={styles.pill}>
76+
{role}
77+
</Pill>
78+
))}
79+
</PopoverContent>
80+
</Popover>
81+
);
82+
};
83+
84+
const styles = {
85+
pill: (theme) => ({
86+
backgroundColor: theme.experimental.pillDefault.background,
87+
borderColor: theme.experimental.pillDefault.outline,
88+
color: theme.experimental.pillDefault.text,
89+
width: "fit-content",
90+
}),
91+
} satisfies Record<string, Interpolation<Theme>>;

0 commit comments

Comments
 (0)