required can have a value that is a boolean when used with controls (#34251)

* required is a boolean when used with controls

fixes: #33614
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* simplified rules declaration

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* added missing messages

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* use value when it's present

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-10-25 15:24:09 +02:00 committed by GitHub
parent 624817bdc1
commit 2f64f43266
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 43 additions and 127 deletions

View file

@ -84,7 +84,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject {
#jwksUrl = "config.jwksUrl"; #jwksUrl = "config.jwksUrl";
#pkceSwitch = "#config\\.pkceEnabled"; #pkceSwitch = "#config\\.pkceEnabled";
#pkceMethod = "#pkceMethod"; #pkceMethod = "#pkceMethod";
#clientAuth = "#clientAuthentication"; #clientAuth = "#clientAuthMethod";
#clientAssertionSigningAlg = "#clientAssertionSigningAlg"; #clientAssertionSigningAlg = "#clientAssertionSigningAlg";
#clientAssertionAudienceInput = "#clientAssertionAudience"; #clientAssertionAudienceInput = "#clientAssertionAudience";
#jwtX509HeadersSwitch = "#jwtX509HeadersEnabled"; #jwtX509HeadersSwitch = "#jwtX509HeadersEnabled";

View file

@ -84,7 +84,7 @@ export const EditFlow = ({ execution, onRowChange }: EditFlowProps) => {
name="displayName" name="displayName"
label={t("name")} label={t("name")}
labelIcon={t("flowNameHelp")} labelIcon={t("flowNameHelp")}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
/> />
<TextAreaControl <TextAreaControl
name="description" name="description"

View file

@ -161,7 +161,7 @@ export const ExecutionConfigModal = ({
name="alias" name="alias"
label={t("alias")} label={t("alias")}
labelIcon={t("authenticationAliasHelp")} labelIcon={t("authenticationAliasHelp")}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
isDisabled={!!config} isDisabled={!!config}
/> />
<DynamicComponents <DynamicComponents

View file

@ -90,7 +90,7 @@ export const AddSubFlowModal = ({
name="name" name="name"
label={t("name")} label={t("name")}
labelIcon={t("clientIdHelp")} labelIcon={t("clientIdHelp")}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
/> />
<TextControl <TextControl
name="description" name="description"

View file

@ -10,7 +10,7 @@ export const NameDescription = () => {
name="alias" name="alias"
label={t("name")} label={t("name")}
labelIcon={t("flowNameHelp")} labelIcon={t("flowNameHelp")}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
/> />
<TextControl <TextControl
name="description" name="description"

View file

@ -100,10 +100,7 @@ export const CibaPolicy = ({ realm, realmUpdated }: CibaPolicyProps) => {
value: CIBA_EXPIRES_IN_MAX, value: CIBA_EXPIRES_IN_MAX,
message: t("lessThan", { value: CIBA_EXPIRES_IN_MAX }), message: t("lessThan", { value: CIBA_EXPIRES_IN_MAX }),
}, },
required: { required: t("required"),
value: true,
message: t("required"),
},
}} }}
/> />
<TextControl <TextControl
@ -124,10 +121,7 @@ export const CibaPolicy = ({ realm, realmUpdated }: CibaPolicyProps) => {
value: CIBA_INTERVAL_MAX, value: CIBA_INTERVAL_MAX,
message: t("lessThan", { value: CIBA_INTERVAL_MAX }), message: t("lessThan", { value: CIBA_INTERVAL_MAX }),
}, },
required: { required: t("required"),
value: true,
message: t("required"),
},
}} }}
/> />
<SelectControl <SelectControl

View file

@ -161,7 +161,7 @@ export const WebauthnPolicy = ({
name={`${namePrefix}RpEntityName`} name={`${namePrefix}RpEntityName`}
label={t("webAuthnPolicyRpEntityName")} label={t("webAuthnPolicyRpEntityName")}
labelIcon={t("webAuthnPolicyRpEntityNameHelp")} labelIcon={t("webAuthnPolicyRpEntityNameHelp")}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
/> />
<WebauthnSelect <WebauthnSelect
name={`${namePrefix}SignatureAlgorithms`} name={`${namePrefix}SignatureAlgorithms`}

View file

@ -217,7 +217,7 @@ export default function MappingDetails() {
label={t("name")} label={t("name")}
labelIcon={t("mapperNameHelp")} labelIcon={t("mapperNameHelp")}
readOnlyVariant={isUpdating ? "default" : undefined} readOnlyVariant={isUpdating ? "default" : undefined}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
/> />
<DynamicComponents <DynamicComponents
properties={mapping?.properties || []} properties={mapping?.properties || []}

View file

@ -79,10 +79,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
label={t("name")} label={t("name")}
labelIcon={t("scopeNameHelp")} labelIcon={t("scopeNameHelp")}
rules={{ rules={{
required: { required: t("required"),
value: true,
message: t("required"),
},
onChange: (e) => { onChange: (e) => {
if (isDynamicScopesEnabled) if (isDynamicScopesEnabled)
setDynamicRegex(e.target.validated, true); setDynamicRegex(e.target.validated, true);

View file

@ -19,7 +19,7 @@ export const ClientDescription = ({
name="clientId" name="clientId"
label={t("clientId")} label={t("clientId")}
labelIcon={t("clientIdHelp")} labelIcon={t("clientIdHelp")}
rules={{ required: { value: true, message: t("required") } }} rules={{ required: t("required") }}
/> />
<TextControl <TextControl
name="name" name="name"

View file

@ -1,11 +1,5 @@
import { import { SelectControl } from "@keycloak/keycloak-ui-shared";
HelpItem, import { useFormContext, useWatch } from "react-hook-form";
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { sortProviders } from "../../util"; import { sortProviders } from "../../util";
@ -25,8 +19,6 @@ export const OIDCAuthentication = ({ create = true }: { create?: boolean }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { control } = useFormContext(); const { control } = useFormContext();
const [openClientAuth, setOpenClientAuth] = useState(false);
const [openClientAuthSigAlg, setOpenClientAuthSigAlg] = useState(false);
const clientAuthMethod = useWatch({ const clientAuthMethod = useWatch({
control: control, control: control,
@ -35,96 +27,34 @@ export const OIDCAuthentication = ({ create = true }: { create?: boolean }) => {
return ( return (
<> <>
<FormGroup <SelectControl
label={t("clientAuthentication")}
labelIcon={
<HelpItem
helpText={t("clientAuthenticationHelp")}
fieldLabelId="clientAuthentication"
/>
}
fieldId="clientAuthentication"
>
<Controller
name="config.clientAuthMethod" name="config.clientAuthMethod"
defaultValue={clientAuthentications[0]} label={t("clientAuthentication")}
control={control} labelIcon={t("clientAuthenticationHelp")}
render={({ field }) => ( options={clientAuthentications.map((auth) => ({
<KeycloakSelect key: auth,
toggleId="clientAuthentication" value: t(`clientAuthentications.${auth}`),
onToggle={() => setOpenClientAuth(!openClientAuth)} }))}
onSelect={(value) => { controller={{
field.onChange(value as string); defaultValue: clientAuthentications[0],
setOpenClientAuth(false);
}} }}
selections={field.value}
variant={SelectVariant.single}
aria-label={t("clientAuthentication")}
isOpen={openClientAuth}
>
{clientAuthentications.map((option) => (
<SelectOption
selected={option === field.value}
key={option}
value={option}
>
{t(`clientAuthentications.${option}`)}
</SelectOption>
))}
</KeycloakSelect>
)}
/> />
</FormGroup>
<ClientIdSecret <ClientIdSecret
secretRequired={clientAuthMethod !== "private_key_jwt"} secretRequired={clientAuthMethod !== "private_key_jwt"}
create={create} create={create}
/> />
<FormGroup <SelectControl
label={t("clientAssertionSigningAlg")}
labelIcon={
<HelpItem
helpText={t("clientAssertionSigningAlgHelp")}
fieldLabelId="clientAssertionSigningAlg"
/>
}
fieldId="clientAssertionSigningAlg"
>
<Controller
name="config.clientAssertionSigningAlg" name="config.clientAssertionSigningAlg"
defaultValue="" label={t("clientAssertionSigningAlg")}
control={control} labelIcon={t("clientAssertionSigningAlgHelp")}
render={({ field }) => ( options={[
<KeycloakSelect { key: "", value: t("algorithmNotSpecified") },
maxHeight={200} ...sortProviders(providers).map((p) => ({ key: p, value: p })),
toggleId="clientAssertionSigningAlg"
onToggle={() => setOpenClientAuthSigAlg(!openClientAuthSigAlg)}
onSelect={(value) => {
field.onChange(value.toString());
setOpenClientAuthSigAlg(false);
}}
selections={field.value || t("algorithmNotSpecified")}
variant={SelectVariant.single}
aria-label={t("selectClientAssertionSigningAlg")}
isOpen={openClientAuthSigAlg}
>
{[
<SelectOption selected={field.value === ""} key="" value="">
{t("algorithmNotSpecified")}
</SelectOption>,
...sortProviders(providers).map((option) => (
<SelectOption
selected={option === field.value}
key={option}
value={option}
>
{option}
</SelectOption>
)),
]} ]}
</KeycloakSelect> controller={{
)} defaultValue: "",
}}
/> />
</FormGroup>
{(clientAuthMethod === "private_key_jwt" || {(clientAuthMethod === "private_key_jwt" ||
clientAuthMethod === "client_secret_jwt") && ( clientAuthMethod === "client_secret_jwt") && (
<TextField <TextField

View file

@ -476,7 +476,7 @@ export default function NewClientPolicy() {
name="name" name="name"
label={t("name")} label={t("name")}
rules={{ rules={{
required: { value: true, message: t("required") }, required: t("required"),
validate: (value) => validate: (value) =>
policies.some((policy) => policy.name === value) policies.some((policy) => policy.name === value)
? t("createClientProfileNameHelperText").toString() ? t("createClientProfileNameHelperText").toString()

View file

@ -467,10 +467,7 @@ export default function AttributesGroupForm() {
labelIcon={t("nameHintHelp")} labelIcon={t("nameHintHelp")}
isDisabled={!!matchingGroup || editMode} isDisabled={!!matchingGroup || editMode}
rules={{ rules={{
required: { required: t("required"),
value: true,
message: t("required"),
},
onChange: (event) => { onChange: (event) => {
handleAttributesGroupNameChange(event, event.target.value); handleAttributesGroupNameChange(event, event.target.value);
}, },

View file

@ -332,10 +332,7 @@ export const AddTranslationsDialog = ({
label={t("translationValue")} label={t("translationValue")}
data-testid={`translation-value-${rowIndex}`} data-testid={`translation-value-${rowIndex}`}
rules={{ rules={{
required: { required: t("required"),
value: true,
message: t("required"),
},
}} }}
/> />
)} )}

View file

@ -41,7 +41,7 @@ export const LdapSettingsSearching = ({
controller={{ controller={{
defaultValue: "", defaultValue: "",
rules: { rules: {
required: { value: true, message: t("validateEditMode") }, required: t("validateEditMode"),
}, },
}} }}
options={["", "READ_ONLY", "WRITABLE", "UNSYNCED"]} options={["", "READ_ONLY", "WRITABLE", "UNSYNCED"]}

View file

@ -11,6 +11,7 @@ import {
UseControllerProps, UseControllerProps,
useController, useController,
} from "react-hook-form"; } from "react-hook-form";
import { getRuleValue } from "../utils/getRuleValue";
import { FormLabel } from "./FormLabel"; import { FormLabel } from "./FormLabel";
import { PasswordInput, PasswordInputProps } from "./PasswordInput"; import { PasswordInput, PasswordInputProps } from "./PasswordInput";
@ -32,7 +33,7 @@ export const PasswordControl = <
props: PasswordControlProps<T, P>, props: PasswordControlProps<T, P>,
) => { ) => {
const { labelIcon, ...rest } = props; const { labelIcon, ...rest } = props;
const required = !!props.rules?.required; const required = !!getRuleValue(props.rules?.required);
const defaultValue = props.defaultValue ?? ("" as PathValue<T, P>); const defaultValue = props.defaultValue ?? ("" as PathValue<T, P>);
const { field, fieldState } = useController({ const { field, fieldState } = useController({

View file

@ -14,7 +14,7 @@ import {
UseControllerProps, UseControllerProps,
useController, useController,
} from "react-hook-form"; } from "react-hook-form";
import { getRuleValue } from "../utils/getRuleValue";
import { FormLabel } from "./FormLabel"; import { FormLabel } from "./FormLabel";
export type TextControlProps< export type TextControlProps<
@ -36,7 +36,7 @@ export const TextControl = <
props: TextControlProps<T, P>, props: TextControlProps<T, P>,
) => { ) => {
const { labelIcon, helperText, ...rest } = props; const { labelIcon, helperText, ...rest } = props;
const required = !!props.rules?.required; const required = !!getRuleValue(props.rules?.required);
const defaultValue = props.defaultValue ?? ("" as PathValue<T, P>); const defaultValue = props.defaultValue ?? ("" as PathValue<T, P>);
const { field, fieldState } = useController({ const { field, fieldState } = useController({