Use user select component (#4011)
This commit is contained in:
parent
91acceacd0
commit
be59ed1447
2 changed files with 32 additions and 78 deletions
|
@ -1,3 +1,7 @@
|
||||||
|
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||||
|
import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation";
|
||||||
|
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||||
|
import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
|
||||||
import {
|
import {
|
||||||
ClipboardCopy,
|
ClipboardCopy,
|
||||||
Form,
|
Form,
|
||||||
|
@ -18,16 +22,14 @@ import {
|
||||||
TextContent,
|
TextContent,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { QuestionCircleIcon } from "@patternfly/react-icons";
|
import { QuestionCircleIcon } from "@patternfly/react-icons";
|
||||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
|
||||||
import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation";
|
|
||||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
|
||||||
import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
|
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useHelp } from "../../components/help-enabler/HelpHeader";
|
import { useHelp } from "../../components/help-enabler/HelpHeader";
|
||||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
|
import { UserSelect } from "../../components/users/UserSelect";
|
||||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||||
|
@ -120,14 +122,9 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
ClientScopeRepresentation[]
|
ClientScopeRepresentation[]
|
||||||
>([]);
|
>([]);
|
||||||
const [isScopeOpen, setIsScopeOpen] = useState(false);
|
const [isScopeOpen, setIsScopeOpen] = useState(false);
|
||||||
const [isUserOpen, setIsUserOpen] = useState(false);
|
|
||||||
const [selected, setSelected] = useState<string[]>([prefix]);
|
const [selected, setSelected] = useState<string[]>([prefix]);
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
const [userItems, setUserItems] = useState<JSX.Element[]>([]);
|
|
||||||
const [userSearch, setUserSearch] = useState("");
|
|
||||||
const [user, setUser] = useState<UserRepresentation>();
|
|
||||||
|
|
||||||
const [key, setKey] = useState("");
|
const [key, setKey] = useState("");
|
||||||
const refresh = () => setKey(`${new Date().getTime()}`);
|
const refresh = () => setKey(`${new Date().getTime()}`);
|
||||||
const [effectiveRoles, setEffectiveRoles] = useState<RoleRepresentation[]>(
|
const [effectiveRoles, setEffectiveRoles] = useState<RoleRepresentation[]>(
|
||||||
|
@ -146,39 +143,14 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
const tabContent4 = useRef(null);
|
const tabContent4 = useRef(null);
|
||||||
const tabContent5 = useRef(null);
|
const tabContent5 = useRef(null);
|
||||||
|
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() => adminClient.clients.listOptionalClientScopes({ id: clientId }),
|
() => adminClient.clients.listOptionalClientScopes({ id: clientId }),
|
||||||
(optionalClientScopes) => setSelectableScopes(optionalClientScopes),
|
(optionalClientScopes) => setSelectableScopes(optionalClientScopes),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const toString = (user: UserRepresentation) => {
|
|
||||||
return (
|
|
||||||
t("common:fullName", {
|
|
||||||
givenName: user.firstName,
|
|
||||||
familyName: user.lastName,
|
|
||||||
}).trim() ||
|
|
||||||
user.username ||
|
|
||||||
""
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useFetch(
|
|
||||||
() => adminClient.users.find({ search: userSearch }),
|
|
||||||
(users) =>
|
|
||||||
setUserItems(
|
|
||||||
users
|
|
||||||
.map((user) => {
|
|
||||||
user.toString = function () {
|
|
||||||
return toString(this);
|
|
||||||
};
|
|
||||||
return user;
|
|
||||||
})
|
|
||||||
.map((user) => <SelectOption key={user.id} value={user} />)
|
|
||||||
),
|
|
||||||
[userSearch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
const scope = selected.join(" ");
|
const scope = selected.join(" ");
|
||||||
|
@ -218,22 +190,23 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
const scope = selected.join(" ");
|
const scope = selected.join(" ");
|
||||||
|
const user = form.getValues("user");
|
||||||
if (!user) return [];
|
if (!user) return [];
|
||||||
|
|
||||||
return await Promise.all([
|
return await Promise.all([
|
||||||
adminClient.clients.evaluateGenerateAccessToken({
|
adminClient.clients.evaluateGenerateAccessToken({
|
||||||
id: clientId,
|
id: clientId,
|
||||||
userId: user.id!,
|
userId: user[0],
|
||||||
scope,
|
scope,
|
||||||
}),
|
}),
|
||||||
adminClient.clients.evaluateGenerateUserInfo({
|
adminClient.clients.evaluateGenerateUserInfo({
|
||||||
id: clientId,
|
id: clientId,
|
||||||
userId: user.id!,
|
userId: user[0],
|
||||||
scope,
|
scope,
|
||||||
}),
|
}),
|
||||||
adminClient.clients.evaluateGenerateIdToken({
|
adminClient.clients.evaluateGenerateIdToken({
|
||||||
id: clientId,
|
id: clientId,
|
||||||
userId: user.id!,
|
userId: user[0],
|
||||||
scope,
|
scope,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
@ -243,7 +216,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
setUserInfo(prettyPrintJSON(userInfo));
|
setUserInfo(prettyPrintJSON(userInfo));
|
||||||
setIdToken(prettyPrintJSON(idToken));
|
setIdToken(prettyPrintJSON(idToken));
|
||||||
},
|
},
|
||||||
[user, selected]
|
[form.getValues("user"), selected]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -301,39 +274,16 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
</Split>
|
</Split>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<FormProvider {...form}>
|
||||||
label={t("user")}
|
<UserSelect
|
||||||
fieldId="user"
|
name="user"
|
||||||
labelIcon={
|
label="users"
|
||||||
<HelpItem
|
|
||||||
helpText="clients-help:user"
|
helpText="clients-help:user"
|
||||||
fieldLabelId="clients:user"
|
defaultValue=""
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
toggleId="user"
|
|
||||||
variant={SelectVariant.typeahead}
|
variant={SelectVariant.typeahead}
|
||||||
typeAheadAriaLabel={t("user")}
|
isRequired
|
||||||
onToggle={() => setIsUserOpen(!isUserOpen)}
|
|
||||||
onFilter={(e) => {
|
|
||||||
const value = e?.target.value || "";
|
|
||||||
setUserSearch(value);
|
|
||||||
return userItems;
|
|
||||||
}}
|
|
||||||
onClear={() => {
|
|
||||||
setUser(undefined);
|
|
||||||
setUserSearch("");
|
|
||||||
}}
|
|
||||||
selections={[user]}
|
|
||||||
onSelect={(_, value) => {
|
|
||||||
setUser(value as UserRepresentation);
|
|
||||||
setUserSearch("");
|
|
||||||
setIsUserOpen(false);
|
|
||||||
}}
|
|
||||||
isOpen={isUserOpen}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormProvider>
|
||||||
</Form>
|
</Form>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|
||||||
|
@ -365,7 +315,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
>
|
>
|
||||||
<GeneratedCodeTab
|
<GeneratedCodeTab
|
||||||
text={accessToken}
|
text={accessToken}
|
||||||
user={user}
|
user={form.getValues("user")}
|
||||||
label="generatedAccessToken"
|
label="generatedAccessToken"
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
@ -378,7 +328,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
>
|
>
|
||||||
<GeneratedCodeTab
|
<GeneratedCodeTab
|
||||||
text={idToken}
|
text={idToken}
|
||||||
user={user}
|
user={form.getValues("user")}
|
||||||
label="generatedIdToken"
|
label="generatedIdToken"
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
@ -391,7 +341,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
|
||||||
>
|
>
|
||||||
<GeneratedCodeTab
|
<GeneratedCodeTab
|
||||||
text={userInfo}
|
text={userInfo}
|
||||||
user={user}
|
user={form.getValues("user")}
|
||||||
label="generatedUserInfo"
|
label="generatedUserInfo"
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
|
@ -7,6 +7,7 @@ import {
|
||||||
Select,
|
Select,
|
||||||
SelectVariant,
|
SelectVariant,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
import { debounce } from "lodash-es";
|
||||||
|
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import type { UserQuery } from "@keycloak/keycloak-admin-client/lib/resources/users";
|
import type { UserQuery } from "@keycloak/keycloak-admin-client/lib/resources/users";
|
||||||
|
@ -42,6 +43,7 @@ export const UserSelect = ({
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
const { adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
|
const debounceFn = useCallback(debounce(setSearch, 1000), []);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() => {
|
() => {
|
||||||
|
@ -107,12 +109,14 @@ export const UserSelect = ({
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
selections={value}
|
selections={value}
|
||||||
onFilter={(_, value) => {
|
onFilter={(_, value) => {
|
||||||
setSearch(value);
|
debounceFn(value);
|
||||||
return convert(users);
|
return convert(users);
|
||||||
}}
|
}}
|
||||||
onSelect={(_, v) => {
|
onSelect={(_, v) => {
|
||||||
const option = v.toString();
|
const option = v.toString();
|
||||||
if (value.includes(option)) {
|
if (variant !== SelectVariant.typeaheadMulti) {
|
||||||
|
onChange([option]);
|
||||||
|
} else if (value.includes(option)) {
|
||||||
onChange(value.filter((item: string) => item !== option));
|
onChange(value.filter((item: string) => item !== option));
|
||||||
} else {
|
} else {
|
||||||
onChange([...value, option]);
|
onChange([...value, option]);
|
||||||
|
|
Loading…
Reference in a new issue