Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the UX of the SMTP Section #16223

Merged
merged 35 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
daac40e
feat: allow reply_to and email_from to be set for an SMTP config
Ridhwana Jan 20, 2022
9208be7
feat: use these SMTP values in the mailers
Ridhwana Jan 20, 2022
0337165
spec: test the application_mailer
Ridhwana Jan 20, 2022
f7e3d98
fix: validate with email, and not url
Ridhwana Jan 21, 2022
1fa44cf
feat: mimic macs changes from https://github.com/forem/forem/pull/162…
Ridhwana Jan 21, 2022
dcc5201
Merge branch 'main' into Ridhwana/update-smtp-email-addresses
Ridhwana Feb 2, 2022
9963ba4
setup packs for admin
Ridhwana Feb 2, 2022
924ded9
refactor: order the keys and add a const for the auth methods
Ridhwana Feb 3, 2022
4995d2b
feat: rename the header to a more user friendlly name
Ridhwana Feb 3, 2022
fb44a96
chore: move the section with Emails
Ridhwana Feb 3, 2022
ed0ca8a
feat: add a toggle that will show and hide the SMTP form under certai…
Ridhwana Feb 3, 2022
14aa5be
feat: add the javaScript to handle the toggles
Ridhwana Feb 3, 2022
585acea
feat: add a better description until we convert to a dropdown
Ridhwana Feb 3, 2022
1ddbf0d
feat: ensure that we have declared sendgrid_enabled
Ridhwana Feb 3, 2022
8f955bc
chore: add anote to the config controller
Ridhwana Feb 3, 2022
05450d7
chore: remove references of the email addresses to keep brnach scoped
Ridhwana Feb 3, 2022
d3df886
feat: tweak js
Ridhwana Feb 3, 2022
1d79faf
test: cypress workflow to update smtp settings
Ridhwana Feb 3, 2022
af6f1fe
feat : update the smtp tests
Ridhwana Feb 7, 2022
9ca2b06
remove comments
Ridhwana Feb 7, 2022
fbd3cd2
update test
Ridhwana Feb 7, 2022
adee068
chore: rename NOTE
Ridhwana Feb 7, 2022
869acf0
feat: polisha dn test ForemInstance.only_sendgrid_enabled?
Ridhwana Feb 7, 2022
6e0abcf
main merge conflicts and rubocop fixes
Ridhwana Feb 7, 2022
c722e3a
chore: remove specs
Ridhwana Feb 7, 2022
26aad50
Update app/lib/constants/settings/smtp.rb
Ridhwana Feb 7, 2022
a48ad74
Update spec/system/admin/config/admin_updates_smtp_settings_spec.rb
Ridhwana Feb 7, 2022
1d177c9
Update spec/system/admin/config/admin_updates_smtp_settings_spec.rb
Ridhwana Feb 7, 2022
08f6440
Update spec/system/admin/config/admin_updates_smtp_settings_spec.rb
Ridhwana Feb 7, 2022
7a6aed4
Update app/javascript/packs/admin/config/smtp.js
Ridhwana Feb 7, 2022
147dacb
refactor js as per comments
Ridhwana Feb 8, 2022
505309c
refactor as per comments
Ridhwana Feb 8, 2022
5268989
Update cypress/integration/seededFlows/adminFlows/config/emailServerS…
Ridhwana Feb 8, 2022
2c00bef
refactor: update the Cypress tests
Ridhwana Feb 8, 2022
7d34f8d
Merge branch 'main' into Ridhwana/update-smtp-email-addresses
Ridhwana Feb 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/controllers/stories/feeds_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ def signed_in_base_feed
end

def signed_out_base_feed
strategy = AbExperiment.get(experiment: AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, controller: self, user: current_user,
default_value: AbExperiment::ORIGINAL_VARIANT)
strategy = AbExperiment.get(experiment: AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, controller: self,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some Rubocop fixes for LayoutLength

user: current_user, default_value: AbExperiment::ORIGINAL_VARIANT)
feed = if strategy.weighted_query_strategy?
Articles::Feeds::WeightedQueryStrategy.new(user: current_user, page: @page, tags: params[:tag])
elsif Settings::UserExperience.feed_strategy == "basic"
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/admin/controllers/config_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const emailAuthModalBody = `
<p>If you disable Email address as a registration option, people cannot create an account with their email address.</p>
<p>However, people who have already created an account using their email address can continue to login.</p>`;

// NOTE: In an effort to move away from Stimulus and create consistency across the codebase
// we are using vanilla JavaScript (app/javascript/packs/admin/config) to handle any new interactions.
export default class ConfigController extends Controller {
static targets = [
'authenticationProviders',
Expand Down
23 changes: 23 additions & 0 deletions app/javascript/packs/admin/config/smtp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
document
.getElementById('settings_smtp_own_email_server')
?.addEventListener('change', (event) => {
const [customSMTPSection] = document.getElementsByClassName(
'js-custom-smtp-section',
);

if (event.target.checked) {
customSMTPSection.classList.remove('hidden');
} else {
// when the user indicates that they do not want to use their own server
// we clear the form values except for those fields that have a default value.
const inputs = customSMTPSection.getElementsByTagName('input');
const haveDefaultValues = ['authentication'];

for (const input of inputs) {
if (!haveDefaultValues.some((el) => input.name.includes(el))) {
input.value = '';
}
}
customSMTPSection.classList.add('hidden');
}
});
7 changes: 4 additions & 3 deletions app/lib/constants/settings/smtp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ module SMTP
DETAILS = {
address: {
description: "Address of the remote mail server",
placeholder: "ie. smtp.gmail.com"
placeholder: "i.e. smtp.gmail.com"
},
port: {
description: "The port that your mail server runs on",
placeholder: "25"
},
authentication: {
description: " If your mail server requires authentication, " \
"you need to specify the authentication type here",
placeholder: "ie. plain, login, or cram_md5"
"you need to specify the authentication type here. " \
" i.e. plain, login, or cram_md5",
placeholder: "i.e. plain, login, or cram_md5"
},
user_name: {
description: "If your mail server requires authentication, copy the username from your server",
Expand Down
8 changes: 8 additions & 0 deletions app/models/forem_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ def self.smtp_enabled?
Settings::SMTP.provided_minimum_settings? || ENV["SENDGRID_API_KEY"].present?
end

def self.sendgrid_enabled?
ENV["SENDGRID_API_KEY"].present?
end

def self.only_sendgrid_enabled?
ForemInstance.sendgrid_enabled? && !Settings::SMTP.provided_minimum_settings?
end

def self.invitation_only?
Settings::Authentication.invite_only_mode?
end
Expand Down
5 changes: 3 additions & 2 deletions app/models/settings/smtp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ module Settings
class SMTP < Base
self.table_name = :settings_smtp

OPTIONS = %i[address port authentication user_name password domain].freeze
OPTIONS = %i[user_name password address authentication domain port].freeze
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the order of the fields to be a bit more friendly

AUTHENTICATION_METHODS = %w[plain login cram_md5].freeze

setting :address, type: :string, default: ApplicationConfig["SMTP_ADDRESS"].presence
setting :authentication, type: :string, default: ApplicationConfig["SMTP_AUTHENTICATION"].presence,
validates: { inclusion: %w[plain login cram_md5] }
validates: { inclusion: AUTHENTICATION_METHODS }
setting :domain, type: :string, default: ApplicationConfig["SMTP_DOMAIN"].presence
setting :password, type: :string, default: ApplicationConfig["SMTP_PASSWORD"].presence
setting :port, type: :integer, default: ApplicationConfig["SMTP_PORT"].presence || 25
Expand Down
4 changes: 2 additions & 2 deletions app/services/articles/feeds/weighted_query_strategy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class WeightedQueryStrategy
# Weight to give to the number of comments on the article.
comments_count_factor: {
clause: "articles.comments_count",
cases: (0..9).map { |n| [n, 0.8 + 0.02 * n] },
cases: (0..9).map { |n| [n, 0.8 + (0.02 * n)] },
fallback: 1,
requires_user: false,
group_by: "articles.comments_count"
Expand Down Expand Up @@ -190,7 +190,7 @@ class WeightedQueryStrategy
# user follows and the article has.
matching_tags_factor: {
clause: "LEAST(10.0, SUM(followed_tags.points))::integer",
cases: (0..9).map { |n| [n, 0.70 + 0.0303 * n] },
cases: (0..9).map { |n| [n, 0.70 + (0.0303 * n)] },
fallback: 1,
requires_user: true,
joins: ["LEFT OUTER JOIN taggings
Expand Down
45 changes: 35 additions & 10 deletions app/views/admin/settings/forms/_smtp.html.erb
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
<%= javascript_packs_with_chunks_tag "admin/config/smtp", defer: true %>

<%= form_for(Settings::SMTP.new,
url: admin_settings_smtp_settings_path,
html: { data: { action: "submit->config#updateConfigurationSettings" } }) do |f| %>
html: { data: {
action: "submit->config#updateConfigurationSettings",
testid: "emailServerSettings"
} }) do |f| %>
<div class="card mt-3" id="smtp-section">
<%= render partial: "admin/shared/card_header",
locals: { header: "SMTP Settings", state: "collapse", target: "smtpSettingsBodyContainer", expanded: false } %>
locals: { header: "Email Server Settings (SMTP)", state: "collapse", target: "smtpSettingsBodyContainer", expanded: false } %>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed the SMTP section to a more understandable term

<div id="smtpSettingsBodyContainer" class="card-body collapse hide" aria-labelledby="smtpSettingsBodyContainer">
<fieldset class="grid gap-4">
<% Settings::SMTP::OPTIONS.each do |config_key| %>
<div class="crayons-field">
<%= admin_config_label config_key %>
<%= admin_config_description Constants::Settings::SMTP::DETAILS[config_key][:description] %>
<%= f.text_field config_key,
class: "crayons-textfield",
value: Settings::SMTP.public_send(config_key),
placeholder: Constants::Settings::SMTP::DETAILS[config_key][:placeholder] %>

<% if ForemInstance.sendgrid_enabled? %>
<div class="crayons-field crayons-field--checkbox mt-3">
<%= f.check_box :own_email_server,
checked: Settings::SMTP.provided_minimum_settings?,
class: "crayons-checkbox" %>
<%= f.label :own_email_server, class: "crayons-field__label" do %>
Use my own email server
<% end %>
</div>
<% end %>

<% if ForemInstance.only_sendgrid_enabled? %>
<p class="mb-3">
As a Forem Cloud client, we provide an email server managed by the Forem team. All settings are managed by us and the from and reply email addresses are set as <%= ForemInstance.email %>. However, you can override this to use your own email server.
</p>
<% end %>

<div class="js-custom-smtp-section <%= "hidden" if ForemInstance.only_sendgrid_enabled? %>">
<% Settings::SMTP::OPTIONS.each do |config_key| %>
<div class="crayons-field mt-3">
<%= admin_config_label config_key, model: Settings::SMTP %>
<%= admin_config_description Constants::Settings::SMTP::DETAILS[config_key][:description] %>
<%= f.text_field config_key,
class: "crayons-textfield",
value: Settings::SMTP.public_send(config_key),
placeholder: Constants::Settings::SMTP::DETAILS[config_key][:placeholder],
"data-config-target": "smtpSetting#{config_key.to_s.camelize(:upper)}" %>
</div>
<% end %>
</div>
</fieldset>
<%= render "update_setting_button", f: f %>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/admin/settings/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<%= render partial: "forms/community" %>
<%= render partial: "forms/credits" %>
<%= render partial: "forms/emails" %>
<%= render partial: "forms/smtp" %>
<%= render partial: "forms/google_analytics" %>
<%= render partial: "forms/images", locals: { logo_allowed_types: @logo_allowed_types, logo_max_file_size: @logo_max_file_size } %>
<%= render partial: "forms/mascot" %>
Expand All @@ -42,7 +43,6 @@
<%= render partial: "forms/newsletter" %>
<%= render partial: "forms/onboarding" %>
<%= render partial: "forms/rate_limit" %>
<%= render partial: "forms/smtp" %>
<%= render partial: "forms/sponsors" %>
<%= render partial: "forms/tags" %>
<%= render partial: "forms/user_experience" %>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
describe('Email Server Settings Section', () => {
beforeEach(() => {
cy.testSetup();
cy.fixture('users/adminUser.json').as('user');

cy.get('@user').then((user) => {
cy.loginAndVisit(user, '/admin/customization/config');
});
});

describe('email server settings', () => {
it('updates the smtp fields', () => {
cy.findByTestId('emailServerSettings').as('emailServerSettings');

cy.get('@emailServerSettings')
.findByText('Email Server Settings (SMTP)')
.click();

cy.get('@emailServerSettings').within(() => {
cy.findByText('Email Server Settings (SMTP)').click();
cy.findByLabelText('User name').clear().type('jane_doe');
cy.findByLabelText('Password').clear().type('abc123456');
cy.findByLabelText('Address').clear().type('smtp.gmail.com');
cy.findByLabelText('Authentication').clear().type('plain');
cy.findByText('Update Settings').click();
});

cy.url().should('contains', '/admin/customization/config');
cy.findByText('Successfully updated settings.').should('be.visible');
cy.findByLabelText('User name').should('have.value', 'jane_doe');
cy.findByLabelText('Password').should('have.value', 'abc123456');
cy.findByLabelText('Address').should('have.value', 'smtp.gmail.com');
cy.findByLabelText('Authentication').should('have.value', 'plain');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ describe('Set a landing page from the admin portal', () => {
cy.findByRole('main').within(() => {
cy.findByRole('checkbox', {
name: "Use as 'Locked Screen' Determines if this page will be used as a landing page for anonymous viewers.",
})
.check();
}).check();

cy.findAllByRole('button', { name: 'Update Page' }).first().click();
});
Expand All @@ -139,8 +138,7 @@ describe('Set a landing page from the admin portal', () => {
cy.findByRole('main').within(() => {
cy.findByRole('checkbox', {
name: "Use as 'Locked Screen' Determines if this page will be used as a landing page for anonymous viewers.",
})
.check();
}).check();

cy.findAllByRole('button', { name: 'Cancel' }).first().click();

Expand Down
25 changes: 25 additions & 0 deletions spec/models/forem_instance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,29 @@
expect(described_class.contact_email).to be(email)
end
end

describe ".only_sendgrid_enabled?" do
it "returns false when the minimum SMTP settings are provided" do
allow(Settings::SMTP).to receive(:user_name).and_return("something")
allow(Settings::SMTP).to receive(:password).and_return("something")
allow(Settings::SMTP).to receive(:address).and_return("something")

expect(described_class.only_sendgrid_enabled?).to be(false)
end

it "returns false when Sendgrid is not enabled" do
allow(described_class).to receive(:sendgrid_enabled?).and_return(false)
expect(described_class.only_sendgrid_enabled?).to be(false)
end

it "returns true if Sendgrid is enabled and the minimum SMTP settings are not provided" do
allow(described_class).to receive(:sendgrid_enabled?).and_return(true)

allow(Settings::SMTP).to receive(:user_name).and_return(nil)
allow(Settings::SMTP).to receive(:password).and_return(nil)
allow(Settings::SMTP).to receive(:address).and_return(nil)

expect(described_class.only_sendgrid_enabled?).to be(true)
end
end
end
111 changes: 111 additions & 0 deletions spec/system/admin/config/admin_updates_smtp_settings_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
require "rails_helper"

RSpec.describe "Admin updates SMTP Settings", type: :system do
let(:admin) { create(:user, :super_admin) }

before do
sign_in admin
end

# We're unable to set and unset an ENV variable in Cypress to test different scenarios
# hence, we test the view layouts with Capybara, and we test successful updates with Cypress.
context "when Sendgrid is not enabled and SMTP is not enabled" do
before do
allow(ForemInstance).to receive(:sendgrid_enabled?).and_return(false)
allow(Settings::SMTP).to receive(:address).and_return(nil)
allow(Settings::SMTP).to receive(:user_name).and_return(nil)
allow(Settings::SMTP).to receive(:password).and_return(nil)
visit admin_config_path
end

it "does not show the 'Use my own email server' checkbox" do
within("form[data-testid='emailServerSettings']") do
expect(page).not_to have_content("Use my own email server")
end
end

it "shows the SMTP Form", :aggregate_failures do
within("form[data-testid='emailServerSettings']") do
expect(page).to have_selector(".js-custom-smtp-section")
expect(page).not_to have_selector(".js-custom-smtp-section.hidden")
end
end
end

context "when Sendgrid is enabled and SMTP is not enabled" do
before do
allow(ForemInstance).to receive(:sendgrid_enabled?).and_return(true)
allow(ForemInstance).to receive(:email).and_return("yo@forem.com")
allow(Settings::SMTP).to receive(:address).and_return(nil)
allow(Settings::SMTP).to receive(:user_name).and_return(nil)
allow(Settings::SMTP).to receive(:password).and_return(nil)
visit admin_config_path
end

it "shows the checkbox to allow one to toggle ones own server" do
within("form[data-testid='emailServerSettings']") do
expect(page).to have_content("Use my own email server")
end
end

it "shows a description" do
within("form[data-testid='emailServerSettings']") do
# rubocop:disable Layout/LineLength
expect(page).to have_content("As a Forem Cloud client, we provide an email server managed by the Forem team. All settings are managed by us and the from and reply email addresses are set as yo@forem.com. However, you can override this to use your own email server.")
# rubocop:enable Layout/LineLength
end
end

it "does not show an SMTP Form" do
within("form[data-testid='emailServerSettings']") do
expect(page).to have_selector(".js-custom-smtp-section.hidden")
end
end
end

context "when Sendgrid is not enabled and SMTP is enabled" do
before do
allow(ForemInstance).to receive(:sendgrid_enabled?).and_return(false)
allow(Settings::SMTP).to receive(:address).and_return("smtp.gmail.com")
allow(Settings::SMTP).to receive(:user_name).and_return("jane_doe")
allow(Settings::SMTP).to receive(:password).and_return("abc123456")
visit admin_config_path
end

it "does not show the 'Use my own email server' checkbox" do
within("form[data-testid='emailServerSettings']") do
expect(page).not_to have_content("Use my own email server")
end
end

it "shows the SMTP Form", :aggregate_failures do
within("form[data-testid='emailServerSettings']") do
expect(page).to have_selector(".js-custom-smtp-section")
expect(page).not_to have_selector(".js-custom-smtp-section.hidden")
end
end
end

context "when Sendgrid is enabled and SMTP is enabled" do
before do
allow(ForemInstance).to receive(:sendgrid_enabled?).and_return(true)
allow(Settings::SMTP).to receive(:address).and_return("smtp.gmail.com")
allow(Settings::SMTP).to receive(:user_name).and_return("jane_doe")
allow(Settings::SMTP).to receive(:password).and_return("abc123456")
visit admin_config_path
end

it "shows the 'Use my own email server' checkbox" do
within("form[data-testid='emailServerSettings']") do
expect(page).to have_content("Use my own email server")
end
end

it "shows an SMTP Form", :aggregate_failures do
within("form[data-testid='emailServerSettings']") do
expect(page).to have_selector(".js-custom-smtp-section")
expect(page).not_to have_selector(".js-custom-smtp-section.hidden")
end
end
end
end