DEV: Convert group SMTP settings form to FormKit (#27965)

This commit changes the group SMTP settings form (at
`/g/:name/manage/email`) to use
FormKit, our magical new form component system  

---------

Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This commit is contained in:
Martin Brennan 2024-07-24 09:52:52 +10:00 committed by GitHub
parent d681decf01
commit db8c1f20ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 221 additions and 274 deletions

View File

@ -1,41 +1,27 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { cached, tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { action, getProperties } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
import { or } from "truth-helpers";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import Form from "discourse/components/form";
import formatDate from "discourse/helpers/format-date";
import withEventValue from "discourse/helpers/with-event-value";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { GROUP_SMTP_SSL_MODES } from "discourse/lib/constants";
import emailProviderDefaultSettings from "discourse/lib/email-provider-default-settings";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import I18n from "I18n";
import ComboBox from "select-kit/components/combo-box";
export default class GroupSmtpEmailSettings extends Component {
@service currentUser;
@service toasts;
@tracked smtpSettingsValid = false;
@tracked testingSettings = false;
form = new TrackedObject({
email_username: this.args.group.email_username,
email_password: this.args.group.email_password,
email_from_alias: this.args.group.email_from_alias,
smtp_server: this.args.group.smtp_server,
smtp_port: (this.args.group.smtp_port || "").toString(),
smtp_ssl_mode: this.args.group.smtp_ssl_mode || GROUP_SMTP_SSL_MODES.none,
});
get sslModes() {
return Object.keys(GROUP_SMTP_SSL_MODES).map((key) => {
return {
@ -45,16 +31,21 @@ export default class GroupSmtpEmailSettings extends Component {
});
}
get missingSettings() {
if (!this.form) {
return true;
}
return [
this.form.email_username,
this.form.email_password,
this.form.smtp_server,
this.form.smtp_port,
].some((value) => isEmpty(value));
@cached
get formData() {
const form = getProperties(
this.args.group,
"email_username",
"email_password",
"email_from_alias",
"smtp_server",
"smtp_port",
"smtp_ssl_mode"
);
form.smtp_ssl_mode ??= GROUP_SMTP_SSL_MODES.none;
return form;
}
@action
@ -64,55 +55,22 @@ export default class GroupSmtpEmailSettings extends Component {
}
@action
onChangeSslMode(newMode) {
this.form.smtp_ssl_mode = newMode;
this.changeSmtpSettingsValid(false);
}
@action
changeEmailUsername(newValue) {
this.form.email_username = newValue;
this.changeSmtpSettingsValid(false);
}
@action
changeEmailPassword(newValue) {
this.form.email_password = newValue;
this.changeSmtpSettingsValid(false);
}
@action
changeEmailFromAlias(newValue) {
this.form.email_from_alias = newValue;
this.changeSmtpSettingsValid(false);
}
@action
changeSmtpServer(newValue) {
this.form.smtp_server = newValue;
this.changeSmtpSettingsValid(false);
}
@action
changeSmtpPort(newValue) {
this.form.smtp_port = newValue;
this.changeSmtpSettingsValid(false);
}
@action
prefillSettings(provider, event) {
prefillSettings(provider, setData, event) {
event?.preventDefault();
Object.assign(this.form, emailProviderDefaultSettings(provider, "smtp"));
const providerDefaults = emailProviderDefaultSettings(provider, "smtp");
Object.keys(providerDefaults).forEach((key) => {
setData(key, providerDefaults[key]);
});
}
@action
testSmtpSettings() {
testSmtpSettings(data) {
const settings = {
host: this.form.smtp_server,
port: this.form.smtp_port,
ssl_mode: this.form.smtp_ssl_mode,
username: this.form.email_username,
password: this.form.email_password,
host: data.smtp_server,
port: data.smtp_port,
ssl_mode: data.smtp_ssl_mode,
username: data.email_username,
password: data.email_password,
};
this.testingSettings = true;
@ -124,149 +82,118 @@ export default class GroupSmtpEmailSettings extends Component {
})
.then(() => {
this.changeSmtpSettingsValid(true);
this.args.group.setProperties({
smtp_server: this.form.smtp_server,
smtp_port: this.form.smtp_port,
smtp_ssl_mode: this.form.smtp_ssl_mode,
email_username: this.form.email_username,
email_from_alias: this.form.email_from_alias,
email_password: this.form.email_password,
smtp_server: data.smtp_server,
smtp_port: data.smtp_port,
smtp_ssl_mode: data.smtp_ssl_mode,
email_username: data.email_username,
email_from_alias: data.email_from_alias || "",
email_password: data.email_password,
});
this.toasts.success({
duration: 3000,
data: { message: I18n.t("groups.manage.email.smtp_settings_valid") },
});
})
.catch(popupAjaxError)
.finally(() => (this.testingSettings = false));
}
@action
resetTestingSettings(field, value, { set }) {
this.changeSmtpSettingsValid(false);
set(field, value);
}
<template>
<div class="group-smtp-email-settings">
<form class="groups-form form-horizontal group-smtp-form">
<div>
<div class="control-group">
<label for="username">{{i18n
"groups.manage.email.credentials.username"
}}</label>
<input
type="text"
name="username"
class="group-smtp-form__smtp-username"
value={{this.form.email_username}}
tabindex="1"
{{on "input" (withEventValue this.changeEmailUsername)}}
/>
</div>
<Form
@data={{this.formData}}
@onSubmit={{this.testSmtpSettings}}
as |form|
>
<form.Row as |row|>
<row.Col @size={{6}}>
<form.Field
@name="smtp_server"
@title={{i18n "groups.manage.email.credentials.smtp_server"}}
@validation="required"
@onSet={{fn this.resetTestingSettings "smtp_server"}}
as |field|
>
<field.Input />
</form.Field>
</row.Col>
<row.Col @size={{6}}>
<form.Field
@name="email_username"
@title={{i18n "groups.manage.email.credentials.username"}}
@validation="required"
@onSet={{fn this.resetTestingSettings "email_username"}}
as |field|
>
<field.Input />
</form.Field>
</row.Col>
<div class="control-group">
<label for="smtp_server">{{i18n
"groups.manage.email.credentials.smtp_server"
}}</label>
<input
type="text"
name="smtp_server"
class="group-smtp-form__smtp-server"
value={{this.form.smtp_server}}
tabindex="4"
{{on "input" (withEventValue this.changeSmtpServer)}}
/>
</div>
<row.Col @size={{6}}>
<form.Field
@name="smtp_port"
@title={{i18n "groups.manage.email.credentials.smtp_port"}}
@validation="required|integer"
@onSet={{fn this.resetTestingSettings "smtp_port"}}
as |field|
>
<field.Input @type="number" />
</form.Field>
</row.Col>
<row.Col @size={{6}}>
<form.Field
@name="email_password"
@title={{i18n "groups.manage.email.credentials.password"}}
@validation="required"
@onSet={{fn this.resetTestingSettings "email_password"}}
as |field|
>
<field.Password />
</form.Field>
</row.Col>
<div class="control-group">
<label for="smtp_ssl_mode">
{{i18n "groups.manage.email.credentials.smtp_ssl_mode"}}
</label>
<ComboBox
@content={{this.sslModes}}
@valueProperty="value"
@value={{this.form.smtp_ssl_mode}}
name="smtp_ssl_mode"
class="group-smtp-form__smtp-ssl-mode"
tabindex="6"
@onChange={{this.onChangeSslMode}}
/>
</div>
</div>
<row.Col @size={{6}}>
<form.Field
@name="smtp_ssl_mode"
@title={{i18n "groups.manage.email.credentials.smtp_ssl_mode"}}
@validation="required"
@onSet={{fn this.resetTestingSettings "smtp_ssl_mode"}}
as |field|
>
<field.Select as |select|>
{{#each this.sslModes as |sslMode|}}
<select.Option
@value={{sslMode.value}}
>{{sslMode.name}}</select.Option>
{{/each}}
</field.Select>
</form.Field>
</row.Col>
<row.Col @size={{6}}>
<form.Field
@name="email_from_alias"
@title={{i18n "groups.manage.email.settings.from_alias"}}
@description={{i18n
"groups.manage.email.settings.from_alias_hint"
}}
as |field|
>
<field.Input />
</form.Field>
</row.Col>
</form.Row>
<div>
<div class="control-group">
<label for="password">{{i18n
"groups.manage.email.credentials.password"
}}</label>
<input
type="password"
name="password"
class="group-smtp-form__smtp-password"
value={{this.form.email_password}}
tabindex="2"
{{on "input" (withEventValue this.changeEmailPassword)}}
/>
</div>
<div class="control-group">
<label for="smtp_port">{{i18n
"groups.manage.email.credentials.smtp_port"
}}</label>
<input
type="text"
name="smtp_port"
class="group-smtp-form__smtp-port"
value={{this.form.smtp_port}}
tabindex="5"
{{on "input" (withEventValue this.changeSmtpPort)}}
/>
</div>
</div>
<div>
<div class="control-group">
<label for="from_alias">{{i18n
"groups.manage.email.settings.from_alias"
}}</label>
<input
type="text"
name="from_alias"
class="group-smtp-form__smtp-from-alias"
id="from_alias"
value={{this.form.email_from_alias}}
{{on "input" (withEventValue this.changeEmailFromAlias)}}
tabindex="3"
/>
<p>{{i18n "groups.manage.email.settings.from_alias_hint"}}</p>
</div>
</div>
</form>
<div class="control-group">
<div class="group-smtp-prefill-options">
{{i18n "groups.manage.email.prefill.title"}}
<ul>
<li>
<a
id="prefill_smtp_gmail"
href
{{on "click" (fn this.prefillSettings "gmail")}}
>{{i18n "groups.manage.email.prefill.gmail"}}</a>
</li>
<li>
<a
id="prefill_smtp_outlook"
href
{{on "click" (fn this.prefillSettings "outlook")}}
>{{i18n "groups.manage.email.prefill.outlook"}}</a>
</li>
<li>
<a
id="prefill_smtp_office365"
href
{{on "click" (fn this.prefillSettings "office365")}}
>{{i18n "groups.manage.email.prefill.office365"}}</a>
</li>
</ul>
</div>
</div>
<div class="control-group buttons">
<DButton
@disabled={{or this.missingSettings this.testingSettings}}
@action={{this.testSmtpSettings}}
<form.Submit
@disabled={{or this.testingSettings}}
@icon="cog"
@label="groups.manage.email.test_settings"
@title="groups.manage.email.settings_required"
@ -274,18 +201,33 @@ export default class GroupSmtpEmailSettings extends Component {
class="btn-primary group-smtp-form__test-smtp-settings"
/>
<ConditionalLoadingSpinner
@size="small"
@condition={{this.testingSettings}}
/>
{{#if @smtpSettingsValid}}
<span class="group-smtp-form__smtp-settings-ok">
{{dIcon "check-circle"}}
{{i18n "groups.manage.email.smtp_settings_valid"}}
</span>
{{/if}}
</div>
<form.Container class="group-smtp-prefill-options">
{{i18n "groups.manage.email.prefill.title"}}
<ul>
<li>
<a
id="prefill_smtp_gmail"
href
{{on "click" (fn this.prefillSettings "gmail" form.set)}}
>{{i18n "groups.manage.email.prefill.gmail"}}</a>
</li>
<li>
<a
id="prefill_smtp_outlook"
href
{{on "click" (fn this.prefillSettings "outlook" form.set)}}
>{{i18n "groups.manage.email.prefill.outlook"}}</a>
</li>
<li>
<a
id="prefill_smtp_office365"
href
{{on "click" (fn this.prefillSettings "office365" form.set)}}
>{{i18n "groups.manage.email.prefill.office365"}}</a>
</li>
</ul>
</form.Container>
</Form>
{{#if @group.smtp_updated_at}}
<div class=".group-smtp-form__last-updated-details">

View File

@ -104,7 +104,7 @@ export default class Validator {
}
break;
case "input-number":
if (typeof value === "undefined" || isNaN(Number(value))) {
if (!value || typeof value === "undefined" || isNaN(Number(value))) {
error = true;
}
break;

View File

@ -1,6 +1,7 @@
import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers";
import { click, currentRouteName, visit } from "@ember/test-helpers";
import { test } from "qunit";
import { GROUP_SMTP_SSL_MODES } from "discourse/lib/constants";
import formKit from "discourse/tests/helpers/form-kit-helper";
import {
acceptance,
exists,
@ -85,38 +86,38 @@ acceptance(
assert.ok(exists(".group-smtp-email-settings"));
await click("#prefill_smtp_gmail");
assert.strictEqual(
query(".group-smtp-form__smtp-server").value,
"smtp.gmail.com",
"prefills SMTP server settings for gmail"
);
assert.strictEqual(
query(".group-smtp-form__smtp-port").value,
"587",
"prefills SMTP port settings for gmail"
);
assert.strictEqual(
selectKit(".group-smtp-form__smtp-ssl-mode").header().value(),
assert
.form()
.field("smtp_server")
.hasValue("smtp.gmail.com", "prefills SMTP server settings for gmail");
assert
.form()
.field("smtp_port")
.hasValue("587", "prefills SMTP port settings for gmail");
assert
.form()
.field("smtp_ssl_mode")
.hasValue(
GROUP_SMTP_SSL_MODES.starttls.toString(),
"prefills SSL mode to STARTTLS for gmail"
);
assert.ok(
exists(".group-smtp-form__test-smtp-settings:disabled"),
await formKit().submit();
assert.form().hasErrors(
{
[I18n.t("groups.manage.email.credentials.username")]: "Required",
[I18n.t("groups.manage.email.credentials.password")]: "Required",
},
"does not allow testing settings if not all fields are filled"
);
await fillIn('input[name="username"]', "myusername@gmail.com");
await fillIn('input[name="password"]', "password@gmail.com");
await fillIn("#from_alias", "akasomegroup@example.com");
await click(".group-smtp-form__test-smtp-settings");
assert.ok(
exists(".group-smtp-form__smtp-settings-ok"),
"tested settings are ok"
);
await formKit().field("email_username").fillIn("myusername@gmail.com");
await formKit().field("email_password").fillIn("password");
await formKit()
.field("email_from_alias")
.fillIn("akasomegroup@example.com");
await formKit().submit();
await click(".group-manage-save");
assert.strictEqual(
@ -144,9 +145,9 @@ acceptance(
await click("#enable_smtp");
await click("#prefill_smtp_gmail");
await fillIn('input[name="username"]', "myusername@gmail.com");
await fillIn('input[name="password"]', "password@gmail.com");
await click(".group-smtp-form__test-smtp-settings");
await formKit().field("email_username").fillIn("myusername@gmail.com");
await formKit().field("email_password").fillIn("password");
await formKit().submit();
await click(".group-manage-save");
assert.notOk(
@ -284,25 +285,28 @@ acceptance(
assert.notOk(exists("#enable_smtp:disabled"), "SMTP is not disabled");
assert.notOk(exists("#enable_imap:disabled"), "IMAP is not disabled");
assert.strictEqual(
query("[name='username']").value,
"test@test.com",
"email username is prefilled"
);
assert.strictEqual(
query("[name='password']").value,
"password",
"email password is prefilled"
);
assert.strictEqual(
query("[name='smtp_server']").value,
"smtp.gmail.com",
"smtp server is prefilled"
);
assert.strictEqual(
query("[name='smtp_port']").value,
"587",
"smtp port is prefilled"
assert
.form()
.field("email_username")
.hasValue("test@test.com", "email username is prefilled");
assert
.form()
.field("email_password")
.hasValue("password", "email password is prefilled");
assert
.form()
.field("smtp_server")
.hasValue("smtp.gmail.com", "SMTP server is prefilled");
assert
.form()
.field("smtp_port")
.hasValue("587", "SMTP port is prefilled");
assert
.form()
.field("smtp_ssl_mode")
.hasValue(
GROUP_SMTP_SSL_MODES.starttls.toString(),
"SMTP ssl mode is prefilled"
);
assert.strictEqual(
@ -355,9 +359,9 @@ acceptance(
await click("#enable_smtp");
await click("#prefill_smtp_gmail");
await fillIn('input[name="username"]', "myusername@gmail.com");
await fillIn('input[name="password"]', "password@gmail.com");
await click(".group-smtp-form__test-smtp-settings");
await formKit().field("email_username").fillIn("myusername@gmail.com");
await formKit().field("email_password").fillIn("password");
await formKit().submit();
assert.strictEqual(
query(".dialog-body").innerText.trim(),

View File

@ -1,5 +1,5 @@
.form-kit__control-icon {
width: auto !important;
width: 100% !important;
min-width: var(--form-kit-small-input);
.formatted-selection {
display: none !important;

View File

@ -1,5 +1,6 @@
.form-kit__control-menu {
@include default-input;
justify-content: space-between;
}
.fk-d-menu {

View File

@ -1,5 +1,5 @@
.form-kit__control-password {
width: auto !important;
width: 100%;
height: 100%;
padding: 0 0 0 0.5em !important;
margin: 0 !important;
@ -20,7 +20,7 @@
.form-kit__control-password-wrapper {
display: flex;
max-width: 100%;
width: auto !important;
width: 100%;
height: 2em;
background: var(--secondary);
border: 1px solid var(--primary-low-mid);

View File

@ -1,5 +1,5 @@
@mixin default-input {
width: auto !important;
width: 100% !important;
height: 2em;
background: var(--secondary);
border: 1px solid var(--primary-low-mid);