From d9d4cc0b2c9c3ef3333a3c6448b8a401c7d4e977 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 5 Feb 2025 18:02:54 +0000 Subject: [PATCH 1/6] feat: add combobox using claim field values --- .../IdpSyncPage/IdpGroupSyncForm.tsx | 54 +++++++++++++++---- .../IdpSyncPage/IdpRoleSyncForm.tsx | 54 +++++++++++++++---- .../IdpSyncPage/IdpSyncPage.tsx | 33 ++++++++++-- .../IdpSyncPage/IdpSyncPageView.tsx | 6 +++ 4 files changed, 126 insertions(+), 21 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx index 9d63baf180fbc..3f8de7e4ad0db 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx @@ -6,6 +6,7 @@ import type { Organization, } from "api/typesGenerated"; import { Button } from "components/Button/Button"; +import { Combobox } from "components/Combobox/Combobox"; import { HelpTooltip, HelpTooltipContent, @@ -30,7 +31,7 @@ import { } from "components/Tooltip/Tooltip"; import { useFormik } from "formik"; import { Plus, Trash, TriangleAlert } from "lucide-react"; -import { type FC, useId, useState } from "react"; +import { type FC, useId, useState, type KeyboardEventHandler } from "react"; import { docs } from "utils/docs"; import { isUUID } from "utils/uuid"; import * as Yup from "yup"; @@ -70,6 +71,7 @@ interface IdpGroupSyncFormProps { legacyGroupMappingCount: number; organization: Organization; onSubmit: (data: GroupSyncSettings) => void; + onSyncFieldChange: (value: string) => void; } export const IdpGroupSyncForm: FC = ({ @@ -81,6 +83,7 @@ export const IdpGroupSyncForm: FC = ({ groupsMap, organization, onSubmit, + onSyncFieldChange, }) => { const form = useFormik({ initialValues: { @@ -97,6 +100,8 @@ export const IdpGroupSyncForm: FC = ({ const [idpGroupName, setIdpGroupName] = useState(""); const [coderGroups, setCoderGroups] = useState([]); const id = useId(); + const [comboInputValue, setComboInputValue] = useState(""); + const [open, setOpen] = useState(false); const getGroupNames = (groupIds: readonly string[]) => { return groupIds.map((groupId) => groupsMap.get(groupId) || groupId); @@ -116,6 +121,19 @@ export const IdpGroupSyncForm: FC = ({ form.handleSubmit(); }; + const handleKeyDown: KeyboardEventHandler = (event) => { + if ( + event.key === "Enter" && + comboInputValue && + !claimFieldValues?.some((value) => value === comboInputValue.toLowerCase()) + ) { + event.preventDefault(); + setIdpGroupName(comboInputValue); + setComboInputValue(""); + setOpen(false); + } + }; + return (
= ({ value={form.values.field} onChange={(event) => { void form.setFieldValue("field", event.target.value); + onSyncFieldChange(event.target.value); }} className="w-72" /> @@ -202,14 +221,31 @@ export const IdpGroupSyncForm: FC = ({ - { - setIdpGroupName(event.target.value); - }} - /> + {claimFieldValues ? ( + { + setIdpGroupName(value); + setOpen(false); + }} + /> + ) : ( + { + setIdpGroupName(event.target.value); + }} + /> + )}
From dd2a6fffe7ee4359613768589e001724b48969b1 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 5 Feb 2025 19:25:40 +0000 Subject: [PATCH 2/6] chore: update to new table component --- site/src/components/Table/Table.tsx | 2 +- .../IdpOrgSyncPage/IdpOrgSyncPageView.tsx | 2 +- .../IdpSyncPage/IdpGroupSyncForm.tsx | 7 +- .../IdpSyncPage/IdpMappingTable.tsx | 85 +++++++++---------- .../IdpSyncPage/IdpRoleSyncForm.tsx | 7 +- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/site/src/components/Table/Table.tsx b/site/src/components/Table/Table.tsx index 8daf0b57f91a7..604fc3d4f4196 100644 --- a/site/src/components/Table/Table.tsx +++ b/site/src/components/Table/Table.tsx @@ -68,7 +68,7 @@ export const TableRow = React.forwardRef< ref={ref} className={cn( "border-0 border-b border-solid border-border transition-colors", - "hover:bg-muted/50 data-[state=selected]:bg-muted", + "data-[state=selected]:bg-muted", className, )} {...props} diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx index bdcc65b89aaba..5871cf98f21a5 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx @@ -367,7 +367,7 @@ const IdpMappingTable: FC = ({ isEmpty, children }) => { IdP organization Coder organization - + diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx index 3f8de7e4ad0db..d34180e18f3eb 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx @@ -1,5 +1,3 @@ -import TableCell from "@mui/material/TableCell"; -import TableRow from "@mui/material/TableRow"; import type { Group, GroupSyncSettings, @@ -29,6 +27,7 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import { TableCell, TableRow } from "components/Table/Table"; import { useFormik } from "formik"; import { Plus, Trash, TriangleAlert } from "lucide-react"; import { type FC, useId, useState, type KeyboardEventHandler } from "react"; @@ -125,7 +124,9 @@ export const IdpGroupSyncForm: FC = ({ if ( event.key === "Enter" && comboInputValue && - !claimFieldValues?.some((value) => value === comboInputValue.toLowerCase()) + !claimFieldValues?.some( + (value) => value === comboInputValue.toLowerCase(), + ) ) { event.preventDefault(); setIdpGroupName(comboInputValue); diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx index c277c7a14e1c9..26db62e130459 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx @@ -1,12 +1,13 @@ -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; -import TableRow from "@mui/material/TableRow"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; import { EmptyState } from "components/EmptyState/EmptyState"; import { Link } from "components/Link/Link"; +import { + Table, + TableBody, + TableCell, + TableHeader, + TableRow, +} from "components/Table/Table"; import type { FC } from "react"; import { docs } from "utils/docs"; @@ -22,44 +23,40 @@ export const IdpMappingTable: FC = ({ children, }) => { return ( -
- - - - - IdP {type.toLocaleLowerCase()} - - Coder {type.toLocaleLowerCase()} - - - - - - - - - - - How to setup IdP {type.toLocaleLowerCase()} sync - - } - /> - - - - {children} - - -
-
+
+ + + + IdP {type.toLocaleLowerCase()} + Coder {type.toLocaleLowerCase()} + + + + + + + + + + How to setup IdP {type.toLocaleLowerCase()} sync + + } + /> + + + + {children} + + +
Showing {rowCount}{" "} diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx index 98bae0122b071..cc2bab6001c48 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx @@ -1,5 +1,3 @@ -import TableCell from "@mui/material/TableCell"; -import TableRow from "@mui/material/TableRow"; import type { Organization, Role, RoleSyncSettings } from "api/typesGenerated"; import { Button } from "components/Button/Button"; import { Combobox } from "components/Combobox/Combobox"; @@ -16,6 +14,7 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import { TableCell, TableRow } from "components/Table/Table"; import { useFormik } from "formik"; import { Plus, Trash, TriangleAlert } from "lucide-react"; import { type FC, type KeyboardEventHandler, useId, useState } from "react"; @@ -99,7 +98,9 @@ export const IdpRoleSyncForm: FC = ({ if ( event.key === "Enter" && comboInputValue && - !claimFieldValues?.some((value) => value === comboInputValue.toLowerCase()) + !claimFieldValues?.some( + (value) => value === comboInputValue.toLowerCase(), + ) ) { event.preventDefault(); setIdpRoleName(comboInputValue); From 48d885674eac71e844c7a19c8467107fd73a973e Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 5 Feb 2025 20:49:57 +0000 Subject: [PATCH 3/6] fix: fix text --- .../OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx | 2 +- .../OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx | 2 +- .../OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx index d34180e18f3eb..214bb21ee4a14 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx @@ -226,7 +226,7 @@ export const IdpGroupSyncForm: FC = ({ = ({
Showing {rowCount}{" "} - groups + {type.toLocaleLowerCase()}{(rowCount === 0 || rowCount > 1) && "s"}
diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx index cc2bab6001c48..173eb3f267376 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx @@ -167,7 +167,7 @@ export const IdpRoleSyncForm: FC = ({ Date: Wed, 5 Feb 2025 20:50:20 +0000 Subject: [PATCH 4/6] fix: format --- .../OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx index 1973d81d41155..07785038f9a73 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpMappingTable.tsx @@ -60,7 +60,8 @@ export const IdpMappingTable: FC = ({
Showing {rowCount}{" "} - {type.toLocaleLowerCase()}{(rowCount === 0 || rowCount > 1) && "s"} + {type.toLocaleLowerCase()} + {(rowCount === 0 || rowCount > 1) && "s"}
From 447cad7d09c4a70c2cb1f55af27f9924ec7b4d37 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 7 Feb 2025 20:37:37 +0000 Subject: [PATCH 5/6] chore: cleanup --- .../IdpSyncPage/IdpGroupSyncForm.tsx | 4 +- .../IdpSyncPage/IdpRoleSyncForm.tsx | 2 +- .../IdpSyncPage/IdpSyncPage.tsx | 47 ++++++++++--------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx index 214bb21ee4a14..506f003043f6e 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx @@ -21,16 +21,16 @@ import { } from "components/MultiSelectCombobox/MultiSelectCombobox"; import { Spinner } from "components/Spinner/Spinner"; import { Switch } from "components/Switch/Switch"; +import { TableCell, TableRow } from "components/Table/Table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; -import { TableCell, TableRow } from "components/Table/Table"; import { useFormik } from "formik"; import { Plus, Trash, TriangleAlert } from "lucide-react"; -import { type FC, useId, useState, type KeyboardEventHandler } from "react"; +import { type FC, type KeyboardEventHandler, useId, useState } from "react"; import { docs } from "utils/docs"; import { isUUID } from "utils/uuid"; import * as Yup from "yup"; diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx index 173eb3f267376..154f944e5b904 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx @@ -8,13 +8,13 @@ import { type Option, } from "components/MultiSelectCombobox/MultiSelectCombobox"; import { Spinner } from "components/Spinner/Spinner"; +import { TableCell, TableRow } from "components/Table/Table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; -import { TableCell, TableRow } from "components/Table/Table"; import { useFormik } from "formik"; import { Plus, Trash, TriangleAlert } from "lucide-react"; import { type FC, type KeyboardEventHandler, useId, useState } from "react"; diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx index f0e98b6e68a5c..d14cf43b68fcb 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx @@ -17,7 +17,7 @@ import { Link } from "components/Link/Link"; import { Paywall } from "components/Paywall/Paywall"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; -import { type FC, useState } from "react"; +import { type FC, useEffect, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQueries, useQuery, useQueryClient } from "react-query"; import { useParams, useSearchParams } from "react-router-dom"; @@ -27,15 +27,15 @@ import IdpSyncPageView from "./IdpSyncPageView"; export const IdpSyncPage: FC = () => { const queryClient = useQueryClient(); + // IdP sync does not have its own entitlement and is based on templace_rbac + const { template_rbac: isIdpSyncEnabled } = useFeatureVisibility(); const { organization: organizationName } = useParams() as { organization: string; }; - const [groupClaimField, setGroupClaimField] = useState(""); - const [roleClaimField, setRoleClaimField] = useState(""); - // IdP sync does not have its own entitlement and is based on templace_rbac - const { template_rbac: isIdpSyncEnabled } = useFeatureVisibility(); const { organizations } = useOrganizationSettings(); const organization = organizations?.find((o) => o.name === organizationName); + const [groupField, setGroupField] = useState(""); + const [roleField, setRoleField] = useState(""); const [ groupIdpSyncSettingsQuery, @@ -48,7 +48,7 @@ export const IdpSyncPage: FC = () => { ...groupIdpSyncSettings(organizationName), onSuccess: (data: GroupSyncSettings) => { if (data?.field) { - setGroupClaimField(data.field); + setGroupField(data.field); } }, }, @@ -56,7 +56,7 @@ export const IdpSyncPage: FC = () => { ...roleIdpSyncSettings(organizationName), onSuccess: (data: RoleSyncSettings) => { if (data?.field) { - setRoleClaimField(data.field); + setRoleField(data.field); } }, }, @@ -65,12 +65,25 @@ export const IdpSyncPage: FC = () => { ], }); + useEffect(() => { + if (!groupIdpSyncSettingsQuery.data) { + return; + } + + setGroupField(groupIdpSyncSettingsQuery.data.field); + }, [groupIdpSyncSettingsQuery.data]); + + useEffect(() => { + if (!roleIdpSyncSettingsQuery.data) { + return; + } + + setRoleField(roleIdpSyncSettingsQuery.data.field); + }, [roleIdpSyncSettingsQuery.data]); + const [searchParams] = useSearchParams(); const tab = searchParams.get("tab") || "groups"; - const field = - tab === "groups" - ? groupIdpSyncSettingsQuery.data?.field - : roleIdpSyncSettingsQuery.data?.field; + const field = tab === "groups" ? groupField : roleField; const fieldValuesQuery = useQuery( field @@ -103,14 +116,6 @@ export const IdpSyncPage: FC = () => { } } - const handleGroupSyncFieldChange = (value: string) => { - setGroupClaimField(value); - }; - - const handleRoleSyncFieldChange = (value: string) => { - setRoleClaimField(value); - }; - return ( <> @@ -146,8 +151,8 @@ export const IdpSyncPage: FC = () => { groupsMap={groupsMap} roles={rolesQuery.data} organization={organization} - onGroupSyncFieldChange={handleGroupSyncFieldChange} - onRoleSyncFieldChange={handleRoleSyncFieldChange} + onGroupSyncFieldChange={setGroupField} + onRoleSyncFieldChange={setRoleField} error={error} onSubmitGroupSyncSettings={async (data) => { try { From 4356123f7ba6e94369c44c706278999194f02a0c Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 7 Feb 2025 20:48:31 +0000 Subject: [PATCH 6/6] chore: more cleanup --- .../IdpSyncPage/IdpGroupSyncForm.tsx | 2 +- .../IdpSyncPage/IdpRoleSyncForm.tsx | 2 +- .../IdpSyncPage/IdpSyncPage.tsx | 18 ++---------------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx index 506f003043f6e..5340ec99dda79 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpGroupSyncForm.tsx @@ -232,7 +232,7 @@ export const IdpGroupSyncForm: FC = ({ inputValue={comboInputValue} onInputChange={setComboInputValue} onKeyDown={handleKeyDown} - onSelect={(value: string) => { + onSelect={(value) => { setIdpGroupName(value); setOpen(false); }} diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx index 154f944e5b904..faeaf0773dffd 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpRoleSyncForm.tsx @@ -173,7 +173,7 @@ export const IdpRoleSyncForm: FC = ({ inputValue={comboInputValue} onInputChange={setComboInputValue} onKeyDown={handleKeyDown} - onSelect={(value: string) => { + onSelect={(value) => { setIdpRoleName(value); setOpen(false); }} diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx index d14cf43b68fcb..769510d4bf22f 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx @@ -44,22 +44,8 @@ export const IdpSyncPage: FC = () => { rolesQuery, ] = useQueries({ queries: [ - { - ...groupIdpSyncSettings(organizationName), - onSuccess: (data: GroupSyncSettings) => { - if (data?.field) { - setGroupField(data.field); - } - }, - }, - { - ...roleIdpSyncSettings(organizationName), - onSuccess: (data: RoleSyncSettings) => { - if (data?.field) { - setRoleField(data.field); - } - }, - }, + groupIdpSyncSettings(organizationName), + roleIdpSyncSettings(organizationName), groupsByOrganization(organizationName), organizationRoles(organizationName), ],