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

Skip to content

Commit 323559b

Browse files
authored
feat: show warning on unrecognized idp group and role mapping claims (#16485)
1 parent 8a3a79f commit 323559b

File tree

9 files changed

+191
-65
lines changed

9 files changed

+191
-65
lines changed

site/src/api/api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ class ApiMethods {
804804
) => {
805805
const params = new URLSearchParams();
806806
params.set("claimField", field);
807-
const response = await this.axios.get<TypesGen.Response>(
807+
const response = await this.axios.get<readonly string[]>(
808808
`/api/v2/organizations/${organization}/settings/idpsync/field-values?${params}`,
809809
);
810810
return response.data;

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { userEvent, within } from "@storybook/test";
2+
import { expect, userEvent, within } from "@storybook/test";
33
import {
44
MockOrganization,
55
MockOrganization2,
@@ -45,10 +45,16 @@ export const MissingGroups: Story = {
4545
},
4646
};
4747

48-
export const MissingClaim: Story = {
48+
export const MissingClaims: Story = {
4949
args: {
5050
claimFieldValues: [],
5151
},
52+
play: async ({ canvasElement }) => {
53+
const user = userEvent.setup();
54+
const warning = canvasElement.querySelector(".lucide-triangle-alert")!;
55+
expect(warning).not.toBe(null);
56+
await user.hover(warning);
57+
},
5258
};
5359

5460
export const AssignDefaultOrgWarningDialog: Story = {

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { TooltipProvider } from "@radix-ui/react-tooltip";
21
import type {
32
Organization,
43
OrganizationSyncSettings,
@@ -30,7 +29,6 @@ import {
3029
type Option,
3130
} from "components/MultiSelectCombobox/MultiSelectCombobox";
3231
import { Spinner } from "components/Spinner/Spinner";
33-
import { Stack } from "components/Stack/Stack";
3432
import { Switch } from "components/Switch/Switch";
3533
import {
3634
Table,
@@ -42,6 +40,7 @@ import {
4240
import {
4341
Tooltip,
4442
TooltipContent,
43+
TooltipProvider,
4544
TooltipTrigger,
4645
} from "components/Tooltip/Tooltip";
4746
import { useFormik } from "formik";

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx

+56-15
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ import {
2222
} from "components/MultiSelectCombobox/MultiSelectCombobox";
2323
import { Spinner } from "components/Spinner/Spinner";
2424
import { Switch } from "components/Switch/Switch";
25+
import {
26+
Tooltip,
27+
TooltipContent,
28+
TooltipProvider,
29+
TooltipTrigger,
30+
} from "components/Tooltip/Tooltip";
2531
import { useFormik } from "formik";
26-
import { Plus, Trash } from "lucide-react";
32+
import { Plus, Trash, TriangleAlert } from "lucide-react";
2733
import { type FC, useId, useState } from "react";
2834
import { docs } from "utils/docs";
2935
import { isUUID } from "utils/uuid";
@@ -32,16 +38,6 @@ import { ExportPolicyButton } from "./ExportPolicyButton";
3238
import { IdpMappingTable } from "./IdpMappingTable";
3339
import { IdpPillList } from "./IdpPillList";
3440

35-
interface IdpGroupSyncFormProps {
36-
groupSyncSettings: GroupSyncSettings;
37-
groupsMap: Map<string, string>;
38-
groups: Group[];
39-
groupMappingCount: number;
40-
legacyGroupMappingCount: number;
41-
organization: Organization;
42-
onSubmit: (data: GroupSyncSettings) => void;
43-
}
44-
4541
const groupSyncValidationSchema = Yup.object({
4642
field: Yup.string().trim(),
4743
regex_filter: Yup.string().trim(),
@@ -65,15 +61,27 @@ const groupSyncValidationSchema = Yup.object({
6561
.default({}),
6662
});
6763

68-
export const IdpGroupSyncForm = ({
64+
interface IdpGroupSyncFormProps {
65+
groupSyncSettings: GroupSyncSettings;
66+
claimFieldValues: readonly string[] | undefined;
67+
groupsMap: Map<string, string>;
68+
groups: Group[];
69+
groupMappingCount: number;
70+
legacyGroupMappingCount: number;
71+
organization: Organization;
72+
onSubmit: (data: GroupSyncSettings) => void;
73+
}
74+
75+
export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
6976
groupSyncSettings,
77+
claimFieldValues,
7078
groupMappingCount,
7179
legacyGroupMappingCount,
7280
groups,
7381
groupsMap,
7482
organization,
7583
onSubmit,
76-
}: IdpGroupSyncFormProps) => {
84+
}) => {
7785
const form = useFormik<GroupSyncSettings>({
7886
initialValues: {
7987
field: groupSyncSettings?.field ?? "",
@@ -270,6 +278,7 @@ export const IdpGroupSyncForm = ({
270278
<GroupRow
271279
key={idpGroup}
272280
idpGroup={idpGroup}
281+
exists={claimFieldValues?.includes(idpGroup)}
273282
coderGroup={getGroupNames(groups)}
274283
onDelete={handleDelete}
275284
/>
@@ -288,6 +297,7 @@ export const IdpGroupSyncForm = ({
288297
<GroupRow
289298
key={groupId}
290299
idpGroup={idpGroup}
300+
exists={claimFieldValues?.includes(idpGroup)}
291301
coderGroup={getGroupNames([groupId])}
292302
onDelete={handleDelete}
293303
/>
@@ -303,17 +313,48 @@ export const IdpGroupSyncForm = ({
303313

304314
interface GroupRowProps {
305315
idpGroup: string;
316+
exists: boolean | undefined;
306317
coderGroup: readonly string[];
307318
onDelete: (idpOrg: string) => void;
308319
}
309320

310-
const GroupRow: FC<GroupRowProps> = ({ idpGroup, coderGroup, onDelete }) => {
321+
const GroupRow: FC<GroupRowProps> = ({
322+
idpGroup,
323+
exists = true,
324+
coderGroup,
325+
onDelete,
326+
}) => {
311327
return (
312328
<TableRow data-testid={`group-${idpGroup}`}>
313-
<TableCell>{idpGroup}</TableCell>
329+
<TableCell>
330+
<div className="flex flex-row items-center gap-2 text-content-primary">
331+
{idpGroup}
332+
{!exists && (
333+
<TooltipProvider>
334+
<Tooltip>
335+
<TooltipTrigger asChild>
336+
<TriangleAlert className="size-icon-xs cursor-pointer text-content-warning" />
337+
</TooltipTrigger>
338+
<TooltipContent
339+
align="start"
340+
alignOffset={-8}
341+
sideOffset={8}
342+
className="p-2 text-xs text-content-secondary max-w-sm"
343+
>
344+
This value has not be seen in the specified claim field
345+
before. You might want to check your IdP configuration and
346+
ensure that this value is not misspelled.
347+
</TooltipContent>
348+
</Tooltip>
349+
</TooltipProvider>
350+
)}
351+
</div>
352+
</TableCell>
353+
314354
<TableCell>
315355
<IdpPillList roles={coderGroup} />
316356
</TableCell>
357+
317358
<TableCell>
318359
<Button
319360
variant="outline"

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx

+53-13
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@ import {
99
type Option,
1010
} from "components/MultiSelectCombobox/MultiSelectCombobox";
1111
import { Spinner } from "components/Spinner/Spinner";
12+
import {
13+
Tooltip,
14+
TooltipContent,
15+
TooltipProvider,
16+
TooltipTrigger,
17+
} from "components/Tooltip/Tooltip";
1218
import { useFormik } from "formik";
13-
import { Plus, Trash } from "lucide-react";
19+
import { Plus, Trash, TriangleAlert } from "lucide-react";
1420
import { type FC, useId, useState } from "react";
1521
import * as Yup from "yup";
1622
import { ExportPolicyButton } from "./ExportPolicyButton";
1723
import { IdpMappingTable } from "./IdpMappingTable";
1824
import { IdpPillList } from "./IdpPillList";
1925

20-
interface IdpRoleSyncFormProps {
21-
roleSyncSettings: RoleSyncSettings;
22-
roleMappingCount: number;
23-
organization: Organization;
24-
roles: Role[];
25-
onSubmit: (data: RoleSyncSettings) => void;
26-
}
27-
2826
const roleSyncValidationSchema = Yup.object({
2927
field: Yup.string().trim(),
3028
regex_filter: Yup.string().trim(),
@@ -48,13 +46,23 @@ const roleSyncValidationSchema = Yup.object({
4846
.default({}),
4947
});
5048

51-
export const IdpRoleSyncForm = ({
49+
interface IdpRoleSyncFormProps {
50+
roleSyncSettings: RoleSyncSettings;
51+
claimFieldValues: readonly string[] | undefined;
52+
roleMappingCount: number;
53+
organization: Organization;
54+
roles: Role[];
55+
onSubmit: (data: RoleSyncSettings) => void;
56+
}
57+
58+
export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
5259
roleSyncSettings,
60+
claimFieldValues,
5361
roleMappingCount,
5462
organization,
5563
roles,
5664
onSubmit,
57-
}: IdpRoleSyncFormProps) => {
65+
}) => {
5866
const form = useFormik<RoleSyncSettings>({
5967
initialValues: {
6068
field: roleSyncSettings?.field ?? "",
@@ -210,6 +218,7 @@ export const IdpRoleSyncForm = ({
210218
<RoleRow
211219
key={idpRole}
212220
idpRole={idpRole}
221+
exists={claimFieldValues?.includes(idpRole)}
213222
coderRoles={roles}
214223
onDelete={handleDelete}
215224
/>
@@ -222,17 +231,48 @@ export const IdpRoleSyncForm = ({
222231

223232
interface RoleRowProps {
224233
idpRole: string;
234+
exists: boolean | undefined;
225235
coderRoles: readonly string[];
226236
onDelete: (idpOrg: string) => void;
227237
}
228238

229-
const RoleRow: FC<RoleRowProps> = ({ idpRole, coderRoles, onDelete }) => {
239+
const RoleRow: FC<RoleRowProps> = ({
240+
idpRole,
241+
exists = true,
242+
coderRoles,
243+
onDelete,
244+
}) => {
230245
return (
231246
<TableRow data-testid={`role-${idpRole}`}>
232-
<TableCell>{idpRole}</TableCell>
247+
<TableCell>
248+
<div className="flex flex-row items-center gap-2 text-content-primary">
249+
{idpRole}
250+
{!exists && (
251+
<TooltipProvider>
252+
<Tooltip>
253+
<TooltipTrigger asChild>
254+
<TriangleAlert className="size-icon-xs cursor-pointer text-content-warning" />
255+
</TooltipTrigger>
256+
<TooltipContent
257+
align="start"
258+
alignOffset={-8}
259+
sideOffset={8}
260+
className="p-2 text-xs text-content-secondary max-w-sm"
261+
>
262+
This value has not be seen in the specified claim field
263+
before. You might want to check your IdP configuration and
264+
ensure that this value is not misspelled.
265+
</TooltipContent>
266+
</Tooltip>
267+
</TooltipProvider>
268+
)}
269+
</div>
270+
</TableCell>
271+
233272
<TableCell>
234273
<IdpPillList roles={coderRoles} />
235274
</TableCell>
275+
236276
<TableCell>
237277
<Button
238278
variant="outline"

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx

+18-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getErrorMessage } from "api/errors";
22
import { groupsByOrganization } from "api/queries/groups";
33
import {
44
groupIdpSyncSettings,
5+
organizationIdpSyncClaimFieldValues,
56
patchGroupSyncSettings,
67
patchRoleSyncSettings,
78
roleIdpSyncSettings,
@@ -17,8 +18,8 @@ import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
1718
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
1819
import type { FC } from "react";
1920
import { Helmet } from "react-helmet-async";
20-
import { useMutation, useQueries, useQueryClient } from "react-query";
21-
import { useParams } from "react-router-dom";
21+
import { useMutation, useQueries, useQuery, useQueryClient } from "react-query";
22+
import { useParams, useSearchParams } from "react-router-dom";
2223
import { docs } from "utils/docs";
2324
import { pageTitle } from "utils/page";
2425
import IdpSyncPageView from "./IdpSyncPageView";
@@ -47,6 +48,19 @@ export const IdpSyncPage: FC = () => {
4748
],
4849
});
4950

51+
const [searchParams] = useSearchParams();
52+
const tab = searchParams.get("tab") || "groups";
53+
const field =
54+
tab === "groups"
55+
? groupIdpSyncSettingsQuery.data?.field
56+
: roleIdpSyncSettingsQuery.data?.field;
57+
58+
const fieldValuesQuery = useQuery(
59+
field
60+
? organizationIdpSyncClaimFieldValues(organizationName, field)
61+
: { enabled: false },
62+
);
63+
5064
if (!organization) {
5165
return <EmptyState message="Organization not found" />;
5266
}
@@ -99,8 +113,10 @@ export const IdpSyncPage: FC = () => {
99113
</Cond>
100114
<Cond>
101115
<IdpSyncPageView
116+
tab={tab}
102117
groupSyncSettings={groupIdpSyncSettingsQuery.data}
103118
roleSyncSettings={roleIdpSyncSettingsQuery.data}
119+
claimFieldValues={fieldValuesQuery.data}
104120
groups={groupsQuery.data}
105121
groupsMap={groupsMap}
106122
roles={rolesQuery.data}

0 commit comments

Comments
 (0)