changed to use ui-shared (#27933)

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-03-22 11:27:09 +01:00 committed by GitHub
parent 5a99c558dc
commit 53d52ecf15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 114 additions and 289 deletions

View file

@ -29,6 +29,7 @@ export default class PoliciesTab extends CommonPage {
inputClient(clientName: string) {
cy.get("#clients").click();
cy.get("ul li").contains(clientName).click();
cy.get("#clients").click();
return this;
}
}

View file

@ -5,16 +5,11 @@ import {
Dropdown,
DropdownToggle,
Form,
FormGroup,
Select,
SelectOption,
SelectVariant,
} from "@patternfly/react-core";
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { SelectControl, TextControl } from "ui-shared";
import useToggle from "../../utils/useToggle";
import "./search-dropdown.css";
@ -42,16 +37,14 @@ export const SearchDropdown = ({
type,
}: SearchDropdownProps) => {
const { t } = useTranslation();
const form = useForm<SearchForm>({ mode: "onChange" });
const {
register,
control,
reset,
formState: { isDirty },
handleSubmit,
} = useForm<SearchForm>({ mode: "onChange" });
} = form;
const [open, toggle] = useToggle();
const [typeOpen, toggleType] = useToggle();
const submit = (form: SearchForm) => {
toggle();
@ -60,21 +53,6 @@ export const SearchDropdown = ({
useEffect(() => reset(search), [search]);
const typeOptions = (value?: string) => [
<SelectOption key="empty" value="">
{t("allTypes")}
</SelectOption>,
...(types || []).map((type) => (
<SelectOption
selected={type.type === value}
key={type.type}
value={type.type}
>
{type.name}
</SelectOption>
)),
];
return (
<Dropdown
data-testid="searchdropdown_dorpdown"
@ -91,105 +69,59 @@ export const SearchDropdown = ({
}
isOpen={open}
>
<Form
isHorizontal
className="keycloak__client_authentication__searchdropdown_form"
onSubmit={handleSubmit(submit)}
>
<FormGroup label={t("name")} fieldId="name">
<KeycloakTextInput
id="name"
data-testid="searchdropdown_name"
{...register("name")}
/>
</FormGroup>
{type === "resource" && (
<>
<FormGroup label={t("type")} fieldId="type">
<KeycloakTextInput
id="type"
data-testid="searchdropdown_type"
{...register("type")}
/>
</FormGroup>
<FormGroup label={t("uris")} fieldId="uri">
<KeycloakTextInput
id="uri"
data-testid="searchdropdown_uri"
{...register("uri")}
/>
</FormGroup>
<FormGroup label={t("owner")} fieldId="owner">
<KeycloakTextInput
id="owner"
data-testid="searchdropdown_owner"
{...register("owner")}
/>
</FormGroup>
</>
)}
{type !== "resource" && type !== "policy" && (
<FormGroup label={t("resource")} fieldId="resource">
<KeycloakTextInput
id="resource"
data-testid="searchdropdown_resource"
{...register("resource")}
/>
</FormGroup>
)}
{type !== "policy" && (
<FormGroup label={t("scope")} fieldId="scope">
<KeycloakTextInput
id="scope"
data-testid="searchdropdown_scope"
{...register("scope")}
/>
</FormGroup>
)}
{type !== "resource" && (
<FormGroup label={t("type")} fieldId="type">
<Controller
<FormProvider {...form}>
<Form
isHorizontal
className="keycloak__client_authentication__searchdropdown_form"
onSubmit={handleSubmit(submit)}
>
<TextControl name="name" label={t("name")} />
{type === "resource" && (
<>
<TextControl name="type" label={t("type")} />
<TextControl name="uris" label={t("uris")} />
<TextControl name="owner" label={t("owner")} />
</>
)}
{type !== "resource" && type !== "policy" && (
<TextControl name="resource" label={t("resource")} />
)}
{type !== "policy" && <TextControl name="scope" label={t("scope")} />}
{type !== "resource" && (
<SelectControl
name="type"
defaultValue=""
control={control}
render={({ field }) => (
<Select
toggleId="type"
onToggle={toggleType}
onSelect={(event, value) => {
event.stopPropagation();
field.onChange(value);
toggleType();
}}
selections={field.value || t("allTypes")}
variant={SelectVariant.single}
aria-label={t("type")}
isOpen={typeOpen}
>
{typeOptions(field.value)}
</Select>
)}
label={t("type")}
controller={{
defaultValue: "",
}}
options={[
{ key: "", value: t("allTypes") },
...(types || []).map(({ type, name }) => ({
key: type!,
value: name!,
})),
]}
/>
</FormGroup>
)}
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="search-btn"
isDisabled={!isDirty}
>
{t("search")}
</Button>
<Button
variant="link"
data-testid="revert-btn"
onClick={() => onSearch({})}
>
{t("clear")}
</Button>
</ActionGroup>
</Form>
)}
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="search-btn"
isDisabled={!isDirty}
>
{t("search")}
</Button>
<Button
variant="link"
data-testid="revert-btn"
onClick={() => onSearch({})}
>
{t("clear")}
</Button>
</ActionGroup>
</Form>
</FormProvider>
</Dropdown>
);
};

View file

@ -1,3 +1,4 @@
import type ResourceServerRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceServerRepresentation";
import {
AlertVariant,
Button,
@ -5,24 +6,22 @@ import {
FormGroup,
PageSection,
Radio,
Switch,
} from "@patternfly/react-core";
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared";
import { adminClient } from "../../admin-client";
import type ResourceServerRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceServerRepresentation";
import { DefaultSwitchControl } from "../../components/SwitchControl";
import { useAlerts } from "../../components/alert/Alerts";
import { FixedButtonsGroup } from "../../components/form/FixedButtonGroup";
import { FormAccess } from "../../components/form/FormAccess";
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
import { useAccess } from "../../context/access/Access";
import { useFetch } from "../../utils/useFetch";
import useToggle from "../../utils/useToggle";
import { DecisionStrategySelect } from "./DecisionStrategySelect";
import { ImportDialog } from "./ImportDialog";
import { useFetch } from "../../utils/useFetch";
import { useAccess } from "../../context/access/Access";
const POLICY_ENFORCEMENT_MODES = [
"ENFORCING",
@ -145,35 +144,12 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
</FormGroup>
<FormProvider {...form}>
<DecisionStrategySelect isLimited />
</FormProvider>
<FormGroup
hasNoPaddingTop
label={t("allowRemoteResourceManagement")}
fieldId="allowRemoteResourceManagement"
labelIcon={
<HelpItem
helpText={t("allowRemoteResourceManagementHelp")}
fieldLabelId="allowRemoteResourceManagement"
/>
}
>
<Controller
<DefaultSwitchControl
name="allowRemoteResourceManagement"
data-testid="allowRemoteResourceManagement"
defaultValue={false}
control={control}
render={({ field }) => (
<Switch
id="allowRemoteResourceManagement"
label={t("on")}
labelOff={t("off")}
isChecked={field.value}
onChange={field.onChange}
aria-label={t("allowRemoteResourceManagement")}
/>
)}
label={t("allowRemoteResourceManagement")}
labelIcon={t("allowRemoteResourceManagementHelp")}
/>
</FormGroup>
</FormProvider>
<FixedButtonsGroup
name="authenticationSettings"
reset={() => reset(resource)}

View file

@ -1,114 +1,18 @@
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import type { ClientQuery } from "@keycloak/keycloak-admin-client/lib/resources/clients";
import {
FormGroup,
Select,
SelectOption,
SelectVariant,
} from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { SelectVariant } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared";
import { adminClient } from "../../../admin-client";
import { useFetch } from "../../../utils/useFetch";
import { ClientSelect } from "../../../components/client/ClientSelect";
export const Client = () => {
const { t } = useTranslation();
const {
control,
getValues,
formState: { errors },
} = useFormContext();
const values: string[] | undefined = getValues("clients");
const [open, setOpen] = useState(false);
const [clients, setClients] = useState<ClientRepresentation[]>([]);
const [search, setSearch] = useState("");
useFetch(
async () => {
const params: ClientQuery = {
max: 20,
};
if (search) {
params.clientId = search;
params.search = true;
}
if (values?.length && !search) {
return await Promise.all(
values.map(
(id: string) =>
adminClient.clients.findOne({ id }) as ClientRepresentation,
),
);
}
return await adminClient.clients.find(params);
},
setClients,
[search],
);
const convert = (clients: ClientRepresentation[]) =>
clients.map((option) => (
<SelectOption
key={option.id!}
value={option.id}
selected={values?.includes(option.id!)}
>
{option.clientId}
</SelectOption>
));
return (
<FormGroup
<ClientSelect
name="clients"
label={t("clients")}
labelIcon={
<HelpItem helpText={t("policyClientHelp")} fieldLabelId="client" />
}
fieldId="clients"
helperTextInvalid={t("requiredClient")}
validated={errors.clients ? "error" : "default"}
isRequired
>
<Controller
name="clients"
defaultValue={[]}
control={control}
rules={{
validate: (value) => value.length > 0,
}}
render={({ field }) => (
<Select
toggleId="clients"
variant={SelectVariant.typeaheadMulti}
typeAheadAriaLabel={t("clients")}
onToggle={(open) => setOpen(open)}
isOpen={open}
selections={field.value}
aria-label={t("selectClients")}
onFilter={(_, value) => {
setSearch(value);
return convert(clients);
}}
onSelect={(_, v) => {
const option = v.toString();
if (field.value.includes(option)) {
field.onChange(
field.value.filter((item: string) => item !== option),
);
} else {
field.onChange([...field.value, option]);
}
setOpen(false);
}}
>
{convert(clients)}
</Select>
)}
/>
</FormGroup>
helpText={t("policyClientHelp")}
required
defaultValue={[]}
variant={SelectVariant.typeaheadMulti}
/>
);
};

View file

@ -1,6 +1,6 @@
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import type { ClientQuery } from "@keycloak/keycloak-admin-client/lib/resources/clients";
import { SelectVariant } from "@patternfly/react-core";
import { SelectProps, SelectVariant } from "@patternfly/react-core";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { SelectControl } from "ui-shared";
@ -9,7 +9,7 @@ import { adminClient } from "../../admin-client";
import { useFetch } from "../../utils/useFetch";
import type { ComponentProps } from "../dynamic/components";
type ClientSelectProps = ComponentProps & {};
type ClientSelectProps = ComponentProps & Pick<SelectProps, "variant">;
export const ClientSelect = ({
name,
@ -18,6 +18,7 @@ export const ClientSelect = ({
defaultValue,
isDisabled = false,
required = false,
variant = SelectVariant.typeahead,
}: ClientSelectProps) => {
const { t } = useTranslation();
@ -54,12 +55,12 @@ export const ClientSelect = ({
},
}}
onFilter={(value) => setSearch(value)}
variant={SelectVariant.typeahead}
variant={variant}
isDisabled={isDisabled}
options={[
{ key: "", value: t("none") },
...clients.map(({ id, clientId }) => ({ key: id!, value: clientId! })),
]}
options={clients.map(({ id, clientId }) => ({
key: id!,
value: clientId!,
}))}
/>
);
};

View file

@ -1,12 +1,3 @@
import { useState } from "react";
import {
Controller,
ControllerProps,
FieldValues,
FieldPath,
useFormContext,
UseControllerProps,
} from "react-hook-form";
import {
Select,
SelectOption,
@ -14,6 +5,15 @@ import {
SelectVariant,
ValidatedOptions,
} from "@patternfly/react-core";
import { useState } from "react";
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
UseControllerProps,
useFormContext,
} from "react-hook-form";
import { FormLabel } from "./FormLabel";
export type SelectControlOption = {
@ -21,6 +21,8 @@ export type SelectControlOption = {
value: string;
};
type OptionType = string[] | SelectControlOption[];
export type SelectControlProps<
T extends FieldValues,
P extends FieldPath<T> = FieldPath<T>,
@ -37,12 +39,17 @@ export type SelectControlProps<
UseControllerProps<T, P> & {
name: string;
label?: string;
options: string[] | SelectControlOption[];
options: OptionType;
labelIcon?: string;
controller: Omit<ControllerProps, "name" | "render">;
onFilter?: (value: string) => void;
};
const isString = (option: SelectControlOption | string): option is string =>
typeof option === "string";
const key = (option: SelectControlOption | string) =>
isString(option) ? option : option.key;
export const SelectControl = <
T extends FieldValues,
P extends FieldPath<T> = FieldPath<T>,
@ -68,19 +75,19 @@ export const SelectControl = <
option.toString().toLowerCase().startsWith(lowercasePrefix),
)
.map((option) => (
<SelectOption
key={typeof option === "string" ? option : option.key}
value={typeof option === "string" ? option : option.key}
>
{typeof option === "string" ? option : option.value}
<SelectOption key={key(option)} value={key(option)}>
{isString(option) ? option : option.value}
</SelectOption>
));
};
const isSelectBasedOptions = (
options: OptionType,
): options is SelectControlOption[] => typeof options[0] !== "string";
return (
<FormLabel
name={name}
label={label}
isRequired={controller.rules?.required === true}
isRequired={!!controller.rules?.required}
error={errors[name]}
labelIcon={labelIcon}
>
@ -94,8 +101,8 @@ export const SelectControl = <
toggleId={name.slice(name.lastIndexOf(".") + 1)}
onToggle={(isOpen) => setOpen(isOpen)}
selections={
typeof options[0] !== "string"
? (options as SelectControlOption[])
isSelectBasedOptions(options)
? options
.filter((o) =>
Array.isArray(value)
? value.includes(o.key)
@ -104,11 +111,15 @@ export const SelectControl = <
.map((o) => o.value)
: value
}
onSelect={(_, v) => {
if (variant === "typeaheadmulti") {
onSelect={(event, v) => {
event.stopPropagation();
if (Array.isArray(value)) {
const option = v.toString();
if (value.includes(option)) {
onChange(value.filter((item: string) => item !== option));
const key = isSelectBasedOptions(options)
? options.find((o) => o.value === option)?.key
: option;
if (value.includes(key)) {
onChange(value.filter((item: string) => item !== key));
} else {
onChange([...value, option]);
}