diff --git a/site/src/api/api.ts b/site/src/api/api.ts index cd21b5b063ac6..5a314ddde151a 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -698,7 +698,7 @@ class ApiMethods { } const response = await this.axios.get( - `/api/v2/organizations/${organization}/provisionerdaemons?${params.toString()}`, + `/api/v2/organizations/${organization}/provisionerdaemons?${params}`, ); return response.data; }; @@ -787,19 +787,25 @@ class ApiMethods { return response.data; }; - getIdpSyncClaimFieldValues = async (claimField: string) => { - const response = await this.axios.get( - `/api/v2/settings/idpsync/field-values?claimField=${claimField}`, + getDeploymentIdpSyncFieldValues = async ( + field: string, + ): Promise => { + const params = new URLSearchParams(); + params.set("claimField", field); + const response = await this.axios.get( + `/api/v2/settings/idpsync/field-values?${params}`, ); return response.data; }; - getIdpSyncClaimFieldValuesByOrganization = async ( + getOrganizationIdpSyncClaimFieldValues = async ( organization: string, - claimField: string, + field: string, ) => { + const params = new URLSearchParams(); + params.set("claimField", field); const response = await this.axios.get( - `/api/v2/organizations/${organization}/settings/idpsync/field-values?claimField=${claimField}`, + `/api/v2/organizations/${organization}/settings/idpsync/field-values?${params}`, ); return response.data; }; diff --git a/site/src/api/queries/deployment.ts b/site/src/api/queries/deployment.ts index 62449af12fccf..999dd2ee4cbd5 100644 --- a/site/src/api/queries/deployment.ts +++ b/site/src/api/queries/deployment.ts @@ -29,3 +29,10 @@ export const deploymentSSHConfig = () => { queryFn: API.getDeploymentSSHConfig, }; }; + +export const deploymentIdpSyncFieldValues = (field: string) => { + return { + queryKey: ["deployment", "idpSync", "fieldValues", field], + queryFn: () => API.getDeploymentIdpSyncFieldValues(field), + }; +}; diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 33ef19f0d2654..6246664e6ecf0 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -341,32 +341,16 @@ export const organizationsPermissions = ( export const getOrganizationIdpSyncClaimFieldValuesKey = ( organization: string, - claimField: string, -) => [organization, claimField, "organizationIdpSyncClaimFieldValues"]; + field: string, +) => [organization, "idpSync", "fieldValues", field]; export const organizationIdpSyncClaimFieldValues = ( organization: string, - claimField: string, + field: string, ) => { return { - queryKey: getOrganizationIdpSyncClaimFieldValuesKey( - organization, - claimField, - ), + queryKey: getOrganizationIdpSyncClaimFieldValuesKey(organization, field), queryFn: () => - API.getIdpSyncClaimFieldValuesByOrganization(organization, claimField), - }; -}; - -export const getIdpSyncClaimFieldValuesKey = (claimField: string) => [ - claimField, - "idpSyncClaimFieldValues", -]; - -export const idpSyncClaimFieldValues = (claimField: string) => { - return { - queryKey: getIdpSyncClaimFieldValuesKey(claimField), - queryFn: () => API.getIdpSyncClaimFieldValues(claimField), - enabled: !!claimField, + API.getOrganizationIdpSyncClaimFieldValues(organization, field), }; }; diff --git a/site/src/components/Combobox/Combobox.tsx b/site/src/components/Combobox/Combobox.tsx index f5447b3a87062..fa15b6808a05e 100644 --- a/site/src/components/Combobox/Combobox.tsx +++ b/site/src/components/Combobox/Combobox.tsx @@ -18,7 +18,7 @@ import { cn } from "utils/cn"; interface ComboboxProps { value: string; - options?: string[]; + options?: readonly string[]; placeholder?: string; open: boolean; onOpenChange: (open: boolean) => void; diff --git a/site/src/components/Tooltip/Tooltip.stories.tsx b/site/src/components/Tooltip/Tooltip.stories.tsx index 68561b6a189e3..9af79ca76c099 100644 --- a/site/src/components/Tooltip/Tooltip.stories.tsx +++ b/site/src/components/Tooltip/Tooltip.stories.tsx @@ -12,16 +12,12 @@ const meta: Meta = { component: TooltipProvider, args: { children: ( - <> - - - - - - Add to library - - - + + + + + Add to library + ), }, }; diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx index 4d5b53e0f3ea2..295b482f94286 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx @@ -1,9 +1,9 @@ import { getErrorMessage } from "api/errors"; +import { deploymentIdpSyncFieldValues } from "api/queries/deployment"; import { organizationIdpSyncSettings, patchOrganizationSyncSettings, } from "api/queries/idpsync"; -import { idpSyncClaimFieldValues } from "api/queries/organizations"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; import { displayError } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils"; @@ -21,26 +21,23 @@ import { ExportPolicyButton } from "./ExportPolicyButton"; import IdpOrgSyncPageView from "./IdpOrgSyncPageView"; export const IdpOrgSyncPage: FC = () => { - const [claimField, setClaimField] = useState(""); const queryClient = useQueryClient(); // IdP sync does not have its own entitlement and is based on templace_rbac const { template_rbac: isIdpSyncEnabled } = useFeatureVisibility(); const { organizations } = useDashboard(); - const { - data: orgSyncSettingsData, - isLoading, - error, - } = useQuery({ - ...organizationIdpSyncSettings(isIdpSyncEnabled), - onSuccess: (data) => { - if (data?.field) { - setClaimField(data.field); - } - }, - }); + const settingsQuery = useQuery(organizationIdpSyncSettings(isIdpSyncEnabled)); - const { data: claimFieldValues } = useQuery( - idpSyncClaimFieldValues(claimField), + const [field, setField] = useState(""); + useEffect(() => { + if (!settingsQuery.data) { + return; + } + + setField(settingsQuery.data.field); + }, [settingsQuery.data]); + + const fieldValuesQuery = useQuery( + field ? deploymentIdpSyncFieldValues(field) : { enabled: false }, ); const patchOrganizationSyncSettingsMutation = useMutation( @@ -58,14 +55,10 @@ export const IdpOrgSyncPage: FC = () => { } }, [patchOrganizationSyncSettingsMutation.error]); - if (isLoading) { + if (settingsQuery.isLoading) { return ; } - const handleSyncFieldChange = (value: string) => { - setClaimField(value); - }; - return ( <> @@ -84,7 +77,7 @@ export const IdpOrgSyncPage: FC = () => {

- + @@ -96,8 +89,10 @@ export const IdpOrgSyncPage: FC = () => { { try { await patchOrganizationSyncSettingsMutation.mutateAsync(data); @@ -111,9 +106,7 @@ export const IdpOrgSyncPage: FC = () => { ); } }} - onSyncFieldChange={handleSyncFieldChange} - claimFieldValues={claimFieldValues} - error={error || patchOrganizationSyncSettingsMutation.error} + error={settingsQuery.error || fieldValuesQuery.error} /> diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx index 8d02e1f248833..78842737e5baf 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.stories.tsx @@ -5,12 +5,19 @@ import { MockOrganization2, MockOrganizationSyncSettings, MockOrganizationSyncSettings2, + MockOrganizationSyncSettingsEmpty, } from "testHelpers/entities"; import { IdpOrgSyncPageView } from "./IdpOrgSyncPageView"; const meta: Meta = { title: "pages/IdpOrgSyncPageView", component: IdpOrgSyncPageView, + args: { + organizationSyncSettings: MockOrganizationSyncSettings2, + claimFieldValues: Object.keys(MockOrganizationSyncSettings2.mapping), + organizations: [MockOrganization, MockOrganization2], + error: undefined, + }, }; export default meta; @@ -18,35 +25,29 @@ type Story = StoryObj; export const Empty: Story = { args: { - organizationSyncSettings: { - field: "", - mapping: {}, - organization_assign_default: true, - }, - organizations: [MockOrganization, MockOrganization2], - error: undefined, + organizationSyncSettings: MockOrganizationSyncSettingsEmpty, }, }; -export const Default: Story = { - args: { - organizationSyncSettings: MockOrganizationSyncSettings2, - organizations: [MockOrganization, MockOrganization2], - error: undefined, - }, -}; +export const Default: Story = {}; export const HasError: Story = { args: { - ...Default.args, error: "This is a test error", }, }; export const MissingGroups: Story = { args: { - ...Default.args, organizationSyncSettings: MockOrganizationSyncSettings, + claimFieldValues: Object.keys(MockOrganizationSyncSettings.mapping), + organizations: [], + }, +}; + +export const MissingClaim: Story = { + args: { + claimFieldValues: [], }, }; diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx index 031234da0da25..f6822ba0a60ef 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx @@ -1,3 +1,4 @@ +import { TooltipProvider } from "@radix-ui/react-tooltip"; import type { Organization, OrganizationSyncSettings, @@ -28,12 +29,8 @@ import { MultiSelectCombobox, type Option, } from "components/MultiSelectCombobox/MultiSelectCombobox"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/Popover/Popover"; import { Spinner } from "components/Spinner/Spinner"; +import { Stack } from "components/Stack/Stack"; import { Switch } from "components/Switch/Switch"; import { Table, @@ -42,10 +39,14 @@ import { TableHeader, TableRow, } from "components/Table/Table"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import { useFormik } from "formik"; -import { Check, ChevronDown, CornerDownLeft, Plus, Trash } from "lucide-react"; +import { Plus, Trash, TriangleAlert } from "lucide-react"; import { type FC, type KeyboardEventHandler, useId, useState } from "react"; -import { cn } from "utils/cn"; import { docs } from "utils/docs"; import { isUUID } from "utils/uuid"; import * as Yup from "yup"; @@ -53,10 +54,10 @@ import { OrganizationPills } from "./OrganizationPills"; interface IdpSyncPageViewProps { organizationSyncSettings: OrganizationSyncSettings | undefined; + claimFieldValues: readonly string[] | undefined; organizations: readonly Organization[]; onSubmit: (data: OrganizationSyncSettings) => void; onSyncFieldChange: (value: string) => void; - claimFieldValues: string[] | undefined; error?: unknown; } @@ -84,10 +85,10 @@ const validationSchema = Yup.object({ export const IdpOrgSyncPageView: FC = ({ organizationSyncSettings, + claimFieldValues, organizations, onSubmit, onSyncFieldChange, - claimFieldValues, error, }) => { const form = useFormik({ @@ -313,6 +314,7 @@ export const IdpOrgSyncPageView: FC = ({ idpOrg={idpOrg} coderOrgs={getOrgNames(organizations)} onDelete={handleDelete} + exists={claimFieldValues?.includes(idpOrg)} /> ))} @@ -398,18 +400,43 @@ const IdpMappingTable: FC = ({ isEmpty, children }) => { interface OrganizationRowProps { idpOrg: string; + exists: boolean | undefined; coderOrgs: readonly string[]; onDelete: (idpOrg: string) => void; } const OrganizationRow: FC = ({ idpOrg, + exists = true, coderOrgs, onDelete, }) => { return ( - {idpOrg} + +
+ {idpOrg} + {!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. + + + + )} +
+
diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index c522457a63c1d..d8ce878bdef6e 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -2720,6 +2720,13 @@ export const MockOrganizationSyncSettings2: TypesGen.OrganizationSyncSettings = organization_assign_default: true, }; +export const MockOrganizationSyncSettingsEmpty: TypesGen.OrganizationSyncSettings = + { + field: "", + mapping: {}, + organization_assign_default: true, + }; + export const MockGroup: TypesGen.Group = { id: "fbd2116a-8961-4954-87ae-e4575bd29ce0", name: "Front-End", diff --git a/site/tailwind.config.js b/site/tailwind.config.js index b9964e053fda3..e47048a8b2512 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -33,6 +33,7 @@ module.exports = { success: "hsl(var(--content-success))", danger: "hsl(var(--content-danger))", link: "hsl(var(--content-link))", + warning: "hsl(var(--content-warning))", }, surface: { primary: "hsl(var(--surface-primary))", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy