Ream-settings -> User Profile -> Edit Attribute (#2343)
* edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * edit attribute - wip * refactor * refactor * refactor * refactor * refactor * refactor * refactor * moved form initialisation to central place * fixed loading values Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com> Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
be2d7e268e
commit
4c70064bd4
8 changed files with 297 additions and 239 deletions
|
@ -8,33 +8,32 @@ import {
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory, useParams } from "react-router-dom";
|
||||||
import { ScrollForm } from "../components/scroll-form/ScrollForm";
|
import { ScrollForm } from "../components/scroll-form/ScrollForm";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
|
||||||
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||||
import { AttributeGeneralSettings } from "./user-profile/attribute/AttributeGeneralSettings";
|
import { AttributeGeneralSettings } from "./user-profile/attribute/AttributeGeneralSettings";
|
||||||
import { AttributePermission } from "./user-profile/attribute/AttributePermission";
|
import { AttributePermission } from "./user-profile/attribute/AttributePermission";
|
||||||
import { AttributeValidations } from "./user-profile/attribute/AttributeValidations";
|
import { AttributeValidations } from "./user-profile/attribute/AttributeValidations";
|
||||||
import { toUserProfile } from "./routes/UserProfile";
|
import { toUserProfile } from "./routes/UserProfile";
|
||||||
import "./realm-settings-section.css";
|
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { AttributeAnnotations } from "./user-profile/attribute/AttributeAnnotations";
|
import { AttributeAnnotations } from "./user-profile/attribute/AttributeAnnotations";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { UserProfileProvider } from "./user-profile/UserProfileContext";
|
import { UserProfileProvider } from "./user-profile/UserProfileContext";
|
||||||
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||||
|
import type { AttributeParams } from "./routes/Attribute";
|
||||||
import type { KeyValueType } from "../components/attribute-form/attribute-convert";
|
import type { KeyValueType } from "../components/attribute-form/attribute-convert";
|
||||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
import { convertToFormValues } from "../util";
|
||||||
|
import { flatten } from "flat";
|
||||||
|
|
||||||
type UserProfileAttributeType = UserProfileAttribute &
|
import "./realm-settings-section.css";
|
||||||
AttributeRequired &
|
|
||||||
Permission;
|
|
||||||
|
|
||||||
type AttributeRequired = {
|
type UserProfileAttributeType = UserProfileAttribute & Attribute & Permission;
|
||||||
|
|
||||||
|
type Attribute = {
|
||||||
roles: string[];
|
roles: string[];
|
||||||
scopeRequired: string[];
|
scopes: string[];
|
||||||
enabledWhen: boolean;
|
isRequired: boolean;
|
||||||
requiredWhen: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Permission = {
|
type Permission = {
|
||||||
|
@ -63,7 +62,8 @@ const CreateAttributeFormContent = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const { realm } = useRealm();
|
const { realm, attributeName } = useParams<AttributeParams>();
|
||||||
|
const editMode = attributeName ? true : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserProfileProvider>
|
<UserProfileProvider>
|
||||||
|
@ -87,7 +87,7 @@ const CreateAttributeFormContent = ({
|
||||||
type="submit"
|
type="submit"
|
||||||
data-testid="attribute-create"
|
data-testid="attribute-create"
|
||||||
>
|
>
|
||||||
{t("common:create")}
|
{editMode ? t("common:save") : t("common:create")}
|
||||||
</Button>
|
</Button>
|
||||||
<Link
|
<Link
|
||||||
to={toUserProfile({ realm, tab: "attributes" })}
|
to={toUserProfile({ realm, tab: "attributes" })}
|
||||||
|
@ -103,51 +103,51 @@ const CreateAttributeFormContent = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NewAttributeSettings() {
|
export default function NewAttributeSettings() {
|
||||||
const { realm: realmName } = useRealm();
|
const { realm, attributeName } = useParams<AttributeParams>();
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const form = useForm<UserProfileConfig>();
|
const form = useForm<UserProfileConfig>({ shouldUnregister: false });
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const [config, setConfig] = useState<UserProfileConfig | null>(null);
|
const [config, setConfig] = useState<UserProfileConfig | null>(null);
|
||||||
const [clientScopes, setClientScopes] =
|
const editMode = attributeName ? true : false;
|
||||||
useState<ClientScopeRepresentation[]>();
|
|
||||||
|
const convert = (obj: Record<string, unknown>[] | undefined) =>
|
||||||
|
Object.entries(obj || []).map(([key, value]) => ({
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() =>
|
() => adminClient.users.getProfile({ realm }),
|
||||||
Promise.all([
|
(config) => {
|
||||||
adminClient.users.getProfile({ realm: realmName }),
|
|
||||||
adminClient.clientScopes.find(),
|
|
||||||
]),
|
|
||||||
([config, clientScopes]) => {
|
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
setClientScopes(clientScopes);
|
const {
|
||||||
|
annotations,
|
||||||
|
validations,
|
||||||
|
permissions,
|
||||||
|
selector,
|
||||||
|
required,
|
||||||
|
...values
|
||||||
|
} = config.attributes!.find(
|
||||||
|
(attribute) => attribute.name === attributeName
|
||||||
|
)!;
|
||||||
|
convertToFormValues(values, form.setValue);
|
||||||
|
Object.entries(
|
||||||
|
flatten({ permissions, selector, required }, { safe: true })
|
||||||
|
).map(([key, value]) => form.setValue(key, value));
|
||||||
|
form.setValue("annotations", convert(annotations));
|
||||||
|
form.setValue("validations", convert(validations));
|
||||||
|
form.setValue("isRequired", required !== undefined);
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const save = async (profileConfig: UserProfileAttributeType) => {
|
const save = async (profileConfig: UserProfileAttributeType) => {
|
||||||
const scopeNames = clientScopes?.map((clientScope) => clientScope.name);
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
scopes: profileConfig.enabledWhen
|
|
||||||
? scopeNames
|
|
||||||
: profileConfig.selector?.scopes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const required = {
|
|
||||||
roles: profileConfig.roles,
|
|
||||||
scopes: profileConfig.requiredWhen
|
|
||||||
? scopeNames
|
|
||||||
: profileConfig.scopeRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const validations = profileConfig.validations?.reduce(
|
const validations = profileConfig.validations?.reduce(
|
||||||
(prevValidations: any, currentValidations: any) => {
|
(prevValidations: any, currentValidations: any) => {
|
||||||
prevValidations[currentValidations.name] =
|
prevValidations[currentValidations.key] =
|
||||||
currentValidations.config.length === 0
|
currentValidations.value.length === 0 ? {} : currentValidations.value;
|
||||||
? {}
|
|
||||||
: currentValidations.config;
|
|
||||||
return prevValidations;
|
return prevValidations;
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
|
@ -158,29 +158,55 @@ export default function NewAttributeSettings() {
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const newAttribute = [
|
const patchAttributes = () =>
|
||||||
{
|
config?.attributes!.map((attribute) => {
|
||||||
name: profileConfig.name,
|
if (attribute.name !== attributeName) {
|
||||||
displayName: profileConfig.displayName,
|
return attribute;
|
||||||
required,
|
}
|
||||||
validations,
|
|
||||||
selector,
|
|
||||||
permissions: profileConfig.permissions,
|
|
||||||
annotations,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const newAttributesList = config?.attributes!.concat(
|
return Object.assign(
|
||||||
newAttribute as UserProfileAttribute
|
{
|
||||||
);
|
...attribute,
|
||||||
|
name: attributeName,
|
||||||
|
displayName: profileConfig.displayName!,
|
||||||
|
validations,
|
||||||
|
selector: profileConfig.selector,
|
||||||
|
permissions: profileConfig.permissions!,
|
||||||
|
annotations,
|
||||||
|
},
|
||||||
|
profileConfig.isRequired
|
||||||
|
? { required: profileConfig.required }
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const addAttribute = () =>
|
||||||
|
config?.attributes!.concat([
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
name: profileConfig.name,
|
||||||
|
displayName: profileConfig.displayName!,
|
||||||
|
required: profileConfig.isRequired ? profileConfig.required : {},
|
||||||
|
validations,
|
||||||
|
selector: profileConfig.selector,
|
||||||
|
permissions: profileConfig.permissions!,
|
||||||
|
annotations,
|
||||||
|
},
|
||||||
|
profileConfig.isRequired
|
||||||
|
? { required: profileConfig.required }
|
||||||
|
: undefined
|
||||||
|
),
|
||||||
|
] as UserProfileAttribute);
|
||||||
|
|
||||||
|
const updatedAttributes = editMode ? patchAttributes() : addAttribute();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await adminClient.users.updateProfile({
|
await adminClient.users.updateProfile({
|
||||||
attributes: newAttributesList,
|
attributes: updatedAttributes as UserProfileAttribute[],
|
||||||
realm: realmName,
|
realm,
|
||||||
});
|
});
|
||||||
|
|
||||||
history.push(toUserProfile({ realm: realmName, tab: "attributes" }));
|
history.push(toUserProfile({ realm, tab: "attributes" }));
|
||||||
|
|
||||||
addAlert(
|
addAlert(
|
||||||
t("realm-settings:createAttributeSuccess"),
|
t("realm-settings:createAttributeSuccess"),
|
||||||
|
@ -194,8 +220,8 @@ export default function NewAttributeSettings() {
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={t("createAttribute")}
|
titleKey={editMode ? attributeName : t("createAttribute")}
|
||||||
subKey={t("createAttributeSubTitle")}
|
subKey={editMode ? "" : t("createAttributeSubTitle")}
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<CreateAttributeFormContent save={() => form.handleSubmit(save)()} />
|
<CreateAttributeFormContent save={() => form.handleSubmit(save)()} />
|
||||||
|
|
|
@ -404,6 +404,7 @@ export default {
|
||||||
updatedUserProfileSuccess: "User Profile configuration has been saved",
|
updatedUserProfileSuccess: "User Profile configuration has been saved",
|
||||||
updatedUserProfileError: "User Profile configuration hasn't been saved",
|
updatedUserProfileError: "User Profile configuration hasn't been saved",
|
||||||
createAttribute: "Create attribute",
|
createAttribute: "Create attribute",
|
||||||
|
editAttribute: "Edit attribute",
|
||||||
createAttributeSubTitle: "Create a new attribute",
|
createAttributeSubTitle: "Create a new attribute",
|
||||||
createAttributeSuccess:
|
createAttributeSuccess:
|
||||||
"Success! User Profile configuration has been saved.",
|
"Success! User Profile configuration has been saved.",
|
||||||
|
@ -415,6 +416,8 @@ export default {
|
||||||
"Are you sure you want to permanently delete the attribute {{attributeName}}?",
|
"Are you sure you want to permanently delete the attribute {{attributeName}}?",
|
||||||
deleteAttributeSuccess: "Attribute deleted",
|
deleteAttributeSuccess: "Attribute deleted",
|
||||||
deleteAttributeError: "Attribute not deleted",
|
deleteAttributeError: "Attribute not deleted",
|
||||||
|
always: "Always",
|
||||||
|
scopesAsRequested: "Scopes are requested",
|
||||||
generalSettings: "General settings",
|
generalSettings: "General settings",
|
||||||
permission: "Permission",
|
permission: "Permission",
|
||||||
validations: "Validations",
|
validations: "Validations",
|
||||||
|
|
|
@ -11,7 +11,7 @@ export type AttributeParams = {
|
||||||
export const AttributeRoute: RouteDef = {
|
export const AttributeRoute: RouteDef = {
|
||||||
path: "/:realm/realm-settings/userProfile/attributes/:attributeName/edit-attribute",
|
path: "/:realm/realm-settings/userProfile/attributes/:attributeName/edit-attribute",
|
||||||
component: lazy(() => import("../NewAttributeSettings")),
|
component: lazy(() => import("../NewAttributeSettings")),
|
||||||
breadcrumb: (t) => t("realm-settings:createAttribute"),
|
breadcrumb: (t) => t("realm-settings:editAttribute"),
|
||||||
access: "manage-realm",
|
access: "manage-realm",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -59,8 +59,6 @@ export const AttributesTab = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToCreate = () => history.push(toAddAttribute({ realm: realmName }));
|
|
||||||
|
|
||||||
const updatedAttributes = config?.attributes!.filter(
|
const updatedAttributes = config?.attributes!.filter(
|
||||||
(attribute) => attribute.name !== attributeToDelete
|
(attribute) => attribute.name !== attributeToDelete
|
||||||
);
|
);
|
||||||
|
@ -173,7 +171,14 @@ export const AttributesTab = () => {
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
title: t("common:edit"),
|
title: t("common:edit"),
|
||||||
onClick: goToCreate,
|
onClick: (_key, _idx, component) => {
|
||||||
|
history.push(
|
||||||
|
toAttribute({
|
||||||
|
realm: realmName,
|
||||||
|
attributeName: component.name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("common:delete"),
|
title: t("common:delete"),
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormGroup, Grid, GridItem } from "@patternfly/react-core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FormGroup, Grid, GridItem } from "@patternfly/react-core";
|
||||||
|
|
||||||
import { FormAccess } from "../../../components/form-access/FormAccess";
|
import { FormAccess } from "../../../components/form-access/FormAccess";
|
||||||
import { FormProvider, useFormContext } from "react-hook-form";
|
|
||||||
import { AttributeInput } from "../../../components/attribute-input/AttributeInput";
|
import { AttributeInput } from "../../../components/attribute-input/AttributeInput";
|
||||||
|
|
||||||
import "../../realm-settings-section.css";
|
import "../../realm-settings-section.css";
|
||||||
|
|
||||||
export const AttributeAnnotations = () => {
|
export const AttributeAnnotations = () => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormAccess role="manage-realm" isHorizontal>
|
<FormAccess role="manage-realm" isHorizontal>
|
||||||
|
@ -21,9 +20,7 @@ export const AttributeAnnotations = () => {
|
||||||
>
|
>
|
||||||
<Grid className="kc-annotations">
|
<Grid className="kc-annotations">
|
||||||
<GridItem>
|
<GridItem>
|
||||||
<FormProvider {...form}>
|
<AttributeInput name="annotations" />
|
||||||
<AttributeInput name="annotations" />
|
|
||||||
</FormProvider>
|
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
|
@ -17,9 +17,12 @@ import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
import { FormAccess } from "../../../components/form-access/FormAccess";
|
import { FormAccess } from "../../../components/form-access/FormAccess";
|
||||||
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
||||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||||
|
import type { AttributeParams } from "../../routes/Attribute";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { isEqual } from "lodash-es";
|
||||||
|
|
||||||
import "../../realm-settings-section.css";
|
import "../../realm-settings-section.css";
|
||||||
|
|
||||||
const ENABLED_REQUIRED_WHEN = ["Always", "Scopes are requested"] as const;
|
|
||||||
const REQUIRED_FOR = [
|
const REQUIRED_FOR = [
|
||||||
{ label: "Both users and admins", value: ["admin", "user"] },
|
{ label: "Both users and admins", value: ["admin", "user"] },
|
||||||
{ label: "Only users", value: ["user"] },
|
{ label: "Only users", value: ["user"] },
|
||||||
|
@ -36,12 +39,24 @@ export const AttributeGeneralSettings = () => {
|
||||||
const [selectRequiredForOpen, setSelectRequiredForOpen] = useState(false);
|
const [selectRequiredForOpen, setSelectRequiredForOpen] = useState(false);
|
||||||
const [isAttributeGroupDropdownOpen, setIsAttributeGroupDropdownOpen] =
|
const [isAttributeGroupDropdownOpen, setIsAttributeGroupDropdownOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [enabledWhenSelection, setEnabledWhenSelection] = useState("Always");
|
const { attributeName } = useParams<AttributeParams>();
|
||||||
const [requiredWhenSelection, setRequiredWhenSelection] = useState("Always");
|
const editMode = attributeName ? true : false;
|
||||||
|
|
||||||
const requiredToggle = useWatch({
|
const selectedScopes = useWatch({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
name: "required",
|
name: "selector.scopes",
|
||||||
|
defaultValue: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const requiredScopes = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "required.scopes",
|
||||||
|
defaultValue: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const required = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "isRequired",
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,10 +92,11 @@ export const AttributeGeneralSettings = () => {
|
||||||
ref={form.register({
|
ref={form.register({
|
||||||
required: {
|
required: {
|
||||||
value: true,
|
value: true,
|
||||||
message: `${t("validateName")}`,
|
message: t("validateName"),
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
data-testid="attribute-name"
|
data-testid="attribute-name"
|
||||||
|
isDisabled={editMode}
|
||||||
validated={form.errors.name ? "error" : "default"}
|
validated={form.errors.name ? "error" : "default"}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -141,44 +157,49 @@ export const AttributeGeneralSettings = () => {
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<Divider />
|
<Divider />
|
||||||
<FormGroup label={t("enabledWhen")} fieldId="enabledWhen" hasNoPaddingTop>
|
<FormGroup label={t("enabledWhen")} fieldId="enabledWhen" hasNoPaddingTop>
|
||||||
<Controller
|
<Radio
|
||||||
|
id="always"
|
||||||
|
data-testid="always"
|
||||||
|
isChecked={selectedScopes.length === clientScopes?.length}
|
||||||
name="enabledWhen"
|
name="enabledWhen"
|
||||||
data-testid="enabledWhen"
|
label={t("always")}
|
||||||
control={form.control}
|
onChange={(value) => {
|
||||||
defaultValue={ENABLED_REQUIRED_WHEN[0]}
|
if (value) {
|
||||||
render={({ onChange, value }) => (
|
form.setValue(
|
||||||
<>
|
"selector.scopes",
|
||||||
{ENABLED_REQUIRED_WHEN.map((option) => (
|
clientScopes?.map((s) => s.name)
|
||||||
<Radio
|
);
|
||||||
id={option}
|
} else {
|
||||||
key={option}
|
form.setValue("selector.scopes", []);
|
||||||
data-testid={option}
|
}
|
||||||
isChecked={value === option}
|
}}
|
||||||
name="enabledWhen"
|
className="pf-u-mb-md"
|
||||||
onChange={() => {
|
/>
|
||||||
onChange(option);
|
<Radio
|
||||||
setEnabledWhenSelection(option);
|
id="scopesAsRequested"
|
||||||
}}
|
data-testid="scopesAsRequested"
|
||||||
label={option}
|
isChecked={selectedScopes.length !== clientScopes?.length}
|
||||||
className="pf-u-mb-md"
|
name="enabledWhen"
|
||||||
/>
|
label={t("scopesAsRequested")}
|
||||||
))}
|
onChange={(value) => {
|
||||||
</>
|
if (value) {
|
||||||
)}
|
form.setValue("selector.scopes", []);
|
||||||
|
} else {
|
||||||
|
form.setValue(
|
||||||
|
"selector.scopes",
|
||||||
|
clientScopes?.map((s) => s.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="pf-u-mb-md"
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup fieldId="kc-scope-enabled-when">
|
<FormGroup fieldId="kc-scope-enabled-when">
|
||||||
<Controller
|
<Controller
|
||||||
name="scopes"
|
name="selector.scopes"
|
||||||
control={form.control}
|
control={form.control}
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
render={({
|
render={({ onChange, value }) => (
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
onChange: (newValue: string[]) => void;
|
|
||||||
value: string[];
|
|
||||||
}) => (
|
|
||||||
<Select
|
<Select
|
||||||
name="scopes"
|
name="scopes"
|
||||||
data-testid="enabled-when-scope-field"
|
data-testid="enabled-when-scope-field"
|
||||||
|
@ -196,7 +217,7 @@ export const AttributeGeneralSettings = () => {
|
||||||
let changedValue = [""];
|
let changedValue = [""];
|
||||||
if (value) {
|
if (value) {
|
||||||
changedValue = value.includes(option)
|
changedValue = value.includes(option)
|
||||||
? value.filter((item) => item !== option)
|
? value.filter((item: string) => item !== option)
|
||||||
: [...value, option];
|
: [...value, option];
|
||||||
} else {
|
} else {
|
||||||
changedValue = [option];
|
changedValue = [option];
|
||||||
|
@ -209,7 +230,7 @@ export const AttributeGeneralSettings = () => {
|
||||||
onChange([]);
|
onChange([]);
|
||||||
}}
|
}}
|
||||||
isOpen={selectEnabledWhenOpen}
|
isOpen={selectEnabledWhenOpen}
|
||||||
isDisabled={enabledWhenSelection === "Always"}
|
isDisabled={selectedScopes.length === clientScopes?.length}
|
||||||
aria-labelledby={"scope"}
|
aria-labelledby={"scope"}
|
||||||
>
|
>
|
||||||
{clientScopes?.map((option) => (
|
{clientScopes?.map((option) => (
|
||||||
|
@ -232,24 +253,22 @@ export const AttributeGeneralSettings = () => {
|
||||||
hasNoPaddingTop
|
hasNoPaddingTop
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="required"
|
name="isRequired"
|
||||||
|
data-testid="required"
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
render={({ onChange, value }) => (
|
render={({ onChange, value }) => (
|
||||||
<Switch
|
<Switch
|
||||||
id={"kc-required"}
|
id={"kc-required"}
|
||||||
onChange={(value) => {
|
onChange={onChange}
|
||||||
onChange(value);
|
isChecked={value}
|
||||||
form.setValue("required", value);
|
|
||||||
}}
|
|
||||||
isChecked={value === true}
|
|
||||||
label={t("common:on")}
|
label={t("common:on")}
|
||||||
labelOff={t("common:off")}
|
labelOff={t("common:off")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
></Controller>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
{requiredToggle && (
|
{required && (
|
||||||
<>
|
<>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("requiredFor")}
|
label={t("requiredFor")}
|
||||||
|
@ -257,7 +276,7 @@ export const AttributeGeneralSettings = () => {
|
||||||
hasNoPaddingTop
|
hasNoPaddingTop
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="roles"
|
name="required.roles"
|
||||||
data-testid="requiredFor"
|
data-testid="requiredFor"
|
||||||
defaultValue={REQUIRED_FOR[0].value}
|
defaultValue={REQUIRED_FOR[0].value}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
@ -267,10 +286,12 @@ export const AttributeGeneralSettings = () => {
|
||||||
<Radio
|
<Radio
|
||||||
id={option.label}
|
id={option.label}
|
||||||
key={option.label}
|
key={option.label}
|
||||||
data-testid={option}
|
data-testid={option.label}
|
||||||
isChecked={value === option.value}
|
isChecked={isEqual(value, option.value)}
|
||||||
name="roles"
|
name="roles"
|
||||||
onChange={() => onChange(option.value)}
|
onChange={() => {
|
||||||
|
onChange(option.value);
|
||||||
|
}}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
className="kc-requiredFor-option"
|
className="kc-requiredFor-option"
|
||||||
/>
|
/>
|
||||||
|
@ -284,44 +305,49 @@ export const AttributeGeneralSettings = () => {
|
||||||
fieldId="requiredWhen"
|
fieldId="requiredWhen"
|
||||||
hasNoPaddingTop
|
hasNoPaddingTop
|
||||||
>
|
>
|
||||||
<Controller
|
<Radio
|
||||||
|
id="requiredAlways"
|
||||||
|
data-testid="requiredAlways"
|
||||||
|
isChecked={requiredScopes.length === clientScopes?.length}
|
||||||
name="requiredWhen"
|
name="requiredWhen"
|
||||||
data-testid="requiredWhen"
|
label={t("always")}
|
||||||
defaultValue={ENABLED_REQUIRED_WHEN[0]}
|
onChange={(value) => {
|
||||||
control={form.control}
|
if (value) {
|
||||||
render={({ onChange, value }) => (
|
form.setValue(
|
||||||
<>
|
"required.scopes",
|
||||||
{ENABLED_REQUIRED_WHEN.map((option) => (
|
clientScopes?.map((s) => s.name)
|
||||||
<Radio
|
);
|
||||||
id={option}
|
} else {
|
||||||
key={option}
|
form.setValue("required.scopes", []);
|
||||||
data-testid={option}
|
}
|
||||||
isChecked={value === option}
|
}}
|
||||||
name="requiredWhen"
|
className="pf-u-mb-md"
|
||||||
onChange={() => {
|
/>
|
||||||
onChange(option);
|
<Radio
|
||||||
setRequiredWhenSelection(option);
|
id="requiredScopesAsRequested"
|
||||||
}}
|
data-testid="requiredScopesAsRequested"
|
||||||
label={option}
|
isChecked={requiredScopes.length !== clientScopes?.length}
|
||||||
className="pf-u-mb-md"
|
name="requiredWhen"
|
||||||
/>
|
label={t("scopesAsRequested")}
|
||||||
))}
|
onChange={(value) => {
|
||||||
</>
|
if (value) {
|
||||||
)}
|
form.setValue("required.scopes", []);
|
||||||
|
} else {
|
||||||
|
form.setValue(
|
||||||
|
"required.scopes",
|
||||||
|
clientScopes?.map((s) => s.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="pf-u-mb-md"
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup fieldId="kc-scope-required-when">
|
<FormGroup fieldId="kc-scope-required-when">
|
||||||
<Controller
|
<Controller
|
||||||
name="scopeRequired"
|
name="required.scopes"
|
||||||
control={form.control}
|
control={form.control}
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
render={({
|
render={({ onChange, value }) => (
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
onChange: (newValue: string[]) => void;
|
|
||||||
value: string[];
|
|
||||||
}) => (
|
|
||||||
<Select
|
<Select
|
||||||
name="scopeRequired"
|
name="scopeRequired"
|
||||||
data-testid="required-when-scope-field"
|
data-testid="required-when-scope-field"
|
||||||
|
@ -339,7 +365,7 @@ export const AttributeGeneralSettings = () => {
|
||||||
let changedValue = [""];
|
let changedValue = [""];
|
||||||
if (value) {
|
if (value) {
|
||||||
changedValue = value.includes(option)
|
changedValue = value.includes(option)
|
||||||
? value.filter((item) => item !== option)
|
? value.filter((item: string) => item !== option)
|
||||||
: [...value, option];
|
: [...value, option];
|
||||||
} else {
|
} else {
|
||||||
changedValue = [option];
|
changedValue = [option];
|
||||||
|
@ -351,7 +377,7 @@ export const AttributeGeneralSettings = () => {
|
||||||
onChange([]);
|
onChange([]);
|
||||||
}}
|
}}
|
||||||
isOpen={selectRequiredForOpen}
|
isOpen={selectRequiredForOpen}
|
||||||
isDisabled={requiredWhenSelection === "Always"}
|
isDisabled={requiredScopes.length === clientScopes?.length}
|
||||||
aria-labelledby={"scope"}
|
aria-labelledby={"scope"}
|
||||||
>
|
>
|
||||||
{clientScopes?.map((option) => (
|
{clientScopes?.map((option) => (
|
||||||
|
|
|
@ -9,57 +9,53 @@ import "../../realm-settings-section.css";
|
||||||
const Permissions = ({ name }: { name: string }) => {
|
const Permissions = ({ name }: { name: string }) => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
<GridItem lg={4} sm={6}>
|
<Controller
|
||||||
<Controller
|
name={`permissions.${name}`}
|
||||||
name={`permissions.${name}`}
|
control={control}
|
||||||
control={control}
|
defaultValue={[]}
|
||||||
defaultValue={[]}
|
render={({ onChange, value }) => (
|
||||||
render={({ onChange, value }) => (
|
<>
|
||||||
<Checkbox
|
<GridItem lg={4} sm={6}>
|
||||||
id={`user-${name}`}
|
<Checkbox
|
||||||
label={t("user")}
|
id={`user-${name}`}
|
||||||
value="user"
|
label={t("user")}
|
||||||
data-testid={`user-${name}`}
|
value="user"
|
||||||
isChecked={value.includes("user")}
|
data-testid={`user-${name}`}
|
||||||
onChange={() => {
|
isChecked={value.includes("user")}
|
||||||
const option = "user";
|
onChange={() => {
|
||||||
const changedValue = value.includes(option)
|
const option = "user";
|
||||||
? value.filter((item: string) => item !== option)
|
const changedValue = value.includes(option)
|
||||||
: [option];
|
? value.filter((item: string) => item !== option)
|
||||||
|
: [option];
|
||||||
|
|
||||||
onChange(changedValue);
|
onChange(changedValue);
|
||||||
}}
|
}}
|
||||||
isDisabled={value.includes("admin")}
|
isDisabled={value.includes("admin")}
|
||||||
/>
|
/>
|
||||||
)}
|
</GridItem>
|
||||||
/>
|
<GridItem lg={8} sm={6}>
|
||||||
</GridItem>
|
<Checkbox
|
||||||
<GridItem lg={8} sm={6}>
|
id={`admin-${name}`}
|
||||||
<Controller
|
label={t("admin")}
|
||||||
name={`permissions.${name}`}
|
value="admin"
|
||||||
control={control}
|
data-testid={`admin-${name}`}
|
||||||
defaultValue={[]}
|
isChecked={value.includes("admin")}
|
||||||
render={({ onChange, value }) => (
|
onChange={() => {
|
||||||
<Checkbox
|
const option = "admin";
|
||||||
id={`admin-${name}`}
|
const changedValue = value.includes(option)
|
||||||
label={t("admin")}
|
? value.filter((item: string) => item !== option)
|
||||||
value="admin"
|
: ["user", option];
|
||||||
data-testid={`admin-${name}`}
|
|
||||||
isChecked={value.includes("admin")}
|
|
||||||
onChange={() => {
|
|
||||||
const option = "admin";
|
|
||||||
const changedValue = value.includes(option)
|
|
||||||
? value.filter((item: string) => item !== option)
|
|
||||||
: ["user", option];
|
|
||||||
|
|
||||||
onChange(changedValue);
|
onChange(changedValue);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
</GridItem>
|
||||||
/>
|
</>
|
||||||
</GridItem>
|
)}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -22,26 +21,29 @@ import {
|
||||||
Tr,
|
Tr,
|
||||||
} from "@patternfly/react-table";
|
} from "@patternfly/react-table";
|
||||||
import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog";
|
||||||
import type { Validator } from "./Validators";
|
|
||||||
import useToggle from "../../../utils/useToggle";
|
import useToggle from "../../../utils/useToggle";
|
||||||
import { useFormContext, useWatch } from "react-hook-form";
|
import { useFormContext, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
|
import type { KeyValueType } from "../../../components/attribute-form/attribute-convert";
|
||||||
|
|
||||||
import "../../realm-settings-section.css";
|
import "../../realm-settings-section.css";
|
||||||
|
|
||||||
export const AttributeValidations = () => {
|
export const AttributeValidations = () => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const [addValidatorModalOpen, toggleModal] = useToggle();
|
const [addValidatorModalOpen, toggleModal] = useToggle();
|
||||||
const [validatorToDelete, setValidatorToDelete] = useState<{
|
const [validatorToDelete, setValidatorToDelete] =
|
||||||
name: string;
|
useState<{ name: string }>();
|
||||||
}>();
|
|
||||||
const { setValue, control, register } = useFormContext();
|
const { setValue, control, register } = useFormContext();
|
||||||
const validators = useWatch<Validator[]>({
|
|
||||||
|
const validators = useWatch<KeyValueType[]>({
|
||||||
name: "validations",
|
name: "validations",
|
||||||
control,
|
control,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => register("validations"), []);
|
useEffect(() => {
|
||||||
|
register("validations");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
titleKey: t("deleteValidatorConfirmTitle"),
|
titleKey: t("deleteValidatorConfirmTitle"),
|
||||||
|
@ -52,8 +54,9 @@ export const AttributeValidations = () => {
|
||||||
continueButtonVariant: ButtonVariant.danger,
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
const updatedValidators = validators.filter(
|
const updatedValidators = validators.filter(
|
||||||
(validator) => validator.name !== validatorToDelete?.name
|
(validator) => validator.key !== validatorToDelete?.name
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("validations", [...updatedValidators]);
|
setValue("validations", [...updatedValidators]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -63,7 +66,10 @@ export const AttributeValidations = () => {
|
||||||
{addValidatorModalOpen && (
|
{addValidatorModalOpen && (
|
||||||
<AddValidatorDialog
|
<AddValidatorDialog
|
||||||
onConfirm={(newValidator) => {
|
onConfirm={(newValidator) => {
|
||||||
setValue("validations", [...validators, newValidator]);
|
setValue("validations", [
|
||||||
|
...validators,
|
||||||
|
{ key: newValidator.name, value: newValidator.config },
|
||||||
|
]);
|
||||||
}}
|
}}
|
||||||
toggleDialog={toggleModal}
|
toggleDialog={toggleModal}
|
||||||
/>
|
/>
|
||||||
|
@ -94,33 +100,32 @@ export const AttributeValidations = () => {
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{validators.length ? (
|
{validators.map((validator) => (
|
||||||
validators.map((validator: Validator) => (
|
<Tr key={validator.key}>
|
||||||
<Tr key={validator.name}>
|
<Td dataLabel={t("validatorColNames.colName")}>
|
||||||
<Td dataLabel={t("validatorColNames.colName")}>
|
{validator.key}
|
||||||
{validator.name}
|
</Td>
|
||||||
</Td>
|
<Td dataLabel={t("validatorColNames.colConfig")}>
|
||||||
<Td dataLabel={t("validatorColNames.colConfig")}>
|
{JSON.stringify(validator.value)}
|
||||||
{JSON.stringify(validator.config)}
|
</Td>
|
||||||
</Td>
|
<Td>
|
||||||
<Td>
|
<Button
|
||||||
<Button
|
key="validator"
|
||||||
key="validator"
|
variant="link"
|
||||||
variant="link"
|
data-testid="deleteValidator"
|
||||||
data-testid="deleteValidator"
|
onClick={() => {
|
||||||
onClick={() => {
|
toggleDeleteDialog();
|
||||||
toggleDeleteDialog();
|
setValidatorToDelete({
|
||||||
setValidatorToDelete({
|
name: validator.key,
|
||||||
name: validator.name,
|
});
|
||||||
});
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{t("common:delete")}
|
||||||
{t("common:delete")}
|
</Button>
|
||||||
</Button>
|
</Td>
|
||||||
</Td>
|
</Tr>
|
||||||
</Tr>
|
))}
|
||||||
))
|
{validators.length === 0 && (
|
||||||
) : (
|
|
||||||
<Text className="kc-emptyValidators" component={TextVariants.h6}>
|
<Text className="kc-emptyValidators" component={TextVariants.h6}>
|
||||||
{t("realm-settings:emptyValidators")}
|
{t("realm-settings:emptyValidators")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
Loading…
Reference in a new issue