Use react-form-hook v7 for user IDP modal (#3771)

This commit is contained in:
Jon Koops 2022-11-15 16:21:43 +01:00 committed by GitHub
parent 27d8b35d70
commit d0b224cae7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 116 deletions

View file

@ -11,7 +11,6 @@ export default class IdentityProviderLinksTab {
private linkAccountModalIdentityProviderInput = "idpNameInput";
private linkAccountModalUserIdInput = "userIdInput";
private linkAccountModalUsernameInput = "usernameInput";
private linkAccountModalLinkBtn = "Link";
public clickLinkAccount(idpName: string) {
cy.get(this.availableProvidersSection + " tr")
@ -47,7 +46,7 @@ export default class IdentityProviderLinksTab {
}
public clickLinkAccountModalLinkBtn() {
cy.findByTestId(this.linkAccountModalLinkBtn).click();
modalUtils.confirmModal();
cy.intercept("/admin/realms/master").as("load");
cy.wait(["@load"]);
return this;

View file

@ -1,3 +1,4 @@
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
import {
AlertVariant,
Button,
@ -8,28 +9,26 @@ import {
ModalVariant,
ValidatedOptions,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useForm } from "react-hook-form";
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { capitalize } from "lodash-es";
import { useParams } from "react-router-dom";
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
import type { UserParams } from "./routes/User";
import { useForm } from "react-hook-form-v7";
import { useTranslation } from "react-i18next";
import { useAlerts } from "../components/alert/Alerts";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import { useAdminClient } from "../context/auth/AdminClient";
type UserIdpModalProps = {
federatedId?: string;
handleModalToggle: () => void;
refresh: (group?: GroupRepresentation) => void;
userId: string;
federatedId: string;
onClose: () => void;
onRefresh: () => void;
};
export const UserIdpModal = ({
userId,
federatedId,
handleModalToggle,
refresh,
onClose,
onRefresh,
}: UserIdpModalProps) => {
const { t } = useTranslation("users");
const { adminClient } = useAdminClient();
@ -38,22 +37,22 @@ export const UserIdpModal = ({
register,
handleSubmit,
formState: { isValid, errors },
} = useForm({
} = useForm<FederatedIdentityRepresentation>({
mode: "onChange",
});
const { id } = useParams<UserParams>();
const submitForm = async (fedIdentity: FederatedIdentityRepresentation) => {
const onSubmit = async (
federatedIdentity: FederatedIdentityRepresentation
) => {
try {
await adminClient.users.addToFederatedIdentity({
id: id!,
federatedIdentityId: federatedId!,
federatedIdentity: fedIdentity,
id: userId,
federatedIdentityId: federatedId,
federatedIdentity,
});
addAlert(t("users:idpLinkSuccess"), AlertVariant.success);
handleModalToggle();
refresh();
onClose();
onRefresh();
} catch (error) {
addError("users:couldNotLinkIdP", error);
}
@ -65,12 +64,11 @@ export const UserIdpModal = ({
title={t("users:linkAccountTitle", {
provider: capitalize(federatedId),
})}
isOpen={true}
onClose={handleModalToggle}
onClose={onClose}
actions={[
<Button
data-testid={t("link")}
key="confirm"
data-testid="confirm"
variant="primary"
type="submit"
form="group-form"
@ -79,51 +77,31 @@ export const UserIdpModal = ({
{t("link")}
</Button>,
<Button
id="modal-cancel"
data-testid="cancel"
key="cancel"
data-testid="cancel"
variant={ButtonVariant.link}
onClick={() => {
handleModalToggle();
}}
onClick={onClose}
>
{t("common:cancel")}
</Button>,
]}
isOpen
>
<Form id="group-form" onSubmit={handleSubmit(submitForm)}>
<Form id="group-form" onSubmit={handleSubmit(onSubmit)}>
<FormGroup
name="idp-name-group"
label={t("users:identityProvider")}
fieldId="idp-name"
helperTextInvalid={t("common:required")}
validated={
errors.identityProvider
? ValidatedOptions.error
: ValidatedOptions.default
}
fieldId="identityProvider"
>
<KeycloakTextInput
id="identityProvider"
data-testid="idpNameInput"
aria-label="Identity provider name input"
ref={register({ required: true })}
autoFocus
isReadOnly
type="text"
id="link-idp-name"
name="identityProvider"
value={capitalize(federatedId)}
validated={
errors.identityProvider
? ValidatedOptions.error
: ValidatedOptions.default
}
isReadOnly
/>
</FormGroup>
<FormGroup
name="user-id-group"
label={t("users:userID")}
fieldId="user-id"
fieldId="userID"
helperText={t("users-help:userIdHelperText")}
helperTextInvalid={t("common:required")}
validated={
@ -132,40 +110,34 @@ export const UserIdpModal = ({
isRequired
>
<KeycloakTextInput
id="userID"
data-testid="userIdInput"
aria-label="user ID input"
ref={register({ required: true })}
autoFocus
type="text"
id="link-idp-user-id"
name="userId"
validated={
errors.userId ? ValidatedOptions.error : ValidatedOptions.default
}
autoFocus
{...register("userId", { required: true })}
/>
</FormGroup>
<FormGroup
name="username-group"
label={t("users:username")}
fieldId="username"
helperText={t("users-help:usernameHelperText")}
helperTextInvalid={t("common:required")}
validated={
errors.name ? ValidatedOptions.error : ValidatedOptions.default
errors.userName ? ValidatedOptions.error : ValidatedOptions.default
}
isRequired
>
<KeycloakTextInput
id="username"
data-testid="usernameInput"
aria-label="username input"
ref={register({ required: true })}
autoFocus
type="text"
id="link-idp-username"
name="userName"
validated={
errors.name ? ValidatedOptions.error : ValidatedOptions.default
errors.userName
? ValidatedOptions.error
: ValidatedOptions.default
}
{...register("userName", { required: true })}
/>
</FormGroup>
</Form>

View file

@ -1,5 +1,5 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import {
AlertVariant,
Button,
@ -9,40 +9,41 @@ import {
Text,
TextContent,
} from "@patternfly/react-core";
import { cellWidth } from "@patternfly/react-table";
import { capitalize } from "lodash-es";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom-v5-compat";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { cellWidth } from "@patternfly/react-table";
import { useParams } from "react-router-dom";
import { Link } from "react-router-dom-v5-compat";
import { useAdminClient } from "../context/auth/AdminClient";
import { emptyFormatter, upperCaseFormatter } from "../util";
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { capitalize } from "lodash-es";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { useAlerts } from "../components/alert/Alerts";
import { UserIdpModal } from "./UserIdPModal";
import { toIdentityProvider } from "../identity-providers/routes/IdentityProvider";
import { emptyFormatter, upperCaseFormatter } from "../util";
import { UserIdpModal } from "./UserIdPModal";
export const UserIdentityProviderLinks = () => {
type UserIdentityProviderLinksProps = {
userId: string;
};
export const UserIdentityProviderLinks = ({
userId,
}: UserIdentityProviderLinksProps) => {
const [key, setKey] = useState(0);
const [federatedId, setFederatedId] = useState("");
const [isLinkIdPModalOpen, setIsLinkIdPModalOpen] = useState(false);
const { adminClient } = useAdminClient();
const { id } = useParams<{ id: string }>();
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const { t } = useTranslation("users");
const refresh = () => setKey(new Date().getTime());
const handleModalToggle = () => {
setIsLinkIdPModalOpen(!isLinkIdPModalOpen);
};
type WithProviderId = FederatedIdentityRepresentation & {
providerId: string;
};
@ -53,7 +54,7 @@ export const UserIdentityProviderLinks = () => {
const allProviders = await adminClient.identityProviders.find();
const allFedIds = (await adminClient.users.listFederatedIdentities({
id,
id: userId,
})) as WithProviderId[];
for (const element of allFedIds) {
element.providerId = allProviders.find(
@ -94,7 +95,7 @@ export const UserIdentityProviderLinks = () => {
onConfirm: async () => {
try {
await adminClient.users.delFromFederatedIdentity({
id,
id: userId,
federatedIdentityId: federatedId,
});
addAlert(t("users:idpUnlinkSuccess"), AlertVariant.success);
@ -180,9 +181,10 @@ export const UserIdentityProviderLinks = () => {
<>
{isLinkIdPModalOpen && (
<UserIdpModal
userId={userId}
federatedId={federatedId}
handleModalToggle={handleModalToggle}
refresh={refresh}
onClose={() => setIsLinkIdPModalOpen(false)}
onRefresh={refresh}
/>
)}
<UnlinkConfirm />

View file

@ -1,4 +1,5 @@
import { useState } from "react";
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import {
AlertVariant,
ButtonVariant,
@ -7,31 +8,30 @@ import {
Tab,
TabTitleText,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { BruteForced, UserForm } from "./UserForm";
import { useAlerts } from "../components/alert/Alerts";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useNavigate } from "react-router-dom-v5-compat";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { UserGroups } from "./UserGroups";
import { UserConsents } from "./UserConsents";
import { useRealm } from "../context/realm-context/RealmContext";
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { toUser } from "./routes/User";
import { toUsers } from "./routes/Users";
import { UserRoleMapping } from "./UserRoleMapping";
import { UserAttributes } from "./UserAttributes";
import { UserCredentials } from "./UserCredentials";
import { UserSessions } from "./UserSessions";
import { useAccess } from "../context/access/Access";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAccess } from "../context/access/Access";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { toUser, UserParams } from "./routes/User";
import { toUsers } from "./routes/Users";
import { UserAttributes } from "./UserAttributes";
import { UserConsents } from "./UserConsents";
import { UserCredentials } from "./UserCredentials";
import { BruteForced, UserForm } from "./UserForm";
import { UserGroups } from "./UserGroups";
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
import { UserRoleMapping } from "./UserRoleMapping";
import { UserSessions } from "./UserSessions";
const UsersTabs = () => {
const { t } = useTranslation("users");
@ -42,7 +42,7 @@ const UsersTabs = () => {
const { adminClient } = useAdminClient();
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
const { id } = useParams<{ id: string }>();
const { id } = useParams<UserParams>();
const [user, setUser] = useState<UserRepresentation>();
const [bruteForced, setBruteForced] = useState<BruteForced>();
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
@ -247,7 +247,7 @@ const UsersTabs = () => {
<TabTitleText>{t("identityProviderLinks")}</TabTitleText>
}
>
<UserIdentityProviderLinks />
<UserIdentityProviderLinks userId={id} />
</Tab>
)}
<Tab