Use react-hook-form v7 for client scope form (#3885)

This commit is contained in:
Jon Koops 2022-12-06 14:38:28 +01:00 committed by GitHub
parent 49ed77b85c
commit c2924ceb9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 96 deletions

View file

@ -272,12 +272,7 @@ describe("Client Scopes test", () => {
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
listingPage.goToCreateItem(); listingPage.goToCreateItem();
createClientScopePage.save().checkClientNameRequiredMessage(); createClientScopePage.fillClientScopeData("address").save();
createClientScopePage
.fillClientScopeData("address")
.save()
.checkClientNameRequiredMessage(false);
// The error should inform about duplicated name/id // The error should inform about duplicated name/id
masthead.checkNotificationMessage( masthead.checkNotificationMessage(

View file

@ -26,7 +26,7 @@ export default class CreateClientScopePage extends CommonPage {
this.clientScopeDescriptionInput = "#kc-description"; this.clientScopeDescriptionInput = "#kc-description";
this.clientScopeTypeDrpDwn = "#kc-protocol"; this.clientScopeTypeDrpDwn = "#kc-protocol";
this.clientScopeTypeList = "#kc-protocol + ul"; this.clientScopeTypeList = "#kc-protocol + ul";
this.displayOnConsentInput = '[id="kc-display.on.consent.screen-switch"]'; this.displayOnConsentInput = '[id="kc-display-on-consent-screen"]';
this.displayOnConsentSwitch = this.displayOnConsentSwitch =
this.displayOnConsentInput + " + .pf-c-switch__toggle"; this.displayOnConsentInput + " + .pf-c-switch__toggle";
this.consentScreenTextInput = "#kc-consent-screen-text"; this.consentScreenTextInput = "#kc-consent-screen-text";
@ -73,12 +73,6 @@ export default class CreateClientScopePage extends CommonPage {
return this; return this;
} }
checkClientNameRequiredMessage(exist = true) {
cy.get(this.clientScopeNameError).should((!exist ? "not." : "") + "exist");
return this;
}
getSwitchDisplayOnConsentScreenInput() { getSwitchDisplayOnConsentScreenInput() {
return cy.get(this.displayOnConsentInput); return cy.get(this.displayOnConsentInput);
} }

View file

@ -1,37 +1,36 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { Link } from "react-router-dom-v5-compat";
import { useTranslation } from "react-i18next";
import { Controller, useForm, useWatch } from "react-hook-form";
import {
FormGroup,
ValidatedOptions,
Select,
SelectVariant,
SelectOption,
Switch,
ActionGroup,
Button,
} from "@patternfly/react-core";
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation"; import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
import { import {
clientScopeTypesSelectOptions, ActionGroup,
Button,
FormGroup,
Select,
SelectOption,
SelectVariant,
Switch,
ValidatedOptions,
} from "@patternfly/react-core";
import { useEffect, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form-v7";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom-v5-compat";
import { getProtocolName } from "../../clients/utils";
import {
allClientScopeTypes, allClientScopeTypes,
ClientScopeDefaultOptionalType, ClientScopeDefaultOptionalType,
clientScopeTypesSelectOptions,
} from "../../components/client-scope/ClientScopeTypes"; } from "../../components/client-scope/ClientScopeTypes";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider"; import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
import { convertAttributeNameToForm, convertToFormValues } from "../../util"; import { convertAttributeNameToForm, convertToFormValues } from "../../util";
import { useRealm } from "../../context/realm-context/RealmContext";
import { getProtocolName } from "../../clients/utils";
import { toClientScopes } from "../routes/ClientScopes"; import { toClientScopes } from "../routes/ClientScopes";
import { FormAccess } from "../../components/form-access/FormAccess";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
type ScopeFormProps = { type ScopeFormProps = {
clientScope: ClientScopeRepresentation; clientScope?: ClientScopeRepresentation;
save: (clientScope: ClientScopeDefaultOptionalType) => void; save: (clientScope: ClientScopeDefaultOptionalType) => void;
}; };
@ -43,32 +42,30 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
control, control,
handleSubmit, handleSubmit,
setValue, setValue,
formState: { errors }, formState: { errors, isDirty, isValid },
} = useForm<ClientScopeRepresentation>(); } = useForm<ClientScopeDefaultOptionalType>({ mode: "onChange" });
const { realm } = useRealm(); const { realm } = useRealm();
const providers = useLoginProviders(); const providers = useLoginProviders();
const [open, isOpen] = useState(false); const [open, isOpen] = useState(false);
const [openType, setOpenType] = useState(false); const [openType, setOpenType] = useState(false);
const { id } = useParams<{ id: string }>();
const displayOnConsentScreen = useWatch<string>({ const displayOnConsentScreen: string = useWatch({
control, control,
name: convertAttributeNameToForm("attributes.display.on.consent.screen"), name: convertAttributeNameToForm("attributes.display.on.consent.screen"),
defaultValue: defaultValue:
clientScope.attributes?.["display.on.consent.screen"] ?? clientScope?.attributes?.["display.on.consent.screen"] ?? "true",
(id ? "false" : "true"),
}); });
useEffect(() => { useEffect(() => {
convertToFormValues(clientScope, setValue); convertToFormValues(clientScope ?? {}, setValue);
}, [clientScope]); }, [clientScope]);
return ( return (
<FormAccess <FormAccess
isHorizontal
onSubmit={handleSubmit(save)}
role="manage-clients" role="manage-clients"
onSubmit={handleSubmit(save)}
isHorizontal
> >
<FormGroup <FormGroup
label={t("common:name")} label={t("common:name")}
@ -76,24 +73,19 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
<HelpItem helpText="client-scopes-help:name" fieldLabelId="name" /> <HelpItem helpText="client-scopes-help:name" fieldLabelId="name" />
} }
fieldId="kc-name" fieldId="kc-name"
isRequired
validated={ validated={
errors.name ? ValidatedOptions.error : ValidatedOptions.default errors.name ? ValidatedOptions.error : ValidatedOptions.default
} }
helperTextInvalid={t("common:required")} helperTextInvalid={t("common:required")}
isRequired
> >
<KeycloakTextInput <KeycloakTextInput
ref={register({
required: true,
validate: (value: string) =>
!!value.trim() || t("common:required").toString(),
})}
type="text"
id="kc-name" id="kc-name"
name="name"
validated={ validated={
errors.name ? ValidatedOptions.error : ValidatedOptions.default errors.name ? ValidatedOptions.error : ValidatedOptions.default
} }
isRequired
{...register("name", { required: true })}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
@ -111,17 +103,15 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
helperTextInvalid={t("common:maxLength", { length: 255 })} helperTextInvalid={t("common:maxLength", { length: 255 })}
> >
<KeycloakTextInput <KeycloakTextInput
ref={register({ id="kc-description"
maxLength: 255,
})}
validated={ validated={
errors.description errors.description
? ValidatedOptions.error ? ValidatedOptions.error
: ValidatedOptions.default : ValidatedOptions.default
} }
type="text" {...register("description", {
id="kc-description" maxLength: 255,
name="description" })}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
@ -132,21 +122,21 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldLabelId="client-scopes:type" fieldLabelId="client-scopes:type"
/> />
} }
fieldId="type" fieldId="kc-type"
> >
<Controller <Controller
name="type" name="type"
defaultValue={allClientScopeTypes[0]} defaultValue={allClientScopeTypes[0]}
control={control} control={control}
render={({ onChange, value }) => ( render={({ field }) => (
<Select <Select
id="type" toggleId="kc-type"
variant={SelectVariant.single} variant={SelectVariant.single}
isOpen={openType} isOpen={openType}
selections={value} selections={field.value}
onToggle={setOpenType} onToggle={setOpenType}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); field.onChange(value);
setOpenType(false); setOpenType(false);
}} }}
> >
@ -155,7 +145,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
)} )}
/> />
</FormGroup> </FormGroup>
{!id && ( {!clientScope && (
<FormGroup <FormGroup
label={t("protocol")} label={t("protocol")}
labelIcon={ labelIcon={
@ -170,23 +160,21 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
name="protocol" name="protocol"
defaultValue={providers[0]} defaultValue={providers[0]}
control={control} control={control}
render={({ onChange, value }) => ( render={({ field }) => (
<Select <Select
toggleId="kc-protocol" toggleId="kc-protocol"
required
onToggle={isOpen} onToggle={isOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); field.onChange(value);
isOpen(false); isOpen(false);
}} }}
selections={value} selections={field.value}
variant={SelectVariant.single} variant={SelectVariant.single}
aria-label={t("selectEncryptionType")}
isOpen={open} isOpen={open}
> >
{providers.map((option) => ( {providers.map((option) => (
<SelectOption <SelectOption
selected={option === value} selected={option === field.value}
key={option} key={option}
value={option} value={option}
data-testid={`option-${option}`} data-testid={`option-${option}`}
@ -208,22 +196,21 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldLabelId="client-scopes:displayOnConsentScreen" fieldLabelId="client-scopes:displayOnConsentScreen"
/> />
} }
fieldId="kc-display.on.consent.screen" fieldId="kc-display-on-consent-screen"
> >
<Controller <Controller
name={convertAttributeNameToForm( name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.display.on.consent.screen" "attributes.display.on.consent.screen"
)} )}
control={control} control={control}
defaultValue={displayOnConsentScreen} defaultValue={displayOnConsentScreen}
render={({ onChange, value }) => ( render={({ field }) => (
<Switch <Switch
id="kc-display.on.consent.screen-switch" id="kc-display-on-consent-screen"
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value === "true"} isChecked={field.value === "true"}
onChange={(value) => onChange("" + value)} onChange={(value) => field.onChange(value.toString())}
aria-label={t("displayOnConsentScreen")}
/> />
)} )}
/> />
@ -240,10 +227,12 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="kc-consent-screen-text" fieldId="kc-consent-screen-text"
> >
<KeycloakTextArea <KeycloakTextArea
ref={register}
type="text"
id="kc-consent-screen-text" id="kc-consent-screen-text"
name={convertAttributeNameToForm("attributes.consent.screen.text")} {...register(
convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.consent.screen.text"
)
)}
/> />
</FormGroup> </FormGroup>
)} )}
@ -256,20 +245,21 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldLabelId="client-scopes:includeInTokenScope" fieldLabelId="client-scopes:includeInTokenScope"
/> />
} }
fieldId="includeInTokenScope" fieldId="kc-include-in-token-scope"
> >
<Controller <Controller
name={convertAttributeNameToForm("attributes.include.in.token.scope")} name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.include.in.token.scope"
)}
control={control} control={control}
defaultValue="true" defaultValue="true"
render={({ onChange, value }) => ( render={({ field }) => (
<Switch <Switch
id="includeInTokenScope-switch" id="kc-include-in-token-scope"
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value === "true"} isChecked={field.value === "true"}
onChange={(value) => onChange("" + value)} onChange={(value) => field.onChange(value.toString())}
aria-label={t("includeInTokenScope")}
/> />
)} )}
/> />
@ -285,23 +275,28 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="kc-gui-order" fieldId="kc-gui-order"
> >
<Controller <Controller
name={convertAttributeNameToForm("attributes.gui.order")} name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.gui.order"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ field }) => (
<KeycloakTextInput <KeycloakTextInput
id="kc-gui-order" id="kc-gui-order"
type="number" type="number"
value={value} value={field.value}
data-testid="displayOrder"
min={0} min={0}
onChange={onChange} onChange={field.onChange}
/> />
)} )}
/> />
</FormGroup> </FormGroup>
<ActionGroup> <ActionGroup>
<Button variant="primary" type="submit"> <Button
variant="primary"
type="submit"
isDisabled={!isDirty || !isValid}
>
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button <Button

View file

@ -242,7 +242,7 @@ export default function ClientScopeForm() {
<PageSection variant="light" className="pf-u-p-0"> <PageSection variant="light" className="pf-u-p-0">
{!id && ( {!id && (
<PageSection variant="light"> <PageSection variant="light">
<ScopeForm save={save} clientScope={{}} /> <ScopeForm save={save} />
</PageSection> </PageSection>
)} )}
{id && clientScope && ( {id && clientScope && (

View file

@ -29,7 +29,7 @@ const clientScopeTypes = Object.keys(ClientScope);
export const allClientScopeTypes = Object.keys({ export const allClientScopeTypes = Object.keys({
...AllClientScopes, ...AllClientScopes,
...ClientScope, ...ClientScope,
}); }) as AllClientScopeType[];
export const clientScopeTypesSelectOptions = ( export const clientScopeTypesSelectOptions = (
t: TFunction, t: TFunction,