Add configuration for admins to specify compliant email strategies (#20663)

* Add configuration for admins to specify compliant email strategies

* Add keys for test
This commit is contained in:
Ben Halpern 2024-02-21 08:27:26 -08:00 committed by GitHub
parent 406e3fec4d
commit f88784cc35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 122 additions and 41 deletions

View file

@ -12,46 +12,60 @@ function removeExistingCSRF() {
function fetchBaseData() {
fetch('/async_info/base_data')
.then((response) => response.json())
.then(({ token, param, broadcast, user, creator }) => {
if (token) {
removeExistingCSRF();
}
.then(
({
token,
param,
broadcast,
user,
creator,
client_geolocation,
default_email_optin_allowed,
}) => {
if (token) {
removeExistingCSRF();
}
const newCsrfParamMeta = document.createElement('meta');
newCsrfParamMeta.name = 'csrf-param';
newCsrfParamMeta.content = param;
document.head.appendChild(newCsrfParamMeta);
const newCsrfParamMeta = document.createElement('meta');
newCsrfParamMeta.name = 'csrf-param';
newCsrfParamMeta.content = param;
document.head.appendChild(newCsrfParamMeta);
const newCsrfTokenMeta = document.createElement('meta');
newCsrfTokenMeta.name = 'csrf-token';
newCsrfTokenMeta.content = token;
document.head.appendChild(newCsrfTokenMeta);
document.body.dataset.loaded = 'true';
const newCsrfTokenMeta = document.createElement('meta');
newCsrfTokenMeta.name = 'csrf-token';
newCsrfTokenMeta.content = token;
document.head.appendChild(newCsrfTokenMeta);
document.body.dataset.loaded = 'true';
if (broadcast) {
document.body.dataset.broadcast = broadcast;
}
if (broadcast) {
document.body.dataset.broadcast = broadcast;
}
if (checkUserLoggedIn()) {
document.body.dataset.user = user;
document.body.dataset.creator = creator;
browserStoreCache('set', user);
if (checkUserLoggedIn()) {
document.body.dataset.user = user;
document.body.dataset.creator = creator;
document.body.dataset.clientGeolocation =
JSON.stringify(client_geolocation);
document.body.dataset.default_email_optin_allowed =
default_email_optin_allowed;
browserStoreCache('set', user);
setTimeout(() => {
if (typeof ga === 'function') {
ga('set', 'userId', JSON.parse(user).id);
}
if (typeof gtag === 'function') {
gtag('set', 'user_Id', JSON.parse(user).id);
}
}, 400);
} else {
// Ensure user data is not exposed if no one is logged in
delete document.body.dataset.user;
delete document.body.dataset.creator;
browserStoreCache('remove');
}
});
setTimeout(() => {
if (typeof ga === 'function') {
ga('set', 'userId', JSON.parse(user).id);
}
if (typeof gtag === 'function') {
gtag('set', 'user_Id', JSON.parse(user).id);
}
}, 400);
} else {
// Ensure user data is not exposed if no one is logged in
delete document.body.dataset.user;
delete document.body.dataset.creator;
browserStoreCache('remove');
}
},
);
}
function initializeBodyData() {

View file

@ -278,6 +278,15 @@ class ApplicationController < ActionController::Base
end
helper_method :client_geolocation
def default_email_optin_allowed?
return false if Settings::General.geos_with_allowed_default_email_opt_in.blank?
Settings::General.geos_with_allowed_default_email_opt_in.any? do |geo|
client_geolocation.to_s.starts_with?(geo)
end
end
helper_method :default_email_optin_allowed?
def forward_to_app_config_domain
# Let's only redirect get requests for this purpose.
return unless request.get? &&

View file

@ -14,6 +14,8 @@ class AsyncInfoController < ApplicationController
param: request_forgery_protection_token,
token: form_authenticity_token,
user: user_data,
client_geolocation: client_geolocation,
default_email_optin_allowed: default_email_optin_allowed?,
creator: user_is_a_creator
}
end

View file

@ -12,10 +12,13 @@ export class FollowTags extends Component {
this.handleClick = this.handleClick.bind(this);
this.handleComplete = this.handleComplete.bind(this);
const emailState =
document.body.dataset.defaultEmailOptinAllowed === 'true';
this.state = {
allTags: [],
selectedTags: [],
email_digest_periodic: false,
email_digest_periodic: emailState,
};
}

View file

@ -231,4 +231,28 @@ describe('FollowTags', () => {
expect(lastFetchUri).toEqual('/onboarding/notifications');
});
});
describe('emailDigestPeriodic state initialization', () => {
it('should initialize email_digest_periodic to true when data-default-email-optin-allowed is true', async () => {
// Simulate setting data-default-email-optin-allowed to true
document.body.dataset.defaultEmailOptinAllowed = 'true';
const { container } = renderFollowTags();
const checkbox = container.querySelector('#email_digest_periodic');
// Assert that the checkbox is checked, indicating email_digest_periodic state is true
expect(checkbox.checked).toBeTruthy();
});
it('should initialize email_digest_periodic to false when data-default-email-optin-allowed is false', async () => {
// Simulate setting data-default-email-optin-allowed to false
document.body.dataset.defaultEmailOptinAllowed = 'false';
const { container } = renderFollowTags();
const checkbox = container.querySelector('#email_digest_periodic');
// Assert that the checkbox is not checked, indicating email_digest_periodic state is false
expect(checkbox.checked).toBeFalsy();
});
});
});

View file

@ -109,6 +109,10 @@ module Constants
description: I18n.t("lib.constants.settings.general.onboarding_newsletter_opt_in_subhead.description"),
placeholder: I18n.t("lib.constants.settings.general.onboarding_newsletter_opt_in_subhead.placeholder")
},
geos_with_allowed_default_email_opt_in: {
description: I18n.t("lib.constants.settings.general.geos_with_allowed_default_email_opt_in.description"),
placeholder: I18n.t("lib.constants.settings.general.geos_with_allowed_default_email_opt_in.placeholder")
},
payment_pointer: {
description: I18n.t("lib.constants.settings.general.payment.description"),
placeholder: "$pay.somethinglikethis.co/value"

View file

@ -139,6 +139,8 @@ module Settings
setting :onboarding_newsletter_opt_in_head
setting :onboarding_newsletter_opt_in_subhead
setting :geos_with_allowed_default_email_opt_in, type: :array, default: %w[]
setting :default_content_language, type: :string, default: "en",
validates: { inclusion: Languages::Detection.codes }

View file

@ -36,6 +36,13 @@
class: "crayons-textfield",
value: Settings::General.onboarding_newsletter_opt_in_subhead,
placeholder: Constants::Settings::General.details[:onboarding_newsletter_opt_in_subhead][:placeholder] %>
<% if FeatureFlag.enabled?(Geolocation::FEATURE_FLAG) %>
<%= admin_config_label Constants::Settings::General.details[:geos_with_allowed_default_email_opt_in][:description] %>
<%= f.text_field :geos_with_allowed_default_email_opt_in,
class: "crayons-textfield",
value: Settings::General.geos_with_allowed_default_email_opt_in,
placeholder: Constants::Settings::General.details[:geos_with_allowed_default_email_opt_in][:placeholder] %>
<% end %>
</div>
</fieldset>

View file

@ -6,7 +6,7 @@
<fieldset>
<div class="flex email_newsletter mt-5 pt-4 ">
<div class="flex h-5 ml-3">
<input type="checkbox" id="email_newsletter" name="email_newsletter" class="w-4 h-4 mt-1 crayons-checkbox">
<input type="checkbox" id="email_newsletter" name="email_newsletter" class="w-4 h-4 mt-1 crayons-checkbox" <%= "checked".html_safe if default_email_optin_allowed? %>>
</div>
<div class="ml-3">
<label for="email_newsletter" class="font-medium text-gray-900 dark:text-gray-300">
@ -26,7 +26,7 @@
<fieldset>
<div class="flex email_newsletter mt-5 pt-3 ">
<div class="flex h-5 ml-3">
<input type="checkbox" id="email_newsletter" name="email_newsletter" class="w-4 h-4 mt-2 crayons-checkbox">
<input type="checkbox" id="email_newsletter" name="email_newsletter" class="w-4 h-4 mt-2 crayons-checkbox" <%= "checked".html_safe if default_email_optin_allowed? %>>
</div>
<div class="ml-2">
<label for="email_newsletter" class="font-medium text-gray-900 dark:text-gray-300">
@ -52,10 +52,10 @@
<legend>Email preferences</legend>
<ul>
<li class="checkbox-item">
<label for="email_newsletter"><input type="checkbox" id="email_newsletter" name="email_newsletter">I want to receive weekly newsletter emails.</label>
<label for="email_newsletter"><input type="checkbox" id="email_newsletter" name="email_newsletter" <%= "checked".html_safe if default_email_optin_allowed? %>>I want to receive weekly newsletter emails.</label>
</li>
<li class="checkbox-item">
<label for="email_digest_periodic"><input type="checkbox" id="email_digest_periodic" name="email_digest_periodic">I want to receive a periodic digest of top posts from my tags.</label>
<label for="email_digest_periodic"><input type="checkbox" id="email_digest_periodic" name="email_digest_periodic" <%= "checked".html_safe if default_email_optin_allowed? %>>I want to receive a periodic digest of top posts from my tags.</label>
</li>
</ul>
</fieldset>

View file

@ -157,6 +157,9 @@ en:
onboarding_newsletter_opt_in_subhead:
description: "Newsletter checkbox subheading"
placeholder: "E.g., Rest assured, we respect your privacy. Your email will only be used for our newsletter and won't be shared with others."
geos_with_allowed_default_email_opt_in:
description: 'List of countries where the default email opt-in is allowed by admin'
placeholder: 'List of valid countries: comma separated, letters only e.g. US,CA'
payment:
description: 'Used for site-wide web monetization. See: https://github.com/forem/forem/pull/6345'
periodic:

View file

@ -157,6 +157,9 @@ fr:
onboarding_newsletter_opt_in_subhead:
description: "Newsletter checkbox subheading"
placeholder: "E.g., Rest assured, we respect your privacy. Your email will only be used for our newsletter and won't be shared with others."
geos_with_allowed_default_email_opt_in:
description: 'List of countries where the default email opt-in is allowed by admin'
placeholder: 'List of valid countries: comma separated, letters only e.g. US,CA'
payment:
description: 'Used for site-wide web monetization. See: https://github.com/forem/forem/pull/6345'
periodic:

View file

@ -26,7 +26,17 @@ RSpec.describe "AsyncInfo" do
sign_in create(:user)
get "/async_info/base_data"
expect(response.parsed_body.keys).to match_array(%w[broadcast creator param token user])
expect(response.parsed_body.keys).to match_array(%w[broadcast client_geolocation creator
default_email_optin_allowed param token user])
end
it "returns default_email_optin_allowed true if valid match with client geo" do
sign_in create(:user)
allow(controller_instance).to receive(:client_geolocation).and_return("US-NY")
allow(Settings::General).to receive(:geos_with_allowed_default_email_opt_in).and_return(["US"])
get "/async_info/base_data"
expect(response.parsed_body["default_email_optin_allowed"]).to be(true)
end
end
end