From fd1466c0d61b9c92fb58943b919e1b6df972a617 Mon Sep 17 00:00:00 2001 From: jenny-s51 Date: Thu, 10 Jun 2021 16:00:14 -0400 Subject: [PATCH 01/11] test email working, modal wip email connection done remove comment fix cypress test revert test file fdisable email prompting when user email entered prettier update cypress tests fix lint try fix cypress test try longer wait --- cypress/integration/realm_roles_test.spec.ts | 2 + .../integration/realm_settings_test.spec.ts | 3 + .../realm_settings/RealmSettingsPage.ts | 3 + .../confirm-dialog/ConfirmDialog.tsx | 3 + src/context/whoami/WhoAmI.tsx | 6 ++ src/realm-settings/AddUserEmailModal.tsx | 96 +++++++++++++++++++ src/realm-settings/EmailTab.tsx | 71 +++++++++++++- src/realm-settings/LocalizationTab.tsx | 2 +- src/realm-settings/RealmSettingsSection.css | 13 ++- src/realm-settings/RealmSettingsSection.tsx | 30 +++++- src/realm-settings/messages.json | 5 + 11 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 src/realm-settings/AddUserEmailModal.tsx diff --git a/cypress/integration/realm_roles_test.spec.ts b/cypress/integration/realm_roles_test.spec.ts index d93f14304b..115915aad1 100644 --- a/cypress/integration/realm_roles_test.spec.ts +++ b/cypress/integration/realm_roles_test.spec.ts @@ -56,6 +56,8 @@ describe("Realm roles test", function () { // Delete listingPage.deleteItem(itemId); + cy.wait(500); + modalUtils.checkModalTitle("Delete role?").confirmModal(); masthead.checkNotificationMessage("The role has been deleted"); diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts index a4eecb5656..085881648d 100644 --- a/cypress/integration/realm_settings_test.spec.ts +++ b/cypress/integration/realm_settings_test.spec.ts @@ -69,6 +69,9 @@ describe("Realm settings", () => { realmSettingsPage.toggleCheck(realmSettingsPage.enableStartTlsCheck); realmSettingsPage.save(realmSettingsPage.emailSaveBtn); + cy.getId(realmSettingsPage.testConnectionButton).click(); + cy.wait(500) + masthead.checkNotificationMessage("Error! Failed to send email."); }); it("Go to themes tab", () => { diff --git a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts index f30ac56054..7d6a86f8a0 100644 --- a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts +++ b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts @@ -35,6 +35,9 @@ export default class RealmSettingsPage { filterSelectMenu = ".kc-filter-type-select"; passiveKeysOption = "passive-keys-option"; disabledKeysOption = "disabled-keys-option"; + testConnectionButton = "test-connection-button"; + modalTestConnectionButton = "modal-test-connection-button"; + emailAddressInput = "email-address-input"; selectLoginThemeType(themeType: string) { const themesUrl = "/auth/admin/realms/master/themes"; diff --git a/src/components/confirm-dialog/ConfirmDialog.tsx b/src/components/confirm-dialog/ConfirmDialog.tsx index ea0ea80318..54ef203737 100644 --- a/src/components/confirm-dialog/ConfirmDialog.tsx +++ b/src/components/confirm-dialog/ConfirmDialog.tsx @@ -36,6 +36,7 @@ export type ConfirmDialogProps = { titleKey: string; messageKey?: string; noCancelButton?: boolean; + confirmButtonDisabled?: boolean; cancelButtonLabel?: string; continueButtonLabel?: string; continueButtonVariant?: ButtonVariant; @@ -58,6 +59,7 @@ export const ConfirmDialogModal = ({ open = true, variant = ModalVariant.small, toggleDialog, + confirmButtonDisabled, }: ConfirmDialogModalProps) => { const { t } = useTranslation(); return ( @@ -71,6 +73,7 @@ export const ConfirmDialogModal = ({ id="modal-confirm" data-testid="modalConfirm" key="confirm" + isDisabled={confirmButtonDisabled} variant={continueButtonVariant || ButtonVariant.primary} onClick={() => { onConfirm(); diff --git a/src/context/whoami/WhoAmI.tsx b/src/context/whoami/WhoAmI.tsx index 3376635005..7d7aa8d423 100644 --- a/src/context/whoami/WhoAmI.tsx +++ b/src/context/whoami/WhoAmI.tsx @@ -23,6 +23,12 @@ export class WhoAmI { return this.me.displayName; } + public getUserId(): string { + if (this.me === undefined) return ""; + + return this.me.userId; + } + /** * Return the realm I am signed in to. */ diff --git a/src/realm-settings/AddUserEmailModal.tsx b/src/realm-settings/AddUserEmailModal.tsx new file mode 100644 index 0000000000..0b87a7b190 --- /dev/null +++ b/src/realm-settings/AddUserEmailModal.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { + Button, + ButtonVariant, + Form, + FormGroup, + Modal, + ModalVariant, + TextContent, + TextInput, + ValidatedOptions, +} from "@patternfly/react-core"; +import { useTranslation } from "react-i18next"; +import { useForm, UseFormMethods } from "react-hook-form"; + +import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation"; +import { emailRegexPattern } from "../util"; + +type AddUserEmailModalProps = { + id?: string; + form: UseFormMethods; + rename?: string; + handleModalToggle: () => void; + testConnection: () => void; + user: UserRepresentation; + save: (user?: UserRepresentation) => void; +}; + +export const AddUserEmailModal = ({ + handleModalToggle, + save, +}: AddUserEmailModalProps) => { + const { t } = useTranslation("groups"); + const { register, errors, handleSubmit, watch } = useForm(); + + const watchEmailInput = watch("email", ""); + + return ( + + {t("realm-settings:testConnection")} + , + , + ]} + > + + {t("realm-settings:provideEmail")} + +
+ + + +
+
+ ); +}; diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx index 995f3435da..5c0b75b0e8 100644 --- a/src/realm-settings/EmailTab.tsx +++ b/src/realm-settings/EmailTab.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Controller, useForm } from "react-hook-form"; import { @@ -20,23 +20,32 @@ import { emailRegexPattern } from "../util"; import { useAdminClient } from "../context/auth/AdminClient"; import { useAlerts } from "../components/alert/Alerts"; import { useRealm } from "../context/realm-context/RealmContext"; +import { getBaseUrl } from "../util"; import "./RealmSettingsSection.css"; +import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation"; +import { WhoAmIContext } from "../context/whoami/WhoAmI"; +import { AddUserEmailModal } from "./AddUserEmailModal"; type RealmSettingsEmailTabProps = { realm: RealmRepresentation; + user: UserRepresentation; }; export const RealmSettingsEmailTab = ({ realm: initialRealm, + user, }: RealmSettingsEmailTabProps) => { const { t } = useTranslation("realm-settings"); const adminClient = useAdminClient(); const { realm: realmName } = useRealm(); const { addAlert } = useAlerts(); + const { whoAmI } = useContext(WhoAmIContext); const [isAuthenticationEnabled, setAuthenticationEnabled] = useState("true"); const [realm, setRealm] = useState(initialRealm); + const [userEmailModalOpen, setUserEmailModalOpen] = useState(false); + const [currentUser, setCurrentUser] = useState(); const { register, control, @@ -46,10 +55,20 @@ export const RealmSettingsEmailTab = ({ reset: resetForm, } = useForm(); + const userForm = useForm({ mode: "onChange" }); + useEffect(() => { reset(); }, [realm]); + useEffect(() => { + setCurrentUser(user); + }, []); + + const handleModalToggle = () => { + setUserEmailModalOpen(!userEmailModalOpen); + }; + const save = async (form: RealmRepresentation) => { try { const savedRealm = { ...realm, ...form }; @@ -64,6 +83,14 @@ export const RealmSettingsEmailTab = ({ } }; + const saveAndTestEmail = async (user: UserRepresentation) => { + await adminClient.users.update({ id: whoAmI.getUserId() }, user); + const updated = await adminClient.users.findOne({ id: whoAmI.getUserId() }); + setCurrentUser(updated); + handleModalToggle(); + testConnection(); + }; + const reset = () => { if (realm) { resetForm(realm); @@ -71,8 +98,39 @@ export const RealmSettingsEmailTab = ({ } }; + const testConnection = async () => { + const response = await fetch( + `${getBaseUrl(adminClient)}admin/realms/${ + realm.realm + }/testSMTPConnection`, + { + method: "POST", + headers: { + Authorization: `bearer ${await adminClient.getAccessToken()}`, + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(realm.smtpServer! as BodyInit), + } + ); + response.ok + ? addAlert(t("testConnectionSuccess"), AlertVariant.success) + : addAlert(t("testConnectionError"), AlertVariant.danger); + }; + return ( <> + {userEmailModalOpen && ( + { + saveAndTestEmail(user!); + }} + form={userForm} + user={currentUser!} + /> + )} {t("common:save")} + diff --git a/src/realm-settings/LocalizationTab.tsx b/src/realm-settings/LocalizationTab.tsx index 15dd89a548..9ef39eb270 100644 --- a/src/realm-settings/LocalizationTab.tsx +++ b/src/realm-settings/LocalizationTab.tsx @@ -239,7 +239,7 @@ export const LocalizationTab = ({ - + {t("messageBundleDescription")} diff --git a/src/realm-settings/RealmSettingsSection.css b/src/realm-settings/RealmSettingsSection.css index 1119867da4..0a326c772a 100644 --- a/src/realm-settings/RealmSettingsSection.css +++ b/src/realm-settings/RealmSettingsSection.css @@ -1,7 +1,8 @@ .pf-c-card.pf-m-flat.kc-login-screen, .pf-c-card.pf-m-flat.kc-email-settings, .pf-c-card.pf-m-flat.kc-email-template, -.pf-c-card.pf-m-flat.kc-email-connection { +.pf-c-card.pf-m-flat.kc-email-connection, +.pf-c-card.pf-m-flat.kc-message-bundles { border: none; margin-top: 0px; margin-bottom: 0px; @@ -86,9 +87,17 @@ button.pf-c-button.pf-m-link.add-provider { padding-bottom: var(--pf-global--spacer--lg); } -div.tableBorder { +.kc-message-bundles > .pf-c-card__body.kc-form-panel__body > div.tableBorder { border-style: solid; border-width: 1px; border-color: var(--pf-global--BorderColor--100); max-width: 1024px; } + +.pf-c-form__group.kc-email-form-group { + display: inline-block !important; +} + +.pf-c-content.kc-provide-email-text { + padding-bottom: var(--pf-global--spacer--md); +} diff --git a/src/realm-settings/RealmSettingsSection.tsx b/src/realm-settings/RealmSettingsSection.tsx index 85d58b7dc5..fceb9e2294 100644 --- a/src/realm-settings/RealmSettingsSection.tsx +++ b/src/realm-settings/RealmSettingsSection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useContext } from "react"; import { useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Controller, FormProvider, useForm } from "react-hook-form"; @@ -32,6 +32,8 @@ import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepre import { KeysProviderTab } from "./KeysProvidersTab"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { LocalizationTab } from "./LocalizationTab"; +import { WhoAmIContext } from "../context/whoami/WhoAmI"; +import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation"; type RealmSettingsHeaderProps = { onChange: (value: boolean) => void; @@ -136,6 +138,8 @@ export const RealmSettingsSection = () => { const [realmComponents, setRealmComponents] = useState< ComponentRepresentation[] >(); + const [currentUser, setCurrentUser] = useState(); + const { whoAmI } = useContext(WhoAmIContext); const kpComponentTypes = useServerInfo().componentTypes![ "org.keycloak.keys.KeyProvider" @@ -150,6 +154,26 @@ export const RealmSettingsSection = () => { [] ); + useFetch( + () => adminClient.users.findOne({ id: whoAmI.getUserId()! }), + + (user) => { + setCurrentUser(user); + }, + [] + ); + + useEffect(() => { + const update = async () => { + const realmComponents = await adminClient.components.find({ + type: "org.keycloak.keys.KeyProvider", + realm: realmName, + }); + setRealmComponents(realmComponents); + }; + setTimeout(update, 100); + }, [key]); + useFetch( async () => { const realm = await adminClient.realms.findOne({ realm: realmName }); @@ -233,7 +257,9 @@ export const RealmSettingsSection = () => { title={{t("email")}} data-testid="rs-email-tab" > - {realm && } + {realm && ( + + )} Date: Mon, 21 Jun 2021 10:46:05 -0400 Subject: [PATCH 02/11] save and test connection --- src/realm-settings/EmailTab.tsx | 39 +++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx index 5c0b75b0e8..120d04ea3f 100644 --- a/src/realm-settings/EmailTab.tsx +++ b/src/realm-settings/EmailTab.tsx @@ -53,6 +53,7 @@ export const RealmSettingsEmailTab = ({ errors, setValue, reset: resetForm, + getValues, } = useForm(); const userForm = useForm({ mode: "onChange" }); @@ -83,12 +84,25 @@ export const RealmSettingsEmailTab = ({ } }; - const saveAndTestEmail = async (user: UserRepresentation) => { - await adminClient.users.update({ id: whoAmI.getUserId() }, user); - const updated = await adminClient.users.findOne({ id: whoAmI.getUserId() }); - setCurrentUser(updated); - handleModalToggle(); - testConnection(); + const saveAndTestEmail = async (email?: UserRepresentation) => { + if (email) { + await adminClient.users.update({ id: whoAmI.getUserId() }, email); + const updated = await adminClient.users.findOne({ + id: whoAmI.getUserId(), + }); + setCurrentUser(updated); + + await save(getValues()); + testConnection(); + } else { + const user = await adminClient.users.findOne({ id: whoAmI.getUserId() }); + if (!user.email) { + handleModalToggle(); + } else { + await save(getValues()); + testConnection(); + } + } }; const reset = () => { @@ -110,7 +124,7 @@ export const RealmSettingsEmailTab = ({ Accept: "application/json", "Content-Type": "application/json", }, - body: JSON.stringify(realm.smtpServer! as BodyInit), + body: JSON.stringify(getValues()["smtpServer"] as BodyInit), } ); response.ok @@ -124,8 +138,9 @@ export const RealmSettingsEmailTab = ({ { - saveAndTestEmail(user!); + save={(email) => { + saveAndTestEmail(email!); + handleModalToggle(); }} form={userForm} user={currentUser!} @@ -385,11 +400,7 @@ export const RealmSettingsEmailTab = ({ From e565b3a3f89d6e71eeaaa3ebce71f401d72348c7 Mon Sep 17 00:00:00 2001 From: jenny-s51 Date: Tue, 22 Jun 2021 10:18:38 -0400 Subject: [PATCH 07/11] lint --- src/realm-settings/EmailTab.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx index 6658078768..7e54aa535e 100644 --- a/src/realm-settings/EmailTab.tsx +++ b/src/realm-settings/EmailTab.tsx @@ -6,7 +6,6 @@ import { AlertVariant, Button, Checkbox, - formatBreakpointMods, FormGroup, PageSection, Switch, From 0a7387be6c3782b8cf160729f2b17f1665555335 Mon Sep 17 00:00:00 2001 From: jenny-s51 Date: Tue, 22 Jun 2021 11:19:13 -0400 Subject: [PATCH 08/11] afix username/password fields --- src/realm-settings/EmailTab.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx index 7e54aa535e..0bc5f2485c 100644 --- a/src/realm-settings/EmailTab.tsx +++ b/src/realm-settings/EmailTab.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Controller, useForm } from "react-hook-form"; +import { Controller, useForm, useWatch } from "react-hook-form"; import { ActionGroup, AlertVariant, @@ -42,7 +42,6 @@ export const RealmSettingsEmailTab = ({ const { addAlert } = useAlerts(); const { whoAmI } = useContext(WhoAmIContext); - const [isAuthenticationEnabled, setAuthenticationEnabled] = useState("true"); const [realm, setRealm] = useState(initialRealm); const [userEmailModalOpen, setUserEmailModalOpen] = useState(false); const [currentUser, setCurrentUser] = useState(); @@ -61,6 +60,12 @@ export const RealmSettingsEmailTab = ({ const watchFromValue = watch("smtpServer.from", ""); const watchHostValue = watch("smtpServer.host", ""); + const authenticationEnabled = useWatch({ + control, + name: "smtpServer.authentication", + defaultValue: realm?.smtpServer!.authentication, + }); + useEffect(() => { reset(); }, [realm]); @@ -329,7 +334,7 @@ export const RealmSettingsEmailTab = ({ ( { onChange("" + value); - setAuthenticationEnabled(String(value)); }} /> )} /> - {isAuthenticationEnabled === "true" && ( + {authenticationEnabled === "true" && ( <> Date: Tue, 22 Jun 2021 13:16:35 -0400 Subject: [PATCH 09/11] fix test --- cypress/integration/realm_settings_test.spec.ts | 2 ++ .../manage/realm_settings/RealmSettingsPage.ts | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts index 94c72c63fd..e442a614ab 100644 --- a/cypress/integration/realm_settings_test.spec.ts +++ b/cypress/integration/realm_settings_test.spec.ts @@ -75,6 +75,8 @@ describe("Realm settings", () => { "example" + (Math.random() + 1).toString(36).substring(7) + "@example.com" ); + realmSettingsPage.fillHostField("localhost"); + cy.getId(realmSettingsPage.modalTestConnectionButton).click(); masthead.checkNotificationMessage("Error! Failed to send email."); diff --git a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts index b1af8195d9..db84f5aa99 100644 --- a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts +++ b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts @@ -11,6 +11,7 @@ export default class RealmSettingsPage { adminThemeList = "#kc-admin-console-theme + ul"; selectEmailTheme = "#kc-email-theme"; emailThemeList = "#kc-email-theme + ul"; + hostInput = "#kc-host"; selectDefaultLocale = "select-default-locale"; defaultLocaleList = "select-default-locale + ul"; emailSaveBtn = "email-tab-save"; @@ -72,6 +73,11 @@ export default class RealmSettingsPage { return this; } + fillHostField(host: string) { + cy.get(this.hostInput).type(host); + return this; + } + setDefaultLocale(locale: string) { cy.get(this.selectDefaultLocale).click(); cy.get(this.defaultLocaleList).contains(locale).click(); From 9f024402cf967ab0bf013d5ccd526bce03e89326 Mon Sep 17 00:00:00 2001 From: jenny-s51 Date: Tue, 22 Jun 2021 14:49:41 -0400 Subject: [PATCH 10/11] mark suggested change --- cypress/integration/realm_settings_test.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts index e442a614ab..e81d58a76b 100644 --- a/cypress/integration/realm_settings_test.spec.ts +++ b/cypress/integration/realm_settings_test.spec.ts @@ -69,15 +69,9 @@ describe("Realm settings", () => { realmSettingsPage.toggleCheck(realmSettingsPage.enableStartTlsCheck); realmSettingsPage.save(realmSettingsPage.emailSaveBtn); - cy.getId(realmSettingsPage.testConnectionButton).click(); - - realmSettingsPage.fillEmailField( - "example" + (Math.random() + 1).toString(36).substring(7) + "@example.com" - ); realmSettingsPage.fillHostField("localhost"); - - cy.getId(realmSettingsPage.modalTestConnectionButton).click(); + cy.getId(realmSettingsPage.testConnectionButton).click(); masthead.checkNotificationMessage("Error! Failed to send email."); }); From 094f551a1543292b532077e1d97e30db549844d7 Mon Sep 17 00:00:00 2001 From: jenny-s51 Date: Tue, 22 Jun 2021 15:42:38 -0400 Subject: [PATCH 11/11] add modal button and input functions --- cypress/integration/realm_settings_test.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts index e81d58a76b..9c34110e99 100644 --- a/cypress/integration/realm_settings_test.spec.ts +++ b/cypress/integration/realm_settings_test.spec.ts @@ -73,6 +73,12 @@ describe("Realm settings", () => { realmSettingsPage.fillHostField("localhost"); cy.getId(realmSettingsPage.testConnectionButton).click(); + realmSettingsPage.fillEmailField( + "example" + (Math.random() + 1).toString(36).substring(7) + "@example.com" + ); + + cy.getId(realmSettingsPage.modalTestConnectionButton).click(); + masthead.checkNotificationMessage("Error! Failed to send email."); });