Skip to content

Commit 17dae76

Browse files
committed
feat: add dropdown to select claim field value when sync field is set
1 parent 3897ea4 commit 17dae76

File tree

7 files changed

+117
-17
lines changed

7 files changed

+117
-17
lines changed

site/src/api/api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,23 @@ class ApiMethods {
787787
return response.data;
788788
};
789789

790+
getIdpSyncClaimFieldValues = async (claimField: string) => {
791+
const response = await this.axios.get<string[]>(
792+
`/api/v2/settings/idpsync/field-values?claimField=${claimField}`,
793+
);
794+
return response.data;
795+
};
796+
797+
getIdpSyncClaimFieldValuesByOrganization = async (
798+
organization: string,
799+
claimField: string,
800+
) => {
801+
const response = await this.axios.get<TypesGen.Response>(
802+
`/api/v2/organizations/${organization}/settings/idpsync/field-values?claimField=${claimField}`,
803+
);
804+
return response.data;
805+
};
806+
790807
getTemplate = async (templateId: string): Promise<TypesGen.Template> => {
791808
const response = await this.axios.get<TypesGen.Template>(
792809
`/api/v2/templates/${templateId}`,

site/src/api/queries/organizations.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,35 @@ export const organizationsPermissions = (
338338
},
339339
};
340340
};
341+
342+
export const getOrganizationIdpSyncClaimFieldValuesKey = (
343+
organization: string,
344+
claimField: string,
345+
) => [organization, claimField, "organizationIdpSyncClaimFieldValues"];
346+
347+
export const organizationIdpSyncClaimFieldValues = (
348+
organization: string,
349+
claimField: string,
350+
) => {
351+
return {
352+
queryKey: getOrganizationIdpSyncClaimFieldValuesKey(
353+
organization,
354+
claimField,
355+
),
356+
queryFn: () =>
357+
API.getIdpSyncClaimFieldValuesByOrganization(organization, claimField),
358+
};
359+
};
360+
361+
export const getIdpSyncClaimFieldValuesKey = (claimField: string) => [
362+
claimField,
363+
"idpSyncClaimFieldValues",
364+
];
365+
366+
export const idpSyncClaimFieldValues = (claimField: string) => {
367+
return {
368+
queryKey: getIdpSyncClaimFieldValuesKey(claimField),
369+
queryFn: () => API.getIdpSyncClaimFieldValues(claimField),
370+
enabled: !!claimField,
371+
};
372+
};

site/src/components/MultiSelectCombobox/MultiSelectCombobox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ export const MultiSelectCombobox = forwardRef<
572572
>
573573
<X className="h-5 w-5" />
574574
</button>
575-
<ChevronDown className="h-5 w-5 cursor-pointer text-content-secondary hover:text-content-primary" />
575+
<ChevronDown className="size-icon-sm cursor-pointer text-content-secondary hover:text-content-primary" />
576576
</div>
577577
</div>
578578
</div>

site/src/components/Select/Select.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@ export const SelectTrigger = React.forwardRef<
2020
<SelectPrimitive.Trigger
2121
ref={ref}
2222
className={cn(
23-
"flex h-10 w-full font-medium items-center justify-between whitespace-nowrap rounded-md ",
24-
"border border-border border-solid bg-transparent px-3 py-2 text-sm shadow-sm ",
25-
"ring-offset-background text-content-secondary placeholder:text-content-secondary focus:outline-none ",
26-
"focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23+
`flex h-10 w-full font-medium items-center justify-between whitespace-nowrap rounded-md
24+
border border-border border-solid bg-transparent px-3 py-2 text-sm shadow-sm
25+
ring-offset-background text-content-secondary placeholder:text-content-secondary focus:outline-none,
26+
focus:ring-2 focus:ring-content-link disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1
27+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link`,
2728
className,
2829
)}
2930
{...props}
3031
>
3132
{children}
3233
<SelectPrimitive.Icon asChild>
33-
<ChevronDown className="size-icon-sm opacity-50" />
34+
<ChevronDown className="size-icon-sm cursor-pointer text-content-secondary hover:text-content-primary" />
3435
</SelectPrimitive.Icon>
3536
</SelectPrimitive.Trigger>
3637
));
@@ -65,7 +66,7 @@ export const SelectScrollDownButton = React.forwardRef<
6566
)}
6667
{...props}
6768
>
68-
<ChevronDown className="size-icon-sm" />
69+
<ChevronDown className="size-icon-sm cursor-pointer text-content-secondary hover:text-content-primary" />
6970
</SelectPrimitive.ScrollDownButton>
7071
));
7172
SelectScrollDownButton.displayName =

site/src/modules/management/OrganizationSidebarView.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
SettingsSidebarNavItem,
1919
} from "components/Sidebar/Sidebar";
2020
import type { Permissions } from "contexts/auth/permissions";
21-
import { ChevronDown, Plus } from "lucide-react";
21+
import { Check, ChevronDown, Plus } from "lucide-react";
2222
import { useDashboard } from "modules/dashboard/useDashboard";
2323
import { type FC, useState } from "react";
2424
import { useNavigate } from "react-router-dom";
@@ -145,6 +145,13 @@ const OrganizationsSettingsNavigation: FC<
145145
<span className="truncate">
146146
{organization?.display_name || organization?.name}
147147
</span>
148+
{activeOrganization.name === organization.name && (
149+
<Check
150+
size={16}
151+
strokeWidth={2}
152+
className="ml-auto"
153+
/>
154+
)}
148155
</CommandItem>
149156
))}
150157
</div>

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
organizationIdpSyncSettings,
44
patchOrganizationSyncSettings,
55
} from "api/queries/idpsync";
6+
import { idpSyncClaimFieldValues } from "api/queries/organizations";
67
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
78
import { displayError } from "components/GlobalSnackbar/utils";
89
import { displaySuccess } from "components/GlobalSnackbar/utils";
@@ -11,7 +12,7 @@ import { Loader } from "components/Loader/Loader";
1112
import { Paywall } from "components/Paywall/Paywall";
1213
import { useDashboard } from "modules/dashboard/useDashboard";
1314
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
14-
import { type FC, useEffect } from "react";
15+
import { type FC, useEffect, useState } from "react";
1516
import { Helmet } from "react-helmet-async";
1617
import { useMutation, useQuery, useQueryClient } from "react-query";
1718
import { docs } from "utils/docs";
@@ -29,6 +30,11 @@ export const IdpOrgSyncPage: FC = () => {
2930
isLoading,
3031
error,
3132
} = useQuery(organizationIdpSyncSettings(isIdpSyncEnabled));
33+
const [claimField, setClaimField] = useState("");
34+
35+
const { data: claimFieldValues } = useQuery(
36+
idpSyncClaimFieldValues(claimField),
37+
);
3238

3339
const patchOrganizationSyncSettingsMutation = useMutation(
3440
patchOrganizationSyncSettings(queryClient),
@@ -49,6 +55,10 @@ export const IdpOrgSyncPage: FC = () => {
4955
return <Loader />;
5056
}
5157

58+
const handleSyncFieldChange = (value: string) => {
59+
setClaimField(value);
60+
};
61+
5262
return (
5363
<>
5464
<Helmet>
@@ -94,6 +104,8 @@ export const IdpOrgSyncPage: FC = () => {
94104
);
95105
}
96106
}}
107+
onSyncFieldChange={handleSyncFieldChange}
108+
claimFieldValues={claimFieldValues}
97109
error={error || patchOrganizationSyncSettingsMutation.error}
98110
/>
99111
</Cond>

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

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ import {
3333
MultiSelectCombobox,
3434
type Option,
3535
} from "components/MultiSelectCombobox/MultiSelectCombobox";
36+
import {
37+
Select,
38+
SelectContent,
39+
SelectItem,
40+
SelectTrigger,
41+
SelectValue,
42+
} from "components/Select/Select";
3643
import { Spinner } from "components/Spinner/Spinner";
3744
import { Switch } from "components/Switch/Switch";
3845
import { useFormik } from "formik";
@@ -47,6 +54,8 @@ interface IdpSyncPageViewProps {
4754
organizationSyncSettings: OrganizationSyncSettings | undefined;
4855
organizations: readonly Organization[];
4956
onSubmit: (data: OrganizationSyncSettings) => void;
57+
onSyncFieldChange: (value: string) => void;
58+
claimFieldValues: string[] | undefined;
5059
error?: unknown;
5160
}
5261

@@ -76,6 +85,8 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
7685
organizationSyncSettings,
7786
organizations,
7887
onSubmit,
88+
onSyncFieldChange,
89+
claimFieldValues,
7990
error,
8091
}) => {
8192
const form = useFormik<OrganizationSyncSettings>({
@@ -135,6 +146,7 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
135146
value={form.values.field}
136147
onChange={(event) => {
137148
void form.setFieldValue("field", event.target.value);
149+
onSyncFieldChange(event.target.value);
138150
}}
139151
/>
140152
<Button
@@ -190,14 +202,33 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
190202
<Label className="text-sm" htmlFor={`${id}-idp-org-name`}>
191203
IdP organization name
192204
</Label>
193-
<Input
194-
id={`${id}-idp-org-name`}
195-
value={idpOrgName}
196-
className="min-w-72 w-72"
197-
onChange={(event) => {
198-
setIdpOrgName(event.target.value);
199-
}}
200-
/>
205+
206+
{claimFieldValues ? (
207+
<Select
208+
onValueChange={(event) => setIdpOrgName(event)}
209+
value={idpOrgName}
210+
>
211+
<SelectTrigger id={`${id}-idp-org-name`} className="w-72">
212+
<SelectValue placeholder="Select IdP organization" />
213+
</SelectTrigger>
214+
<SelectContent className="[&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2">
215+
{claimFieldValues.map((value) => (
216+
<SelectItem key={value} value={value}>
217+
{value}
218+
</SelectItem>
219+
))}
220+
</SelectContent>
221+
</Select>
222+
) : (
223+
<Input
224+
id={`${id}-idp-org-name`}
225+
value={idpOrgName}
226+
className="w-72"
227+
onChange={(event) => {
228+
setIdpOrgName(event.target.value);
229+
}}
230+
/>
231+
)}
201232
</div>
202233
<div className="grid items-center gap-1 flex-1">
203234
<Label className="text-sm" htmlFor={`${id}-coder-org`}>

0 commit comments

Comments
 (0)