Improve flow for testing e-mail server settings (#3619)

This commit is contained in:
Jon Koops 2022-10-24 13:43:46 +02:00 committed by GitHub
parent c56f9b4132
commit 1de4d24593
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 356 additions and 416 deletions

View file

@ -78,7 +78,15 @@ describe("Realm settings tabs tests", () => {
}); });
it("Go to email tab", () => { it("Go to email tab", () => {
const msg: string = "Error! Failed to send email."; // Configure an e-mail address so we can test the connection settings.
cy.wrap(null).then(async () => {
const adminUser = await adminClient.getAdminUser();
await adminClient.updateUser(adminUser.id!, {
email: "admin@example.com",
});
});
sidebarPage.goToRealmSettings(); sidebarPage.goToRealmSettings();
cy.findByTestId("rs-email-tab").click(); cy.findByTestId("rs-email-tab").click();
//required fields not filled in or not filled properly //required fields not filled in or not filled properly
@ -100,17 +108,10 @@ describe("Realm settings tabs tests", () => {
realmSettingsPage.toggleCheck(realmSettingsPage.enableSslCheck); realmSettingsPage.toggleCheck(realmSettingsPage.enableSslCheck);
realmSettingsPage.toggleCheck(realmSettingsPage.enableStartTlsCheck); realmSettingsPage.toggleCheck(realmSettingsPage.enableStartTlsCheck);
realmSettingsPage.fillHostField("localhost"); realmSettingsPage.fillHostField("localhost");
cy.intercept(`/admin/realms/${realmName}/users/*`).as("load");
cy.findByTestId(realmSettingsPage.testConnectionButton).click(); cy.findByTestId(realmSettingsPage.testConnectionButton).click();
cy.wait("@load");
//ln109-113 cause the tests to fail locally, but is needed for the test to pass on the dashboard. masthead.checkNotificationMessage("Error! Failed to send email", true);
realmSettingsPage.fillEmailField(
"example" + (Math.random() + 1).toString(36).substring(7) + "@example.com"
);
cy.findByTestId(realmSettingsPage.modalTestConnectionButton).click();
masthead.checkNotificationMessage(msg, true);
}); });
it("Go to themes tab", () => { it("Go to themes tab", () => {

View file

@ -4,11 +4,16 @@ export default class Masthead extends CommonElements {
private helpBtn = "#help"; private helpBtn = "#help";
private closeAlertMessageBtn = ".pf-c-alert__action button"; private closeAlertMessageBtn = ".pf-c-alert__action button";
private closeLastAlertMessageBtn = private closeLastAlertMessageBtn =
".pf-c-alert-group > li:first-child .pf-c-alert__action button"; "li:first-child .pf-c-alert__action button";
private alertMessage = ".pf-c-alert__title"; private alertMessage = ".pf-c-alert__title";
private userDrpDwn = "#user-dropdown"; private userDrpDwn = "#user-dropdown";
private userDrpDwnKebab = "#user-dropdown-kebab"; private userDrpDwnKebab = "#user-dropdown-kebab";
private globalAlerts = "global-alerts";
private getAlertsContainer() {
return cy.findByTestId(this.globalAlerts);
}
checkIsAdminConsole() { checkIsAdminConsole() {
cy.get(this.logoBtn).should("exist"); cy.get(this.logoBtn).should("exist");
@ -50,10 +55,13 @@ export default class Masthead extends CommonElements {
} }
checkNotificationMessage(message: string, closeNotification = true) { checkNotificationMessage(message: string, closeNotification = true) {
cy.get(this.alertMessage).should("contain.text", message); this.getAlertsContainer()
.find(this.alertMessage)
.should("contain.text", message);
if (closeNotification) { if (closeNotification) {
cy.get(`button[title="` + message.replaceAll('"', '\\"') + `"]`) this.getAlertsContainer()
.find(`button[title="` + message.replaceAll('"', '\\"') + `"]`)
.last() .last()
.click({ force: true }); .click({ force: true });
} }
@ -61,14 +69,16 @@ export default class Masthead extends CommonElements {
} }
closeLastAlertMessage() { closeLastAlertMessage() {
cy.get(this.closeLastAlertMessageBtn).click(); this.getAlertsContainer().find(this.closeLastAlertMessageBtn).click();
return this; return this;
} }
closeAllAlertMessages() { closeAllAlertMessages() {
cy.get(this.closeAlertMessageBtn).each(() => { this.getAlertsContainer().find(this.closeAlertMessageBtn).click({
cy.get(this.closeAlertMessageBtn).click({ force: true, multiple: true }); force: true,
multiple: true,
}); });
return this; return this;
} }

View file

@ -98,6 +98,17 @@ class AdminClient {
return await this.client.users.create(user); return await this.client.users.create(user);
} }
async updateUser(id: string, payload: UserRepresentation) {
await this.login();
return this.client.users.update({ id }, payload);
}
async getAdminUser() {
await this.login();
const [user] = await this.client.users.find({ username: "admin" });
return user;
}
async addUserToGroup(userId: string, groupId: string) { async addUserToGroup(userId: string, groupId: string) {
await this.login(); await this.login();
await this.client.users.addToGroup({ id: userId, groupId }); await this.client.users.addToGroup({ id: userId, groupId });

View file

@ -114,8 +114,6 @@
"loginWithEmailHelpText": "Allow users to log in with their email address.", "loginWithEmailHelpText": "Allow users to log in with their email address.",
"duplicateEmailsAllowed": "Duplicate emails", "duplicateEmailsAllowed": "Duplicate emails",
"duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.", "duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.",
"provideEmailTitle": "Provide your email address",
"provideEmail": "To test connection, you should provide your email address first.",
"verifyEmail": "Verify email", "verifyEmail": "Verify email",
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.", "verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
"userInfoSettings": "User info settings", "userInfoSettings": "User info settings",
@ -123,8 +121,13 @@
"enableSwitchSuccess": "{{switch}} changed successfully", "enableSwitchSuccess": "{{switch}} changed successfully",
"enableSwitchError": "Could not enable / disable due to {{error}}", "enableSwitchError": "Could not enable / disable due to {{error}}",
"testConnection": "Test connection", "testConnection": "Test connection",
"testConnectionHint": {
"withEmail": "When testing the connection an e-mail will be sent to the current user ({{email}}).",
"withoutEmail": "To test the connection you must first configure an e-mail address for the current user ({{userName}}).",
"withoutEmailAction": "Configure e-mail address"
},
"testConnectionSuccess": "Success! SMTP connection successful. E-mail was sent!", "testConnectionSuccess": "Success! SMTP connection successful. E-mail was sent!",
"testConnectionError": "Error! Failed to send email.", "testConnectionError": "Error! {{error}}",
"realmId": "Realm ID", "realmId": "Realm ID",
"displayName": "Display name", "displayName": "Display name",
"htmlDisplayName": "HTML Display name", "htmlDisplayName": "HTML Display name",

View file

@ -13,7 +13,7 @@ type AlertPanelProps = {
export function AlertPanel({ alerts, onCloseAlert }: AlertPanelProps) { export function AlertPanel({ alerts, onCloseAlert }: AlertPanelProps) {
return ( return (
<AlertGroup isToast> <AlertGroup data-testid="global-alerts" isToast>
{alerts.map(({ id, variant, message, description }) => ( {alerts.map(({ id, variant, message, description }) => (
<Alert <Alert
key={id} key={id}

View file

@ -1,107 +0,0 @@
import {
Button,
ButtonVariant,
Form,
FormGroup,
Modal,
ModalVariant,
TextContent,
ValidatedOptions,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useForm } from "react-hook-form";
import { emailRegexPattern } from "../util";
import { useAdminClient } from "../context/auth/AdminClient";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import type { EmailRegistrationCallback } from "./EmailTab";
type AddUserEmailModalProps = {
callback: EmailRegistrationCallback;
};
type AddUserEmailForm = {
email: string;
};
export const AddUserEmailModal = ({ callback }: AddUserEmailModalProps) => {
const { t } = useTranslation("groups");
const { adminClient } = useAdminClient();
const { whoAmI } = useWhoAmI();
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm<AddUserEmailForm>({
defaultValues: { email: "" },
});
const watchEmailInput = watch("email", "");
const cancel = () => callback(false);
const proceed = () => callback(true);
const save = async (formData: AddUserEmailForm) => {
await adminClient.users.update({ id: whoAmI.getUserId() }, formData);
proceed();
};
return (
<Modal
variant={ModalVariant.small}
title={t("realm-settings:provideEmailTitle")}
isOpen={true}
onClose={cancel}
actions={[
<Button
data-testid="modal-test-connection-button"
key="confirm"
variant="primary"
type="submit"
form="email-form"
isDisabled={!watchEmailInput}
>
{t("common:testConnection")}
</Button>,
<Button
id="modal-cancel"
data-testid="cancel"
key="cancel"
variant={ButtonVariant.link}
onClick={cancel}
>
{t("common:cancel")}
</Button>,
]}
>
<TextContent className="kc-provide-email-text">
{t("realm-settings:provideEmail")}
</TextContent>
<Form id="email-form" isHorizontal onSubmit={handleSubmit(save)}>
<FormGroup
className="kc-email-form-group"
name="add-email-address"
fieldId="email-id"
helperTextInvalid={t("users:emailInvalid")}
validated={
errors.email ? ValidatedOptions.error : ValidatedOptions.default
}
isRequired
>
<KeycloakTextInput
data-testid="email-address-input"
ref={register({ required: true, pattern: emailRegexPattern })}
autoFocus
type="text"
id="add-email"
name="email"
validated={
errors.email ? ValidatedOptions.error : ValidatedOptions.default
}
/>
</FormGroup>
</Form>
</Modal>
);
};

View file

@ -1,5 +1,9 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { import {
ActionGroup, ActionGroup,
ActionListItem,
Alert,
AlertActionLink,
AlertVariant, AlertVariant,
Button, Button,
Checkbox, Checkbox,
@ -7,29 +11,29 @@ import {
PageSection, PageSection,
Switch, Switch,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { useState } from "react"; import { useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom-v5-compat";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form-access/FormAccess"; import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import { PasswordInput } from "../components/password-input/PasswordInput";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useWhoAmI } from "../context/whoami/WhoAmI"; import { toUser } from "../user/routes/User";
import { emailRegexPattern } from "../util"; import { emailRegexPattern } from "../util";
import { AddUserEmailModal } from "./AddUserEmailModal"; import { useCurrentUser } from "../utils/useCurrentUser";
import { PasswordInput } from "../components/password-input/PasswordInput";
import "./realm-settings-section.css"; import "./realm-settings-section.css";
type RealmSettingsEmailTabProps = { type RealmSettingsEmailTabProps = {
realm: RealmRepresentation; realm: RealmRepresentation;
}; };
export type EmailRegistrationCallback = (registered: boolean) => void;
export const RealmSettingsEmailTab = ({ export const RealmSettingsEmailTab = ({
realm: initialRealm, realm: initialRealm,
}: RealmSettingsEmailTabProps) => { }: RealmSettingsEmailTabProps) => {
@ -37,10 +41,9 @@ export const RealmSettingsEmailTab = ({
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const { whoAmI } = useWhoAmI(); const currentUser = useCurrentUser();
const [realm, setRealm] = useState(initialRealm); const [realm, setRealm] = useState(initialRealm);
const [callback, setCallback] = useState<EmailRegistrationCallback>();
const { const {
register, register,
control, control,
@ -63,12 +66,6 @@ export const RealmSettingsEmailTab = ({
const save = async (form: RealmRepresentation) => { const save = async (form: RealmRepresentation) => {
try { try {
const registered = await registerEmailIfNeeded();
if (!registered) {
return;
}
const savedRealm = { ...realm, ...form }; const savedRealm = { ...realm, ...form };
// For default value, back end is expecting null instead of empty string // For default value, back end is expecting null instead of empty string
@ -102,12 +99,6 @@ export const RealmSettingsEmailTab = ({
if (serverSettings.port === 0) serverSettings.port = null; if (serverSettings.port === 0) serverSettings.port = null;
try { try {
const registered = await registerEmailIfNeeded();
if (!registered) {
return;
}
await adminClient.realms.testSMTPConnection( await adminClient.realms.testSMTPConnection(
{ realm: realm.realm! }, { realm: realm.realm! },
serverSettings serverSettings
@ -118,276 +109,246 @@ export const RealmSettingsEmailTab = ({
} }
}; };
/**
* Triggers the flow to register the user's email if the user does not yet have one configured, if successful resolves true, otherwise false.
*/
const registerEmailIfNeeded = async () => {
const user = await adminClient.users.findOne({ id: whoAmI.getUserId() });
// A user should always be found, throw if it is not.
if (!user) {
throw new Error("Unable to find user.");
}
// User already has an e-mail associated with it, no need to register.
if (user.email) {
return true;
}
// User needs to register, show modal to do so.
return new Promise<boolean>((resolve) => {
const callback: EmailRegistrationCallback = (registered) => {
setCallback(undefined);
resolve(registered);
};
setCallback(() => callback);
});
};
return ( return (
<> <PageSection variant="light">
{callback && <AddUserEmailModal callback={callback} />} <FormPanel title={t("template")} className="kc-email-template">
<PageSection variant="light"> <FormAccess
<FormPanel title={t("template")} className="kc-email-template"> isHorizontal
<FormAccess role="manage-realm"
isHorizontal className="pf-u-mt-lg"
role="manage-realm" onSubmit={handleSubmit(save)}
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("from")}
fieldId="kc-display-name"
isRequired
validated={errors.smtpServer?.from ? "error" : "default"}
helperTextInvalid={t("users:emailInvalid")}
>
<KeycloakTextInput
type="email"
id="kc-sender-email-address"
data-testid="sender-email-address"
name="smtpServer.from"
ref={register({
pattern: emailRegexPattern,
required: true,
})}
placeholder="Sender email address"
validated={errors.smtpServer?.from ? "error" : "default"}
/>
</FormGroup>
<FormGroup
label={t("fromDisplayName")}
fieldId="kc-from-display-name"
labelIcon={
<HelpItem
helpText="realm-settings-help:fromDisplayName"
fieldLabelId="realm-settings:authentication"
/>
}
>
<KeycloakTextInput
type="text"
id="kc-from-display-name"
data-testid="from-display-name"
name="smtpServer.fromDisplayName"
ref={register}
placeholder="Display name for Sender email address"
/>
</FormGroup>
<FormGroup
label={t("replyTo")}
fieldId="kc-reply-to"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
helperTextInvalid={t("users:emailInvalid")}
>
<KeycloakTextInput
type="email"
id="kc-reply-to"
name="smtpServer.replyTo"
ref={register({
pattern: emailRegexPattern,
})}
placeholder="Reply to email address"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
/>
</FormGroup>
<FormGroup
label={t("replyToDisplayName")}
fieldId="kc-reply-to-display-name"
labelIcon={
<HelpItem
helpText="realm-settings-help:replyToDisplayName"
fieldLabelId="realm-settings:replyToDisplayName"
/>
}
>
<KeycloakTextInput
type="text"
id="kc-reply-to-display-name"
name="smtpServer.replyToDisplayName"
ref={register}
placeholder='Display name for "reply to" email address'
/>
</FormGroup>
<FormGroup
label={t("envelopeFrom")}
fieldId="kc-envelope-from"
labelIcon={
<HelpItem
helpText="realm-settings-help:envelopeFrom"
fieldLabelId="realm-settings:envelopeFrom"
/>
}
>
<KeycloakTextInput
type="text"
id="kc-envelope-from"
name="smtpServer.envelopeFrom"
ref={register}
placeholder="Sender envelope email address"
/>
</FormGroup>
</FormAccess>
</FormPanel>
<FormPanel
className="kc-email-connection"
title={t("connectionAndAuthentication")}
> >
<FormAccess <FormGroup
isHorizontal label={t("from")}
role="manage-realm" fieldId="kc-display-name"
className="pf-u-mt-lg" isRequired
onSubmit={handleSubmit(save)} validated={errors.smtpServer?.from ? "error" : "default"}
helperTextInvalid={t("users:emailInvalid")}
> >
<FormGroup <KeycloakTextInput
label={t("host")} type="email"
fieldId="kc-host" id="kc-sender-email-address"
isRequired data-testid="sender-email-address"
name="smtpServer.from"
ref={register({
pattern: emailRegexPattern,
required: true,
})}
placeholder="Sender email address"
validated={errors.smtpServer?.from ? "error" : "default"}
/>
</FormGroup>
<FormGroup
label={t("fromDisplayName")}
fieldId="kc-from-display-name"
labelIcon={
<HelpItem
helpText="realm-settings-help:fromDisplayName"
fieldLabelId="realm-settings:authentication"
/>
}
>
<KeycloakTextInput
type="text"
id="kc-from-display-name"
data-testid="from-display-name"
name="smtpServer.fromDisplayName"
ref={register}
placeholder="Display name for Sender email address"
/>
</FormGroup>
<FormGroup
label={t("replyTo")}
fieldId="kc-reply-to"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
helperTextInvalid={t("users:emailInvalid")}
>
<KeycloakTextInput
type="email"
id="kc-reply-to"
name="smtpServer.replyTo"
ref={register({
pattern: emailRegexPattern,
})}
placeholder="Reply to email address"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
/>
</FormGroup>
<FormGroup
label={t("replyToDisplayName")}
fieldId="kc-reply-to-display-name"
labelIcon={
<HelpItem
helpText="realm-settings-help:replyToDisplayName"
fieldLabelId="realm-settings:replyToDisplayName"
/>
}
>
<KeycloakTextInput
type="text"
id="kc-reply-to-display-name"
name="smtpServer.replyToDisplayName"
ref={register}
placeholder='Display name for "reply to" email address'
/>
</FormGroup>
<FormGroup
label={t("envelopeFrom")}
fieldId="kc-envelope-from"
labelIcon={
<HelpItem
helpText="realm-settings-help:envelopeFrom"
fieldLabelId="realm-settings:envelopeFrom"
/>
}
>
<KeycloakTextInput
type="text"
id="kc-envelope-from"
name="smtpServer.envelopeFrom"
ref={register}
placeholder="Sender envelope email address"
/>
</FormGroup>
</FormAccess>
</FormPanel>
<FormPanel
className="kc-email-connection"
title={t("connectionAndAuthentication")}
>
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("host")}
fieldId="kc-host"
isRequired
validated={errors.smtpServer?.host ? "error" : "default"}
helperTextInvalid={t("common:required")}
>
<KeycloakTextInput
type="text"
id="kc-host"
name="smtpServer.host"
ref={register({ required: true })}
placeholder="SMTP host"
validated={errors.smtpServer?.host ? "error" : "default"} validated={errors.smtpServer?.host ? "error" : "default"}
helperTextInvalid={t("common:required")} />
> </FormGroup>
<KeycloakTextInput <FormGroup label={t("port")} fieldId="kc-port">
type="text" <KeycloakTextInput
id="kc-host" type="text"
name="smtpServer.host" id="kc-port"
ref={register({ required: true })} name="smtpServer.port"
placeholder="SMTP host" ref={register}
validated={errors.smtpServer?.host ? "error" : "default"} placeholder="SMTP port (defaults to 25)"
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("port")} fieldId="kc-port"> <FormGroup label={t("encryption")} fieldId="kc-html-display-name">
<KeycloakTextInput <Controller
type="text" name="smtpServer.ssl"
id="kc-port" control={control}
name="smtpServer.port" defaultValue="false"
ref={register} render={({ onChange, value }) => (
placeholder="SMTP port (defaults to 25)" <Checkbox
/> id="kc-enable-ssl"
</FormGroup> data-testid="enable-ssl"
<FormGroup label={t("encryption")} fieldId="kc-html-display-name"> label={t("enableSSL")}
<Controller ref={register}
name="smtpServer.ssl" isChecked={value === "true"}
control={control} onChange={(value) => onChange("" + value)}
defaultValue="false" />
render={({ onChange, value }) => ( )}
<Checkbox />
id="kc-enable-ssl" <Controller
data-testid="enable-ssl" name="smtpServer.starttls"
label={t("enableSSL")} control={control}
ref={register} defaultValue="false"
isChecked={value === "true"} render={({ onChange, value }) => (
onChange={(value) => onChange("" + value)} <Checkbox
/> id="kc-enable-start-tls"
)} data-testid="enable-start-tls"
/> label={t("enableStartTLS")}
<Controller ref={register}
name="smtpServer.starttls" isChecked={value === "true"}
control={control} onChange={(value) => onChange("" + value)}
defaultValue="false" />
render={({ onChange, value }) => ( )}
<Checkbox />
id="kc-enable-start-tls" </FormGroup>
data-testid="enable-start-tls" <FormGroup
label={t("enableStartTLS")} hasNoPaddingTop
ref={register} label={t("authentication")}
isChecked={value === "true"} fieldId="kc-authentication"
onChange={(value) => onChange("" + value)} >
/> <Controller
)} name="smtpServer.auth"
/> control={control}
</FormGroup> defaultValue=""
<FormGroup render={({ onChange, value }) => (
hasNoPaddingTop <Switch
label={t("authentication")} id="kc-authentication-switch"
fieldId="kc-authentication" data-testid="email-authentication-switch"
> label={t("common:enabled")}
<Controller labelOff={t("common:disabled")}
name="smtpServer.auth" isChecked={value === "true"}
control={control} onChange={(value) => {
defaultValue="" onChange("" + value);
render={({ onChange, value }) => ( }}
<Switch aria-label={t("authentication")}
id="kc-authentication-switch" />
data-testid="email-authentication-switch" )}
label={t("common:enabled")} />
labelOff={t("common:disabled")} </FormGroup>
isChecked={value === "true"} {authenticationEnabled === "true" && (
onChange={(value) => { <>
onChange("" + value); <FormGroup
}} label={t("username")}
aria-label={t("authentication")} fieldId="kc-username"
/> isRequired
)} validated={errors.smtpServer?.user ? "error" : "default"}
/> helperTextInvalid={t("common:required")}
</FormGroup> >
{authenticationEnabled === "true" && ( <KeycloakTextInput
<> type="text"
<FormGroup id="kc-username"
label={t("username")} data-testid="username-input"
fieldId="kc-username" name="smtpServer.user"
isRequired ref={register({ required: true })}
placeholder="Login username"
validated={errors.smtpServer?.user ? "error" : "default"} validated={errors.smtpServer?.user ? "error" : "default"}
helperTextInvalid={t("common:required")} />
> </FormGroup>
<KeycloakTextInput <FormGroup
type="text" label={t("password")}
id="kc-username" fieldId="kc-username"
data-testid="username-input" isRequired
name="smtpServer.user" validated={errors.smtpServer?.password ? "error" : "default"}
ref={register({ required: true })} helperTextInvalid={t("common:required")}
placeholder="Login username" labelIcon={
validated={errors.smtpServer?.user ? "error" : "default"} <HelpItem
helpText="realm-settings-help:password"
fieldLabelId="realm-settings:password"
/> />
</FormGroup> }
<FormGroup >
label={t("password")} <PasswordInput
fieldId="kc-username" id="kc-password"
isRequired data-testid="password-input"
name="smtpServer.password"
aria-label={t("password")}
validated={errors.smtpServer?.password ? "error" : "default"} validated={errors.smtpServer?.password ? "error" : "default"}
helperTextInvalid={t("common:required")} ref={register({ required: true })}
labelIcon={ />
<HelpItem </FormGroup>
helpText="realm-settings-help:password" </>
fieldLabelId="realm-settings:password" )}
/>
}
>
<PasswordInput
id="kc-password"
data-testid="password-input"
name="smtpServer.password"
aria-label={t("password")}
validated={
errors.smtpServer?.password ? "error" : "default"
}
ref={register({ required: true })}
/>
</FormGroup>
</>
)}
<ActionGroup> <ActionGroup>
<ActionListItem>
<Button <Button
variant="primary" variant="primary"
type="submit" type="submit"
@ -395,16 +356,22 @@ export const RealmSettingsEmailTab = ({
> >
{t("common:save")} {t("common:save")}
</Button> </Button>
</ActionListItem>
<ActionListItem>
<Button <Button
variant="secondary" variant="secondary"
onClick={() => testConnection()} onClick={() => testConnection()}
data-testid="test-connection-button" data-testid="test-connection-button"
isDisabled={ isDisabled={
!(emailRegexPattern.test(watchFromValue) && watchHostValue) !(emailRegexPattern.test(watchFromValue) && watchHostValue) ||
!currentUser?.email
} }
aria-describedby="descriptionTestConnection"
> >
{t("common:testConnection")} {t("common:testConnection")}
</Button> </Button>
</ActionListItem>
<ActionListItem>
<Button <Button
variant="link" variant="link"
onClick={reset} onClick={reset}
@ -412,10 +379,47 @@ export const RealmSettingsEmailTab = ({
> >
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionListItem>
</FormAccess> </ActionGroup>
</FormPanel> {currentUser && (
</PageSection> <FormGroup id="descriptionTestConnection">
</> {currentUser.email ? (
<Alert
variant="info"
isInline
title={t("testConnectionHint.withEmail", {
email: currentUser.email,
})}
/>
) : (
<Alert
variant="warning"
isInline
title={t("testConnectionHint.withoutEmail", {
userName: currentUser.username,
})}
actionLinks={
<AlertActionLink
component={(props) => (
<Link
{...props}
to={toUser({
realm: realmName,
id: currentUser.id!,
tab: "settings",
})}
/>
)}
>
{t("testConnectionHint.withoutEmailAction")}
</AlertActionLink>
}
/>
)}
</FormGroup>
)}
</FormAccess>
</FormPanel>
</PageSection>
); );
}; };

View file

@ -0,0 +1,18 @@
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { useState } from "react";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useWhoAmI } from "../context/whoami/WhoAmI";
export function useCurrentUser() {
const { whoAmI } = useWhoAmI();
const { adminClient } = useAdminClient();
const [currentUser, setCurrentUser] = useState<UserRepresentation>();
const userId = whoAmI.getUserId();
useFetch(() => adminClient.users.findOne({ id: userId }), setCurrentUser, [
userId,
]);
return currentUser;
}