From df6526311a91624b4699810ff7d9e49207d11af9 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Mon, 5 Sep 2022 23:31:39 +0200 Subject: [PATCH] Changed to use form to store executors (#3242) --- .../src/realm-settings/ClientProfileForm.tsx | 292 +++++++----------- .../src/realm-settings/ExecutorForm.tsx | 11 +- 2 files changed, 119 insertions(+), 184 deletions(-) diff --git a/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx b/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx index b557c65318..0d6257be90 100644 --- a/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx +++ b/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx @@ -1,4 +1,7 @@ -import { Fragment, useEffect, useMemo, useState } from "react"; +import { Fragment, useMemo, useState } from "react"; +import { useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useForm, useFieldArray } from "react-hook-form"; import { ActionGroup, AlertVariant, @@ -19,26 +22,28 @@ import { TextVariants, ValidatedOptions, } from "@patternfly/react-core"; -import { useTranslation } from "react-i18next"; -import { useForm } from "react-hook-form"; + +import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation"; +import type ClientProfilesRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfilesRepresentation"; +import type ClientPolicyExecutorRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyExecutorRepresentation"; import { FormAccess } from "../components/form-access/FormAccess"; import { ViewHeader } from "../components/view-header/ViewHeader"; -import { useParams } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom-v5-compat"; import { useAlerts } from "../components/alert/Alerts"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; -import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation"; import { HelpItem } from "../components/help-enabler/HelpItem"; import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextArea } from "../components/keycloak-text-area/KeycloakTextArea"; import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons"; -import "./realm-settings-section.css"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { toAddExecutor } from "./routes/AddExecutor"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { ClientProfileParams, toClientProfile } from "./routes/ClientProfile"; import { toExecutor } from "./routes/Executor"; import { toClientPolicies } from "./routes/ClientPolicies"; +import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; + +import "./realm-settings-section.css"; type ClientProfileForm = Required; @@ -54,19 +59,25 @@ export default function ClientProfileForm() { const { handleSubmit, setValue, + getValues, register, formState: { isDirty, errors }, + control, } = useForm({ defaultValues, mode: "onChange", }); + const { fields: profileExecutors, remove } = + useFieldArray({ + name: "executors", + control, + }); + const { addAlert, addError } = useAlerts(); const { adminClient } = useAdminClient(); - const [globalProfiles, setGlobalProfiles] = useState< - ClientProfileRepresentation[] - >([]); - const [profiles, setProfiles] = useState([]); + const [profiles, setProfiles] = useState(); + const [isGlobalProfile, setIsGlobalProfile] = useState(false); const { realm, profileName } = useParams(); const serverInfo = useServerInfo(); const executorTypes = useMemo( @@ -82,25 +93,38 @@ export default function ClientProfileForm() { }>(); const editMode = profileName ? true : false; const [key, setKey] = useState(0); - const reload = () => setKey(new Date().getTime()); + const reload = () => setKey(key + 1); useFetch( () => adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }), (profiles) => { - setGlobalProfiles(profiles.globalProfiles ?? []); - setProfiles(profiles.profiles ?? []); + setProfiles({ + globalProfiles: profiles.globalProfiles, + profiles: profiles.profiles?.filter((p) => p.name !== profileName), + }); + const globalProfile = profiles.globalProfiles?.find( + (p) => p.name === profileName + ); + const profile = profiles.profiles?.find((p) => p.name === profileName); + setIsGlobalProfile(globalProfile !== undefined); + setValue("name", globalProfile?.name ?? profile?.name); + setValue( + "description", + globalProfile?.description ?? profile?.description + ); + setValue("executors", globalProfile?.executors ?? profile?.executors); }, [key] ); const save = async (form: ClientProfileForm) => { - const updatedProfiles = editMode ? patchProfiles(form) : addProfile(form); + const updatedProfiles = form; try { await adminClient.clientPolicies.createProfiles({ - profiles: updatedProfiles, - globalProfiles: globalProfiles, + ...profiles, + profiles: [...(profiles?.profiles || []), updatedProfiles], }); addAlert( @@ -121,25 +145,6 @@ export default function ClientProfileForm() { } }; - const patchProfiles = (data: ClientProfileRepresentation) => - profiles.map((profile) => { - if (profile.name !== profileName) { - return profile; - } - - return { - ...profile, - name: data.name, - description: data.description, - }; - }); - - const addProfile = (data: ClientProfileRepresentation) => - profiles.concat({ - ...data, - executors: [], - }); - const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: executorToDelete?.name! ? t("deleteExecutorProfileConfirmTitle") @@ -156,11 +161,11 @@ export default function ClientProfileForm() { onConfirm: async () => { if (executorToDelete?.name!) { - profileExecutors.splice(executorToDelete.idx!, 1); + remove(executorToDelete.idx); try { await adminClient.clientPolicies.createProfiles({ - profiles: profiles, - globalProfiles, + ...profiles, + profiles: [...(profiles!.profiles || []), getValues()], }); addAlert(t("deleteExecutorSuccess"), AlertVariant.success); navigate(toClientProfile({ realm, profileName })); @@ -168,15 +173,8 @@ export default function ClientProfileForm() { addError(t("deleteExecutorError"), error); } } else { - const updatedProfiles = profiles.filter( - (profile) => profile.name !== profileName - ); - try { - await adminClient.clientPolicies.createProfiles({ - profiles: updatedProfiles, - globalProfiles, - }); + await adminClient.clientPolicies.createProfiles(profiles); addAlert(t("deleteClientSuccess"), AlertVariant.success); navigate(toClientPolicies({ realm, tab: "profiles" })); } catch (error) { @@ -186,17 +184,9 @@ export default function ClientProfileForm() { }, }); - const profile = profiles.find((profile) => profile.name === profileName); - const profileExecutors = profile?.executors || []; - const globalProfile = globalProfiles.find( - (globalProfile) => globalProfile.name === profileName - ); - const globalProfileExecutors = globalProfile?.executors || []; - - useEffect(() => { - setValue("name", globalProfile?.name ?? profile?.name); - setValue("description", globalProfile?.description ?? profile?.description); - }, [profiles]); + if (!profiles) { + return ; + } return ( <> @@ -206,12 +196,12 @@ export default function ClientProfileForm() { badges={[ { id: "global-client-profile-badge", - text: globalProfile ? t("global") : "", + text: isGlobalProfile ? t("global") : "", }, ]} divider dropdownItems={ - editMode && !globalProfile + editMode && !isGlobalProfile ? [ @@ -255,11 +245,11 @@ export default function ClientProfileForm() { id="description" aria-label={t("description")} data-testid="client-profile-description" - isReadOnly={!!globalProfile} + isReadOnly={isGlobalProfile} /> - {!globalProfile && ( + {!isGlobalProfile && ( )} - {editMode && !globalProfile && ( + {editMode && !isGlobalProfile && ( - ) : ( - - {executor.executor} - - )} - {executorTypes - ?.filter( - (type) => type.id === executor.executor - ) - .map((type) => ( - - - - - ))} - , - ]} - /> - - - ))} - - )} - {globalProfileExecutors.length > 0 && ( <> - {globalProfileExecutors.map((executor) => ( + {profileExecutors.map((executor, idx) => ( - + - {Object.keys(executor.configuration!).length !== - 0 ? ( + {executor.configuration ? ( + {isGlobalProfile && ( + + )} + + )} + {profileExecutors.length === 0 && ( + <> + + + {t("realm-settings:emptyExecutors")} + )} - {profileExecutors.length === 0 && - globalProfileExecutors.length === 0 && ( - <> - - - {t("realm-settings:emptyExecutors")} - - - )} )} diff --git a/apps/admin-ui/src/realm-settings/ExecutorForm.tsx b/apps/admin-ui/src/realm-settings/ExecutorForm.tsx index 300eb1abd3..3f8aa02d43 100644 --- a/apps/admin-ui/src/realm-settings/ExecutorForm.tsx +++ b/apps/admin-ui/src/realm-settings/ExecutorForm.tsx @@ -27,7 +27,7 @@ import { DynamicComponents } from "../components/dynamic/DynamicComponents"; import type { ExecutorParams } from "./routes/Executor"; type ExecutorForm = { - config: object; + config?: object; executor: string; }; @@ -89,16 +89,15 @@ export default function ExecutorForm() { return profile; } - const profileExecutor = profile.executors!.find( - (executor) => executor.executor === executorName - ); - const executors = (profile.executors ?? []).concat({ executor: formValues.executor, - configuration: formValues.config, + configuration: formValues.config || {}, }); if (editMode) { + const profileExecutor = profile.executors!.find( + (executor) => executor.executor === executorName + ); profileExecutor!.configuration = { ...profileExecutor!.configuration, ...formValues.config,