Centralize user form to user representation conversion (#22851)

This commit is contained in:
Jon Koops 2023-09-04 06:55:34 +02:00 committed by GitHub
parent 28c2cba3b9
commit bb4495adb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 49 deletions

View file

@ -1,5 +1,4 @@
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { AlertVariant, PageSection } from "@patternfly/react-core";
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
@ -16,6 +15,7 @@ import {
isUserProfileError,
userProfileErrorToString,
} from "./UserProfileFields";
import { UserFormFields, toUserRepresentation } from "./form-state";
import { toUser } from "./routes/User";
import "./user-section.css";
@ -25,14 +25,13 @@ export default function CreateUser() {
const { addAlert, addError } = useAlerts();
const navigate = useNavigate();
const { realm } = useRealm();
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
const userForm = useForm<UserFormFields>({ mode: "onChange" });
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
const save = async (formUser: UserRepresentation) => {
const save = async (data: UserFormFields) => {
try {
const createdUser = await adminClient.users.create({
...formUser,
username: formUser.username?.trim(),
...toUserRepresentation(data),
groups: addedGroups.map((group) => group.path!),
enabled: true,
});

View file

@ -15,10 +15,6 @@ import { useNavigate } from "react-router-dom";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import {
KeyValueType,
keyValueToArray,
} from "../components/key-value-form/key-value-convert";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import {
RoutableTabs,
@ -44,6 +40,11 @@ import {
} from "./UserProfileFields";
import { UserRoleMapping } from "./UserRoleMapping";
import { UserSessions } from "./UserSessions";
import {
UserFormFields,
toUserFormFields,
toUserRepresentation,
} from "./form-state";
import { UserParams, UserTab, toUser } from "./routes/User";
import { toUsers } from "./routes/Users";
@ -97,17 +98,15 @@ type EditUserFormProps = {
refresh: () => void;
};
type FormFields = Omit<UserRepresentation, "userProfileMetadata">;
const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
const { t } = useTranslation("users");
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const navigate = useNavigate();
const { hasAccess } = useAccess();
const userForm = useForm<FormFields>({
const userForm = useForm<UserFormFields>({
mode: "onChange",
defaultValues: user,
defaultValues: toUserFormFields(user),
});
const [realmRepresentation, setRealmRepresentattion] =
@ -148,22 +147,13 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
const sessionsTab = useTab("sessions");
// Ensure the form remains up-to-date when the user is updated.
useUpdateEffect(() => userForm.reset(user), [user]);
useUpdateEffect(() => userForm.reset(toUserFormFields(user)), [user]);
const save = async (formUser: UserRepresentation) => {
const attributes =
"key" in (formUser.attributes?.[0] || [])
? keyValueToArray(formUser.attributes as KeyValueType[])
: [];
const save = async (data: UserFormFields) => {
try {
await adminClient.users.update(
{ id: user.id! },
{
...user,
...formUser,
username: formUser.username?.trim(),
attributes,
},
toUserRepresentation(data),
);
addAlert(t("userSaved"), AlertVariant.success);
refresh();
@ -237,7 +227,9 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
{t("common:delete")}
</DropdownItem>,
]}
onToggle={(value) => save({ ...user, enabled: value })}
onToggle={(value) =>
save({ ...toUserFormFields(user), enabled: value })
}
isEnabled={user.enabled}
/>

View file

@ -1,31 +1,17 @@
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { PageSection, PageSectionVariants } from "@patternfly/react-core";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useFormContext } from "react-hook-form";
import {
AttributeForm,
AttributesForm,
} from "../components/key-value-form/AttributeForm";
import { arrayToKeyValue } from "../components/key-value-form/key-value-convert";
import { useUserProfile } from "../realm-settings/user-profile/UserProfileContext";
import { AttributesForm } from "../components/key-value-form/AttributeForm";
import { UserFormFields, toUserFormFields } from "./form-state";
type UserAttributesProps = {
user: UserRepresentation;
save: (user: UserRepresentation) => void;
save: (user: UserFormFields) => void;
};
export const UserAttributes = ({ user, save }: UserAttributesProps) => {
const form = useForm<AttributeForm>({ mode: "onChange" });
const { config } = useUserProfile();
const convertAttributes = () => {
return arrayToKeyValue<UserRepresentation>(user.attributes!);
};
useEffect(() => {
form.setValue("attributes", convertAttributes());
}, [user, config]);
const form = useFormContext<UserFormFields>();
return (
<PageSection variant={PageSectionVariants.light}>
@ -35,7 +21,8 @@ export const UserAttributes = ({ user, save }: UserAttributesProps) => {
fineGrainedAccess={user.access?.manage}
reset={() =>
form.reset({
attributes: convertAttributes(),
...form.getValues(),
attributes: toUserFormFields(user).attributes,
})
}
/>

View file

@ -29,6 +29,7 @@ import useFormatDate from "../utils/useFormatDate";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { FederatedUserLink } from "./FederatedUserLink";
import { UserProfileFields } from "./UserProfileFields";
import { UserFormFields } from "./form-state";
import { RequiredActionMultiSelect } from "./user-credentials/RequiredActionMultiSelect";
export type BruteForced = {
@ -40,13 +41,13 @@ export type UserFormProps = {
user?: UserRepresentation;
bruteForce?: BruteForced;
realm?: RealmRepresentation;
save: (user: UserRepresentation) => void;
save: (user: UserFormFields) => void;
onGroupsUpdate?: (groups: GroupRepresentation[]) => void;
};
const EmailVerified = () => {
const { t } = useTranslation("users");
const { control } = useFormContext();
const { control } = useFormContext<UserFormFields>();
return (
<FormGroup
label={t("emailVerified")}
@ -106,7 +107,7 @@ export const UserForm = ({
control,
reset,
formState: { errors },
} = useFormContext();
} = useFormContext<UserFormFields>();
const watchUsernameInput = watch("username");
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
[],

View file

@ -0,0 +1,23 @@
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import {
KeyValueType,
arrayToKeyValue,
keyValueToArray,
} from "../components/key-value-form/key-value-convert";
export type UserFormFields = Omit<UserRepresentation, "userProfileMetadata"> & {
attributes?: KeyValueType[];
};
export function toUserFormFields(data: UserRepresentation): UserFormFields {
const attributes = arrayToKeyValue(data.attributes);
return { ...data, attributes };
}
export function toUserRepresentation(data: UserFormFields): UserRepresentation {
const username = data.username?.trim();
const attributes = keyValueToArray(data.attributes);
return { ...data, username, attributes };
}