Skip to content

Commit d9d4cc0

Browse files
committed
feat: add combobox using claim field values
1 parent 323559b commit d9d4cc0

File tree

4 files changed

+126
-21
lines changed

4 files changed

+126
-21
lines changed

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

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
Organization,
77
} from "api/typesGenerated";
88
import { Button } from "components/Button/Button";
9+
import { Combobox } from "components/Combobox/Combobox";
910
import {
1011
HelpTooltip,
1112
HelpTooltipContent,
@@ -30,7 +31,7 @@ import {
3031
} from "components/Tooltip/Tooltip";
3132
import { useFormik } from "formik";
3233
import { Plus, Trash, TriangleAlert } from "lucide-react";
33-
import { type FC, useId, useState } from "react";
34+
import { type FC, useId, useState, type KeyboardEventHandler } from "react";
3435
import { docs } from "utils/docs";
3536
import { isUUID } from "utils/uuid";
3637
import * as Yup from "yup";
@@ -70,6 +71,7 @@ interface IdpGroupSyncFormProps {
7071
legacyGroupMappingCount: number;
7172
organization: Organization;
7273
onSubmit: (data: GroupSyncSettings) => void;
74+
onSyncFieldChange: (value: string) => void;
7375
}
7476

7577
export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
@@ -81,6 +83,7 @@ export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
8183
groupsMap,
8284
organization,
8385
onSubmit,
86+
onSyncFieldChange,
8487
}) => {
8588
const form = useFormik<GroupSyncSettings>({
8689
initialValues: {
@@ -97,6 +100,8 @@ export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
97100
const [idpGroupName, setIdpGroupName] = useState("");
98101
const [coderGroups, setCoderGroups] = useState<Option[]>([]);
99102
const id = useId();
103+
const [comboInputValue, setComboInputValue] = useState("");
104+
const [open, setOpen] = useState(false);
100105

101106
const getGroupNames = (groupIds: readonly string[]) => {
102107
return groupIds.map((groupId) => groupsMap.get(groupId) || groupId);
@@ -116,6 +121,19 @@ export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
116121
form.handleSubmit();
117122
};
118123

124+
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
125+
if (
126+
event.key === "Enter" &&
127+
comboInputValue &&
128+
!claimFieldValues?.some((value) => value === comboInputValue.toLowerCase())
129+
) {
130+
event.preventDefault();
131+
setIdpGroupName(comboInputValue);
132+
setComboInputValue("");
133+
setOpen(false);
134+
}
135+
};
136+
119137
return (
120138
<form onSubmit={form.handleSubmit}>
121139
<fieldset
@@ -143,6 +161,7 @@ export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
143161
value={form.values.field}
144162
onChange={(event) => {
145163
void form.setFieldValue("field", event.target.value);
164+
onSyncFieldChange(event.target.value);
146165
}}
147166
className="w-72"
148167
/>
@@ -202,14 +221,31 @@ export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
202221
<Label className="text-sm" htmlFor={`${id}-idp-group-name`}>
203222
IdP group name
204223
</Label>
205-
<Input
206-
id={`${id}-idp-group-name`}
207-
value={idpGroupName}
208-
className="w-72"
209-
onChange={(event) => {
210-
setIdpGroupName(event.target.value);
211-
}}
212-
/>
224+
{claimFieldValues ? (
225+
<Combobox
226+
value={idpGroupName}
227+
options={claimFieldValues}
228+
placeholder="Select IdP organization"
229+
open={open}
230+
onOpenChange={setOpen}
231+
inputValue={comboInputValue}
232+
onInputChange={setComboInputValue}
233+
onKeyDown={handleKeyDown}
234+
onSelect={(value: string) => {
235+
setIdpGroupName(value);
236+
setOpen(false);
237+
}}
238+
/>
239+
) : (
240+
<Input
241+
id={`${id}-idp-group-name`}
242+
value={idpGroupName}
243+
className="w-72"
244+
onChange={(event) => {
245+
setIdpGroupName(event.target.value);
246+
}}
247+
/>
248+
)}
213249
</div>
214250
<div className="grid items-center gap-1 flex-1">
215251
<Label className="text-sm" htmlFor={`${id}-coder-group`}>

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

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import TableCell from "@mui/material/TableCell";
22
import TableRow from "@mui/material/TableRow";
33
import type { Organization, Role, RoleSyncSettings } from "api/typesGenerated";
44
import { Button } from "components/Button/Button";
5+
import { Combobox } from "components/Combobox/Combobox";
56
import { Input } from "components/Input/Input";
67
import { Label } from "components/Label/Label";
78
import {
@@ -17,7 +18,7 @@ import {
1718
} from "components/Tooltip/Tooltip";
1819
import { useFormik } from "formik";
1920
import { Plus, Trash, TriangleAlert } from "lucide-react";
20-
import { type FC, useId, useState } from "react";
21+
import { type FC, type KeyboardEventHandler, useId, useState } from "react";
2122
import * as Yup from "yup";
2223
import { ExportPolicyButton } from "./ExportPolicyButton";
2324
import { IdpMappingTable } from "./IdpMappingTable";
@@ -53,6 +54,7 @@ interface IdpRoleSyncFormProps {
5354
organization: Organization;
5455
roles: Role[];
5556
onSubmit: (data: RoleSyncSettings) => void;
57+
onSyncFieldChange: (value: string) => void;
5658
}
5759

5860
export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
@@ -62,6 +64,7 @@ export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
6264
organization,
6365
roles,
6466
onSubmit,
67+
onSyncFieldChange,
6568
}) => {
6669
const form = useFormik<RoleSyncSettings>({
6770
initialValues: {
@@ -75,6 +78,8 @@ export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
7578
const [idpRoleName, setIdpRoleName] = useState("");
7679
const [coderRoles, setCoderRoles] = useState<Option[]>([]);
7780
const id = useId();
81+
const [comboInputValue, setComboInputValue] = useState("");
82+
const [open, setOpen] = useState(false);
7883

7984
const handleDelete = async (idpOrg: string) => {
8085
const newMapping = Object.fromEntries(
@@ -90,6 +95,19 @@ export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
9095
form.handleSubmit();
9196
};
9297

98+
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
99+
if (
100+
event.key === "Enter" &&
101+
comboInputValue &&
102+
!claimFieldValues?.some((value) => value === comboInputValue.toLowerCase())
103+
) {
104+
event.preventDefault();
105+
setIdpRoleName(comboInputValue);
106+
setComboInputValue("");
107+
setOpen(false);
108+
}
109+
};
110+
93111
return (
94112
<form onSubmit={form.handleSubmit}>
95113
<fieldset
@@ -114,6 +132,7 @@ export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
114132
value={form.values.field}
115133
onChange={(event) => {
116134
void form.setFieldValue("field", event.target.value);
135+
onSyncFieldChange(event.target.value);
117136
}}
118137
className="w-72"
119138
/>
@@ -143,14 +162,31 @@ export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
143162
<Label className="text-sm" htmlFor={`${id}-idp-role-name`}>
144163
IdP role name
145164
</Label>
146-
<Input
147-
id={`${id}-idp-role-name`}
148-
value={idpRoleName}
149-
className="w-72"
150-
onChange={(event) => {
151-
setIdpRoleName(event.target.value);
152-
}}
153-
/>
165+
{claimFieldValues ? (
166+
<Combobox
167+
value={idpRoleName}
168+
options={claimFieldValues}
169+
placeholder="Select IdP organization"
170+
open={open}
171+
onOpenChange={setOpen}
172+
inputValue={comboInputValue}
173+
onInputChange={setComboInputValue}
174+
onKeyDown={handleKeyDown}
175+
onSelect={(value: string) => {
176+
setIdpRoleName(value);
177+
setOpen(false);
178+
}}
179+
/>
180+
) : (
181+
<Input
182+
id={`${id}-idp-role-name`}
183+
value={idpRoleName}
184+
className="w-72"
185+
onChange={(event) => {
186+
setIdpRoleName(event.target.value);
187+
}}
188+
/>
189+
)}
154190
</div>
155191
<div className="grid items-center gap-1 flex-1">
156192
<Label className="text-sm" htmlFor={`${id}-coder-role`}>

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
roleIdpSyncSettings,
99
} from "api/queries/organizations";
1010
import { organizationRoles } from "api/queries/roles";
11+
import type { GroupSyncSettings, RoleSyncSettings } from "api/typesGenerated";
1112
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1213
import { EmptyState } from "components/EmptyState/EmptyState";
1314
import { displayError } from "components/GlobalSnackbar/utils";
@@ -16,7 +17,7 @@ import { Link } from "components/Link/Link";
1617
import { Paywall } from "components/Paywall/Paywall";
1718
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
1819
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
19-
import type { FC } from "react";
20+
import { type FC, useState } from "react";
2021
import { Helmet } from "react-helmet-async";
2122
import { useMutation, useQueries, useQuery, useQueryClient } from "react-query";
2223
import { useParams, useSearchParams } from "react-router-dom";
@@ -29,6 +30,8 @@ export const IdpSyncPage: FC = () => {
2930
const { organization: organizationName } = useParams() as {
3031
organization: string;
3132
};
33+
const [groupClaimField, setGroupClaimField] = useState("");
34+
const [roleClaimField, setRoleClaimField] = useState("");
3235
// IdP sync does not have its own entitlement and is based on templace_rbac
3336
const { template_rbac: isIdpSyncEnabled } = useFeatureVisibility();
3437
const { organizations } = useOrganizationSettings();
@@ -41,8 +44,22 @@ export const IdpSyncPage: FC = () => {
4144
rolesQuery,
4245
] = useQueries({
4346
queries: [
44-
groupIdpSyncSettings(organizationName),
45-
roleIdpSyncSettings(organizationName),
47+
{
48+
...groupIdpSyncSettings(organizationName),
49+
onSuccess: (data: GroupSyncSettings) => {
50+
if (data?.field) {
51+
setGroupClaimField(data.field);
52+
}
53+
},
54+
},
55+
{
56+
...roleIdpSyncSettings(organizationName),
57+
onSuccess: (data: RoleSyncSettings) => {
58+
if (data?.field) {
59+
setRoleClaimField(data.field);
60+
}
61+
},
62+
},
4663
groupsByOrganization(organizationName),
4764
organizationRoles(organizationName),
4865
],
@@ -86,6 +103,14 @@ export const IdpSyncPage: FC = () => {
86103
}
87104
}
88105

106+
const handleGroupSyncFieldChange = (value: string) => {
107+
setGroupClaimField(value);
108+
};
109+
110+
const handleRoleSyncFieldChange = (value: string) => {
111+
setRoleClaimField(value);
112+
};
113+
89114
return (
90115
<>
91116
<Helmet>
@@ -121,6 +146,8 @@ export const IdpSyncPage: FC = () => {
121146
groupsMap={groupsMap}
122147
roles={rolesQuery.data}
123148
organization={organization}
149+
onGroupSyncFieldChange={handleGroupSyncFieldChange}
150+
onRoleSyncFieldChange={handleRoleSyncFieldChange}
124151
error={error}
125152
onSubmitGroupSyncSettings={async (data) => {
126153
try {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ interface IdpSyncPageViewProps {
2121
groupsMap: Map<string, string>;
2222
roles: Role[] | undefined;
2323
organization: Organization;
24+
onGroupSyncFieldChange: (value: string) => void;
25+
onRoleSyncFieldChange: (value: string) => void;
2426
error?: unknown;
2527
onSubmitGroupSyncSettings: (data: GroupSyncSettings) => void;
2628
onSubmitRoleSyncSettings: (data: RoleSyncSettings) => void;
@@ -35,6 +37,8 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
3537
groupsMap,
3638
roles,
3739
organization,
40+
onGroupSyncFieldChange,
41+
onRoleSyncFieldChange,
3842
error,
3943
onSubmitGroupSyncSettings,
4044
onSubmitRoleSyncSettings,
@@ -76,6 +80,7 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
7680
groupsMap={groupsMap}
7781
organization={organization}
7882
onSubmit={onSubmitGroupSyncSettings}
83+
onSyncFieldChange={onGroupSyncFieldChange}
7984
/>
8085
) : (
8186
<IdpRoleSyncForm
@@ -85,6 +90,7 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
8590
roles={roles || []}
8691
organization={organization}
8792
onSubmit={onSubmitRoleSyncSettings}
93+
onSyncFieldChange={onRoleSyncFieldChange}
8894
/>
8995
)}
9096
</div>

0 commit comments

Comments
 (0)
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