From 323559ba82c6057c0b78ac2d058ff035707e5e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B1=E3=82=A4=E3=83=A9?= Date: Fri, 7 Feb 2025 11:41:22 -0700 Subject: [PATCH] feat: show warning on unrecognized idp group and role mapping claims (#16485) --- site/src/api/api.ts | 2 +- .../IdpOrgSyncPageView.stories.tsx | 10 ++- .../IdpOrgSyncPage/IdpOrgSyncPageView.tsx | 3 +- .../IdpSyncPage/IdpGroupSyncForm.tsx | 71 +++++++++++++++---- .../IdpSyncPage/IdpRoleSyncForm.tsx | 66 +++++++++++++---- .../IdpSyncPage/IdpSyncPage.tsx | 20 +++++- .../IdpSyncPage/IdpSyncPageView.stories.tsx | 71 ++++++++++++------- .../IdpSyncPage/IdpSyncPageView.tsx | 9 ++- site/src/testHelpers/entities.ts | 4 +- 9 files changed, 191 insertions(+), 65 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 5a314ddde1..43051961fa 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -804,7 +804,7 @@ class ApiMethods { ) => { const params = new URLSearchParams(); params.set("claimField", field); - const response = await this.axios.get( + const response = await this.axios.get( `/api/v2/organizations/${organization}/settings/idpsync/field-values?${params}`, ); return response.data; diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx index 78842737e5..430fce3a2e 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { userEvent, within } from "@storybook/test"; +import { expect, userEvent, within } from "@storybook/test"; import { MockOrganization, MockOrganization2, @@ -45,10 +45,16 @@ export const MissingGroups: Story = { }, }; -export const MissingClaim: Story = { +export const MissingClaims: Story = { args: { claimFieldValues: [], }, + play: async ({ canvasElement }) => { + const user = userEvent.setup(); + const warning = canvasElement.querySelector(".lucide-triangle-alert")!; + expect(warning).not.toBe(null); + await user.hover(warning); + }, }; export const AssignDefaultOrgWarningDialog: Story = { diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx index f6822ba0a6..bdcc65b89a 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx @@ -1,4 +1,3 @@ -import { TooltipProvider } from "@radix-ui/react-tooltip"; import type { Organization, OrganizationSyncSettings, @@ -30,7 +29,6 @@ import { type Option, } from "components/MultiSelectCombobox/MultiSelectCombobox"; import { Spinner } from "components/Spinner/Spinner"; -import { Stack } from "components/Stack/Stack"; import { Switch } from "components/Switch/Switch"; import { Table, @@ -42,6 +40,7 @@ import { import { Tooltip, TooltipContent, + TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; import { useFormik } from "formik"; diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx index 2f1c0be7fa..9d63baf180 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx @@ -22,8 +22,14 @@ import { } from "components/MultiSelectCombobox/MultiSelectCombobox"; import { Spinner } from "components/Spinner/Spinner"; import { Switch } from "components/Switch/Switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import { useFormik } from "formik"; -import { Plus, Trash } from "lucide-react"; +import { Plus, Trash, TriangleAlert } from "lucide-react"; import { type FC, useId, useState } from "react"; import { docs } from "utils/docs"; import { isUUID } from "utils/uuid"; @@ -32,16 +38,6 @@ import { ExportPolicyButton } from "./ExportPolicyButton"; import { IdpMappingTable } from "./IdpMappingTable"; import { IdpPillList } from "./IdpPillList"; -interface IdpGroupSyncFormProps { - groupSyncSettings: GroupSyncSettings; - groupsMap: Map; - groups: Group[]; - groupMappingCount: number; - legacyGroupMappingCount: number; - organization: Organization; - onSubmit: (data: GroupSyncSettings) => void; -} - const groupSyncValidationSchema = Yup.object({ field: Yup.string().trim(), regex_filter: Yup.string().trim(), @@ -65,15 +61,27 @@ const groupSyncValidationSchema = Yup.object({ .default({}), }); -export const IdpGroupSyncForm = ({ +interface IdpGroupSyncFormProps { + groupSyncSettings: GroupSyncSettings; + claimFieldValues: readonly string[] | undefined; + groupsMap: Map; + groups: Group[]; + groupMappingCount: number; + legacyGroupMappingCount: number; + organization: Organization; + onSubmit: (data: GroupSyncSettings) => void; +} + +export const IdpGroupSyncForm: FC = ({ groupSyncSettings, + claimFieldValues, groupMappingCount, legacyGroupMappingCount, groups, groupsMap, organization, onSubmit, -}: IdpGroupSyncFormProps) => { +}) => { const form = useFormik({ initialValues: { field: groupSyncSettings?.field ?? "", @@ -270,6 +278,7 @@ export const IdpGroupSyncForm = ({ @@ -288,6 +297,7 @@ export const IdpGroupSyncForm = ({ @@ -303,17 +313,48 @@ export const IdpGroupSyncForm = ({ interface GroupRowProps { idpGroup: string; + exists: boolean | undefined; coderGroup: readonly string[]; onDelete: (idpOrg: string) => void; } -const GroupRow: FC = ({ idpGroup, coderGroup, onDelete }) => { +const GroupRow: FC = ({ + idpGroup, + exists = true, + coderGroup, + onDelete, +}) => { return ( - {idpGroup} + +
+ {idpGroup} + {!exists && ( + + + + + + + This value has not be seen in the specified claim field + before. You might want to check your IdP configuration and + ensure that this value is not misspelled. + + + + )} +
+
+ +