Add a contact email address to the /admin/customization/config (#16497)

* feat: remove the default email and cobine the periodic digest and the contact email under the Email section

* refactor: rename  the email_link to contact link and use the contact_email as a default and fallback to the ForemInstance.email

* chore: alignment

* feat: use the contact_email helper

* feat: move the contact_email to the ForemInstance model

* feat: use ForemInstance.contact_email instead of the application helper method

* removed the application Helper

* feat: set the dafault on the contact_email

* fix: cypress tests

* Update app/lib/constants/settings/general.rb

Co-authored-by: Michael Kohl <me@citizen428.net>

Co-authored-by: Michael Kohl <me@citizen428.net>
This commit is contained in:
Ridhwana 2022-02-15 17:37:08 +02:00 committed by GitHub
parent afa214ac52
commit 5e93d3a25e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 133 additions and 118 deletions

View file

@ -71,7 +71,7 @@ module Admin
html = view_context.render partial: "pages/coc_text",
locals: {
community_name: view_context.community_name,
email_link: view_context.email_link
contact_link: view_context.contact_link
}
@page = case slug
when Page::CODE_OF_CONDUCT_SLUG

View file

@ -85,7 +85,7 @@ module Admin
user = User.find(params[:id])
send_to_admin = params[:send_to_admin].to_boolean
if send_to_admin
email = ::ForemInstance.email
email = ::ForemInstance.contact_email
receiver = "admin"
else
email = user.email

View file

@ -24,7 +24,7 @@ class ApiSecretsController < ApplicationController
flash[:notice] = I18n.t("api_secrets_controller.revoked")
else
flash[:error] =
I18n.t("errors.messages.try_again_email", email: ForemInstance.email)
I18n.t("errors.messages.try_again_email", email: ForemInstance.contact_email)
end
redirect_back(fallback_location: root_path)

View file

@ -21,7 +21,7 @@ class ConfirmationsController < Devise::ConfirmationsController
self.resource = resource_class.send_confirmation_instructions(resource_params)
resource.errors.clear # Don't leak user information, like paranoid mode.
message = I18n.t("confirmations_controller.email_sent", email: ForemInstance.email)
message = I18n.t("confirmations_controller.email_sent", email: ForemInstance.contact_email)
flash.now[:global_notice] = message
render :new
end

View file

@ -130,7 +130,7 @@ class UsersController < ApplicationController
def remove_identity
set_current_tab("account")
error_message = I18n.t("errors.messages.try_again_email", email: ForemInstance.email)
error_message = I18n.t("errors.messages.try_again_email", email: ForemInstance.contact_email)
unless Authentication::Providers.enabled?(params[:provider])
flash[:error] = error_message
redirect_to user_settings_path(@tab)

View file

@ -227,8 +227,8 @@ module ApplicationHelper
link_to body, collection.path, **kwargs
end
def email_link(text: nil, additional_info: nil)
email = ForemInstance.email
def contact_link(text: nil, additional_info: nil)
email = ForemInstance.contact_email
mail_to email, text || email, additional_info
end

View file

@ -5,6 +5,11 @@ module Constants
SVG_PLACEHOLDER = "<svg ...></svg>".freeze
DETAILS = {
contact_email: {
description: "Used for contact links. Please provide an email address where users " \
"can get in touch with you or your team.",
placeholder: "hello@example.com"
},
credit_prices_in_cents: {
small: {
description: "Price for small credit purchase (<10 credits).",

View file

@ -32,7 +32,7 @@ class Article < ApplicationRecord
PROHIBITED_UNICODE_CHARACTERS_REGEX = /[\u202a-\u202e]/ # BIDI embedding controls
def self.unique_url_error
I18n.t("models.article.unique_url", email: ForemInstance.email)
I18n.t("models.article.unique_url", email: ForemInstance.contact_email)
end
has_one :discussion_lock, dependent: :delete

View file

@ -13,6 +13,10 @@ class ForemInstance
ApplicationConfig["DEFAULT_EMAIL"]
end
def self.contact_email
Settings::General.contact_email
end
# Return true if we are operating on a local installation, false otherwise
def self.local?
Settings::General.app_domain.include?("localhost")

View file

@ -17,7 +17,8 @@ module Settings
setting :health_check_token, type: :string
setting :video_encoder_key, type: :string
# Email digest frequency
# Emails
setting :contact_email, type: :string, default: ApplicationConfig["DEFAULT_EMAIL"]
setting :periodic_email_digest, type: :integer, default: 2
# Google Analytics Tracking ID, e.g. UA-71991000-1

View file

@ -5,7 +5,7 @@ module ErrorMessages
def self.call(error_message)
return error_message unless error_message.match?(FRONTMATTER_ERROR)
I18n.t("services.error_messages.clean.parse_error", email: ForemInstance.email)
I18n.t("services.error_messages.clean.parse_error", email: ForemInstance.contact_email)
end
end
end

View file

@ -1,29 +0,0 @@
<%= form_for(Settings::General.new,
url: admin_settings_general_settings_path,
html: { data: {
action: "submit->config#updateConfigurationSettings",
testid: "emailDigestSectionForm"
} }) do |f| %>
<div class="card mt-3">
<%= render partial: "admin/shared/card_header",
locals: {
header: "Email digest frequency",
state: "collapse",
target: "emailDigestBodyContainer",
expanded: false
} %>
<div id="emailDigestBodyContainer" class="card-body collapse hide" aria-labelledby="emailDigestBodyContainer">
<fieldset class="grid gap-4">
<div class="crayons-field">
<%= admin_config_label :periodic_email_digest %>
<%= admin_config_description Constants::Settings::General::DETAILS[:periodic_email_digest][:description] %>
<%= f.number_field :periodic_email_digest,
class: "crayons-textfield",
value: Settings::General.periodic_email_digest,
placeholder: Constants::Settings::General::DETAILS[:periodic_email_digest][:placeholder] %>
</div>
</fieldset>
<%= render "update_setting_button", f: f %>
</div>
</div>
<% end %>

View file

@ -1,22 +1,38 @@
<div class="card mt-3">
<%= render partial: "admin/shared/card_header",
locals: {
header: "Emails",
state: "collapse",
target: "emailBodyContainer",
expanded: false
} %>
<div id="emailBodyContainer" class="card-body collapse hide" aria-labelledby="emailBodyContainer">
<fieldset class="grid gap-4">
<div class="crayons-field">
<label for="forem_instance_email" class="crayons-field__label">
Default
</label>
<div class="crayons-field__description">
Default (set via <span class="ff-monospace">default_email</span> environment variable)
<%= form_for(Settings::General.new,
url: admin_settings_general_settings_path,
html: { data: {
action: "submit->config#updateConfigurationSettings",
testid: "emailForm"
} }) do |f| %>
<div class="card mt-3">
<%= render partial: "admin/shared/card_header",
locals: {
header: "Emails",
state: "collapse",
target: "emailBodyContainer",
expanded: false
} %>
<div id="emailBodyContainer" class="card-body collapse hide" aria-labelledby="emailBodyContainer">
<fieldset class="grid gap-4">
<div class="crayons-field">
<%= admin_config_label :contact_email %>
<%= admin_config_description Constants::Settings::General::DETAILS[:contact_email][:description] %>
<%= f.text_field :contact_email,
class: "crayons-textfield",
value: Settings::General.contact_email,
placeholder: Constants::Settings::General::DETAILS[:contact_email][:placeholder] %>
</div>
<input id="forem_instance_email" type="text" value="<%= ForemInstance.email %>" class="form-control" readonly>
</div>
</fieldset>
<div class="crayons-field">
<%= admin_config_label :periodic_email_digest %>
<%= admin_config_description Constants::Settings::General::DETAILS[:periodic_email_digest][:description] %>
<%= f.number_field :periodic_email_digest,
class: "crayons-textfield",
value: Settings::General.periodic_email_digest,
placeholder: Constants::Settings::General::DETAILS[:periodic_email_digest][:placeholder] %>
</div>
</fieldset>
<%= render "update_setting_button", f: f %>
</div>
</div>
</div>
<% end %>

View file

@ -34,7 +34,6 @@
<%= render partial: "forms/community" %>
<%= render partial: "forms/credits" %>
<%= render partial: "forms/emails" %>
<%= render partial: "forms/email_digest_frequency" %>
<%= 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" %>

View file

@ -12,7 +12,7 @@
</p>
<p>
Exporting the data to your admin account will send it to your contact
admin email (<%= ForemInstance.email %>),
admin email (<%= ForemInstance.contact_email %>),
and exporting to the user will send it to their email (<%= @user.email %>).
</p>
<div style="display: flex;">

View file

@ -47,7 +47,7 @@
</div>
<br />
</div>
<p class="bulk-description"><em>Contact <%= email_link(additional_info: { subject: "Custom Bulk Pricing/Partnerships" }) %> for custom bulk pricing and partnerships.</em></p>
<p class="bulk-description"><em>Contact <%= contact_link(additional_info: { subject: "Custom Bulk Pricing/Partnerships" }) %> for custom bulk pricing and partnerships.</em></p>
<details>
<summary>
How many credits does one listing cost?

View file

@ -9,7 +9,7 @@
<%= t("views.deep_links.alert.subtitle") %>
</p>
<p class="mt-5">
<%= t("views.deep_links.alert.desc_html", email: link_to(ForemInstance.email, href: "mailto:#{ForemInstance.email}")) %>
<%= t("views.deep_links.alert.desc_html", email: link_to(ForemInstance.contact_email, href: "mailto:#{ForemInstance.contact_email}")) %>
</p>
</div>

View file

@ -1,6 +1,6 @@
Hi <%= @name %>, your account has been successfully deleted.
Contact us at <%= ForemInstance.email %> if there is anything more we can help with.
Contact us at <%= ForemInstance.contact_email %> if there is anything more we can help with.
Thanks,
The <%= community_name %> Team

View file

@ -26,7 +26,7 @@
<p>
Tag moderation is something we're continuously iterating on, so you can expect adjustments and expanding features over time as we receive feedback. We regularly send out a biweekly Mod Newsletter to keep mods up to date on these changes, so look out for it!
Of course, don't hesitate to reach out to us at any moment if you have any feedback — feel free to write to <%= email_link %> and share your thoughts.
Of course, don't hesitate to reach out to us at any moment if you have any feedback — feel free to write to <%= contact_link %> and share your thoughts.
</p>
<p>

View file

@ -12,7 +12,7 @@ For details on all available features and how to access them, visit our tag mode
Tag moderation is something we're continuously iterating on, so you can expect adjustments and expanding features over time as we receive feedback.
We regularly send out a biweekly Mod Newsletter to keep mods up to date on these changes, so look out for it!
Of course, don't hesitate to reach out to us at any moment if you have any feedback — feel free to write to <%= ForemInstance.email %> and share your thoughts.
Of course, don't hesitate to reach out to us at any moment if you have any feedback — feel free to write to <%= ForemInstance.contact_email %> and share your thoughts.
Thanks so much for volunteering your time to benefit <%= community_name %>. We sincerely appreciate your help!

View file

@ -29,7 +29,7 @@
You may occasionally receive notifications about optional moderation actions; please <a href="<%= app_url(user_settings_path(:notifications)) %>">unsubscribe</a> if you do not want to receive these updates.
</p>
<p>
If you have any questions or feedback for us, please write to <%= email_link %> and share your thoughts.
If you have any questions or feedback for us, please write to <%= contact_link %> and share your thoughts.
</p>
<p>

View file

@ -20,7 +20,7 @@ For details on all available features and how to access them, visit our <%= comm
You may occasionally receive notifications about optional moderation actions; please unsubscribe (<%= app_url(user_settings_path(:notifications)) %>) if you do not want to receive these updates.
If you have any questions or feedback for us, please write to <%= ForemInstance.email %> and share your thoughts.
If you have any questions or feedback for us, please write to <%= ForemInstance.contact_email %> and share your thoughts.
Thanks for being such an awesome member of the community!

View file

@ -79,7 +79,7 @@
<%= t("views.moderations.aside.feedback.subtitle") %>
</span>
<span>
<%= t("views.moderations.aside.feedback.description", email: ForemInstance.email) %>
<%= t("views.moderations.aside.feedback.description", email: ForemInstance.contact_email) %>
</span>
</div>
</aside>

View file

@ -38,7 +38,7 @@
<%= t("views.moderations.notice.desc2_html", code: link_to(t("views.moderations.notice.code_of_conduct"), code_of_conduct_path), trusted: link_to(t("views.moderations.notice.trusted"), community_moderation_path),
tag: link_to(t("views.moderations.notice.tag"), tag_moderation_path)) %>
</p>
<p><%= t("views.moderations.notice.desc3_html", email: mail_to(email_link, email_link)) %></p>
<p><%= t("views.moderations.notice.desc3_html", email: mail_to(contact_link, contact_link)) %></p>
<% unless user_signed_in? %>
<p><em><%= t("views.moderations.notice.desc4") %></em></p>
<% end %>

View file

@ -3,6 +3,6 @@
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName><%= t("xml.opensearch.short_name", community: helper.community_name) %></ShortName>
<Description><%= t("xml.opensearch.description", community: helper.community_name) %></Description>
<Contact><%= ForemInstance.email %></Contact>
<Contact><%= ForemInstance.contact_email %></Contact>
<Url type="text/html" template="<%= URL.url("search") %>?q={searchTerms}" />
</OpenSearchDescription>

View file

@ -36,9 +36,9 @@
<h2>Enforcement</h2>
<p>Violations of the Code of Conduct may be reported by contacting the team via the
<% if creator_settings_form? %>
abuse report form or by sending an email to <%= email_link %>. All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
abuse report form or by sending an email to <%= contact_link %>. All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
<% else %>
<a href="<%= app_url("/report-abuse") %>">abuse report form</a> or by sending an email to <%= email_link %>. All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
<a href="<%= app_url("/report-abuse") %>">abuse report form</a> or by sending an email to <%= contact_link %>. All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
<% end %>
</p>
<p>Moderators have the right and responsibility to remove comments or other contributions that are not aligned to this Code of Conduct, or to suspend temporarily or permanently any members for other behaviors that they deem inappropriate, threatening, offensive, or harmful.</p>

View file

@ -132,14 +132,14 @@
</p>
<p><strong>Deleting Your Personal Information</strong>
<br>
You may request deletion of your personal information and account by emailing <%= email_link %>.
You may request deletion of your personal information and account by emailing <%= contact_link %>.
<br>
<br>
To protect information from accidental or malicious destruction, we may maintain residual copies for a brief time period. But, if you delete your account, your information and content will be unrecoverable after that time.
</p>
<p><strong>Data Portability</strong>
<br>
If you would like to request a copy of your user data, please email <%= email_link %>.</p>
If you would like to request a copy of your user data, please email <%= contact_link %>.</p>
<p><strong>Business Transfers</strong>
<br>
If we are involved in a merger, acquisition, bankruptcy, reorganization or sale of assets such that your information would be transferred or become subject to a different privacy policy, well notify you in advance of any such change.
@ -150,4 +150,4 @@
</p>
<p><strong>Questions</strong>
<br>
We welcome feedback about this policy at <%= email_link %>.</p>
We welcome feedback about this policy at <%= contact_link %>.</p>

View file

@ -79,7 +79,7 @@
<p>
Users agree and certify that they have rights to share all content that they post on dev.to — including, but not limited to, information posted in articles, discussions, and comments. This rule applies to prose, code snippets, collections of links, etc. Regardless of citation, users may not post copy and pasted content that does not belong to them. Users assume all risk for the content they post, including someone else's reliance on its accuracy, claims relating to intellectual property, or other legal rights. If you believe that a user has plagiarized content, misrepresented their identity, misappropriated work, or otherwise run afoul of DMCA regulations, please email
<%= email_link %>. <%= community_name %> may remove any content users post for any reason.
<%= contact_link %>. <%= community_name %> may remove any content users post for any reason.
</p>
<h3 id="site-terms-of-use-modifications">
@ -96,7 +96,7 @@
<p>
All uses of the <%= community_name %> logo, <%= community_name %> badges, brand slogans, iconography, and the like, may only be used with express permission from <%= community_name %>.
<%= community_name %> reserves all rights, even if certain assets are included in <%= community_name %> open source projects. Please contact <%= email_link %> with any questions or to request permission.
<%= community_name %> reserves all rights, even if certain assets are included in <%= community_name %> open source projects. Please contact <%= contact_link %> with any questions or to request permission.
</p>
<h3 id="reserved-names">

View file

@ -27,7 +27,7 @@
<%= community_name %> would love to hear from you!
</p>
<p>
Email: <%= email_link %> 😁
Email: <%= contact_link %> 😁
</p>
<p>
Twitter: <a href="http://twitter.com/<%= Settings::General.social_media_handles["twitter"] %>">@<%= Settings::General.social_media_handles["twitter"] %></a> 👻

View file

@ -5,4 +5,4 @@
Your ability to post and comment may be limited.
</p>
<p>For more information, you may send a message to <%= ForemInstance.email %>.</p>
<p>For more information, you may send a message to <%= contact_link %>.</p>

View file

@ -220,7 +220,7 @@
</div>
<% end %>
<p>
<%= t("views.settings.org.admin.delete.questions_html", email: link_to(ForemInstance.email, ForemInstance.email)) %>
<%= t("views.settings.org.admin.delete.questions_html", email: link_to(ForemInstance.contact_email, ForemInstance.contact_email)) %>
</p>
</div>
</div>

View file

@ -42,7 +42,7 @@
deleteAccountVerificationInput.addEventListener('input', disableDeleteAccountBtn);
</script>
<p>
<%= t("views.settings.destroy.ghost_html", ghost: link_to("@ghost", "/ghost"), click: email_link(text: t("views.settings.destroy.click"), additional_info: { subject: "Request Account Deletion", body: @email_body })) %>
<%= t("views.settings.destroy.ghost_html", ghost: link_to("@ghost", "/ghost"), click: contact_link(text: t("views.settings.destroy.click"), additional_info: { subject: "Request Account Deletion", body: @email_body })) %>
</p>
<p>

View file

@ -15,7 +15,7 @@
<% if params[:state] == "previous-registration" %>
<div class="crayons-banner crayons-banner--error" role="alert">
<%= t("views.settings.previous", email: email_link) %>
<%= t("views.settings.previous", email: contact_link) %>
<%= t("contact_prompts.if_any_questions_html") %>
</div>
<% end %>

View file

@ -1,39 +1,46 @@
describe('Email digest frequency Section', () => {
describe('Emails Section', () => {
beforeEach(() => {
cy.testSetup();
cy.fixture('users/adminUser.json').as('user');
cy.get('@user').then((user) => {
cy.loginUser(user);
cy.loginAndVisit(user, '/admin/customization/config');
});
});
describe('contact settings', () => {
it('can update the contact email', () => {
cy.findByTestId('emailForm').as('emailForm');
cy.get('@emailForm').within(() => {
cy.findByText('Emails').click();
cy.findByLabelText('Contact email').clear().type('yo@dev.to');
cy.findByText('Update Settings').click();
});
cy.url().should('contains', '/admin/customization/config');
cy.findByText('Successfully updated settings.').should('be.visible');
cy.findByLabelText('Contact email').should('have.value', 'yo@dev.to');
});
});
describe('email digest frequency settings', () => {
it('can change the frequency', () => {
cy.get('@user').then(() => {
cy.visit('/admin/customization/config');
cy.findByTestId('emailDigestSectionForm').as('emailDigestSectionForm');
cy.findByTestId('emailForm').as('emailForm');
cy.get('@emailDigestSectionForm')
.findByText('Email digest frequency')
.click();
cy.get('@emailDigestSectionForm')
.get('#settings_general_periodic_email_digest')
.clear()
.type('42');
cy.get('@emailDigestSectionForm').findByText('Update Settings').click();
cy.url().should('contains', '/admin/customization/config');
cy.findByText('Successfully updated settings.').should('be.visible');
cy.get('#settings_general_periodic_email_digest').should(
'have.value',
'42',
);
cy.get('@emailForm').within(() => {
cy.findByText('Emails').click();
cy.findByLabelText('Periodic email digest').clear().type('42');
cy.findByText('Update Settings').click();
});
cy.url().should('contains', '/admin/customization/config');
cy.findByText('Successfully updated settings.').should('be.visible');
cy.findByLabelText('Periodic email digest').should('have.value', '42');
});
});
});

View file

@ -238,24 +238,24 @@ RSpec.describe ApplicationHelper, type: :helper do
end
end
describe "#email_link" do
describe "#contact_link" do
let(:default_email) { "hi@dev.to" }
before do
allow(ForemInstance).to receive(:email).and_return(default_email)
allow(ForemInstance).to receive(:contact_email).and_return(default_email)
end
it "returns an 'a' tag" do
expect(helper.email_link).to have_selector("a")
expect(helper.contact_link).to have_selector("a")
end
it "sets the correct href" do
expect(helper.email_link).to have_link(href: "mailto:#{default_email}")
expect(helper.contact_link).to have_link(href: "mailto:#{default_email}")
end
it "has the correct text in the a tag" do
expect(helper.email_link(text: "Link Name")).to have_text("Link Name")
expect(helper.email_link).to have_text(default_email)
expect(helper.contact_link(text: "Link Name")).to have_text("Link Name")
expect(helper.contact_link).to have_text(default_email)
end
it "returns an href with additional_info parameters" do
@ -266,7 +266,7 @@ RSpec.describe ApplicationHelper, type: :helper do
link = "<a href=\"mailto:#{default_email}?body=This%20is%20a%20longer%20body%20with%20a%20" \
"question%20mark%20%3F%20%0A%20and%20a%20newline&amp;subject=This%20is%20a%20long%20subject\">text</a>"
expect(email_link(text: "text", additional_info: additional_info)).to eq(link)
expect(contact_link(text: "text", additional_info: additional_info)).to eq(link)
end
end

View file

@ -1404,7 +1404,7 @@ RSpec.describe Article, type: :model do
create(:article, body_markdown: body_markdown, feed_source_url: url)
another_article = build(:article, body_markdown: body_markdown, feed_source_url: url)
error_message = "has already been taken. " \
"Email #{ForemInstance.email} for further details."
"Email #{ForemInstance.contact_email} for further details."
expect(another_article).not_to be_valid
expect(another_article.errors.messages[:canonical_url]).to include(error_message)
expect(another_article.errors.messages[:feed_source_url]).to include(error_message)

View file

@ -96,4 +96,16 @@ RSpec.describe ForemInstance, type: :model do
ENV["SENDGRID_API_KEY"] = nil
end
end
describe ".contact_email" do
let(:email) { "contact@dev.to" }
before do
allow(Settings::General).to receive(:contact_email).and_return(email)
end
it "sets the correct email" do
expect(described_class.contact_email).to be(email)
end
end
end

View file

@ -388,7 +388,7 @@ RSpec.describe "UserSettings", type: :request do
expect(response).to redirect_to("/settings/account")
error =
"An error occurred. Please try again or send an email to: #{ForemInstance.email}"
"An error occurred. Please try again or send an email to: #{ForemInstance.contact_email}"
expect(flash[:error]).to eq(error)
end
@ -431,7 +431,7 @@ RSpec.describe "UserSettings", type: :request do
delete users_remove_identity_path, params: { provider: provider }
error =
"An error occurred. Please try again or send an email to: #{ForemInstance.email}"
"An error occurred. Please try again or send an email to: #{ForemInstance.contact_email}"
expect(flash[:error]).to eq(error)
end

View file

@ -7,7 +7,7 @@ RSpec.describe "/confirm-email", type: :system do
click_button "Resend"
expect(page).to have_current_path(user_confirmation_path)
expected_message = I18n.t("confirmations_controller.email_sent", email: ForemInstance.email)
expected_message = I18n.t("confirmations_controller.email_sent", email: ForemInstance.contact_email)
expect(page).to have_content(expected_message)
end
end