Skip to content

Commit 7ff9a07

Browse files
committed
fix: hookup backend data for groups and roles
1 parent a40ba7d commit 7ff9a07

File tree

5 files changed

+206
-109
lines changed

5 files changed

+206
-109
lines changed

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

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,27 @@
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";
411
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
12+
import { Loader } from "components/Loader/Loader";
513
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
614
import { Stack } from "components/Stack/Stack";
15+
import { useDashboard } from "modules/dashboard/useDashboard";
716
import type { FC } from "react";
817
import { Helmet } from "react-helmet-async";
18+
import { useQuery } from "react-query";
919
import { Link as RouterLink, useParams } from "react-router-dom";
1020
import { docs } from "utils/docs";
1121
import { pageTitle } from "utils/page";
22+
import { useOrganizationSettings } from "../ManagementSettingsLayout";
1223
import { IdpSyncHelpTooltip } from "./IdpSyncHelpTooltip";
1324
import IdpSyncPageView from "./IdpSyncPageView";
14-
import {
15-
organizationsPermissions,
16-
groupIdpSyncSettings,
17-
roleIdpSyncSettings,
18-
} from "api/queries/organizations";
19-
import { useQuery } from "react-query";
20-
import { useOrganizationSettings } from "../ManagementSettingsLayout";
21-
import { Loader } from "components/Loader/Loader";
22-
import { EmptyState } from "components/EmptyState/EmptyState";
23-
24-
const mockOIDCConfig = {
25-
allow_signups: true,
26-
client_id: "test",
27-
client_secret: "test",
28-
client_key_file: "test",
29-
client_cert_file: "test",
30-
email_domain: [],
31-
issuer_url: "test",
32-
scopes: [],
33-
ignore_email_verified: true,
34-
username_field: "",
35-
name_field: "",
36-
email_field: "",
37-
auth_url_params: {},
38-
ignore_user_info: true,
39-
organization_field: "",
40-
organization_mapping: {},
41-
organization_assign_default: true,
42-
group_auto_create: false,
43-
group_regex_filter: "^Coder-.*$",
44-
group_allow_list: [],
45-
groups_field: "groups",
46-
group_mapping: { group1: "developers", group2: "admin", group3: "auditors" },
47-
user_role_field: "roles",
48-
user_role_mapping: { role1: ["role1", "role2"] },
49-
user_roles_default: [],
50-
sign_in_text: "",
51-
icon_url: "",
52-
signups_disabled_text: "string",
53-
skip_issuer_checks: true,
54-
};
5525

5626
export const IdpSyncPage: FC = () => {
5727
const { organization: organizationName } = useParams() as {
@@ -64,16 +34,20 @@ export const IdpSyncPage: FC = () => {
6434
// organization: string;
6535
// };
6636
const { organizations } = useOrganizationSettings();
37+
6738
const organization = organizations?.find((o) => o.name === organizationName);
6839
const permissionsQuery = useQuery(
6940
organizationsPermissions(organizations?.map((o) => o.id)),
7041
);
7142
const groupIdpSyncSettingsQuery = useQuery(
7243
groupIdpSyncSettings(organizationName),
7344
);
45+
46+
const groupsQuery = useQuery(groupsByOrganization(organizationName));
7447
const roleIdpSyncSettingsQuery = useQuery(
7548
roleIdpSyncSettings(organizationName),
7649
);
50+
7751
// const permissions = permissionsQuery.data;
7852

7953
if (!organization) {
@@ -121,9 +95,9 @@ export const IdpSyncPage: FC = () => {
12195
</Stack>
12296

12397
<IdpSyncPageView
124-
oidcConfig={mockOIDCConfig}
12598
groupSyncSettings={groupIdpSyncSettingsQuery.data}
12699
roleSyncSettings={roleIdpSyncSettingsQuery.data}
100+
groups={groupsQuery.data}
127101
/>
128102
</>
129103
);

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

Lines changed: 16 additions & 3 deletions
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+
MockGroup,
4+
MockGroup2,
5+
MockGroupSyncSettings,
6+
MockRoleSyncSettings,
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

Lines changed: 48 additions & 32 deletions
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
};
Lines changed: 91 additions & 0 deletions
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)