Move MailchimpBot to Mailchimp::Bot service (#12205)

* Move MailchimpBot to Mailchimp::Bot service

* Update specs

* Put back old Mailchimp Bot
This commit is contained in:
Alex 2021-01-12 10:28:48 -05:00 committed by GitHub
parent 19c9ce96aa
commit dfeccf6c11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 351 additions and 9 deletions

View file

@ -500,7 +500,7 @@ class User < ApplicationRecord
def unsubscribe_from_newsletters
return if email.blank?
MailchimpBot.new(self).unsubscribe_all_newsletters
Mailchimp::Bot.new(self).unsubscribe_all_newsletters
end
def auditable?

View file

@ -0,0 +1,201 @@
module Mailchimp
class Bot
attr_reader :user, :saved_changes, :gibbon
def initialize(user)
@user = user
@saved_changes = user.saved_changes
Gibbon::Request.api_key = SiteConfig.mailchimp_api_key
Gibbon::Request.timeout = 15
@gibbon = Gibbon::Request.new
end
def upsert
return true unless Rails.env.production? || Rails.env.test?
manage_community_moderator_list
manage_tag_moderator_list
upsert_to_newsletter
end
def upsert_to_newsletter
# attempt to update email if user changed email addresses
success = false
begin
gibbon.lists(SiteConfig.mailchimp_newsletter_id).members(target_md5_email).upsert(
body: {
email_address: user.email,
status: user.email_newsletter ? "subscribed" : "unsubscribed",
merge_fields: {
NAME: user.name.to_s,
USERNAME: user.username.to_s,
TWITTER: user.twitter_username.to_s,
GITHUB: user.github_username.to_s,
IMAGE_URL: user.profile_image_url.to_s,
ARTICLES: user.articles.size,
COMMENTS: user.comments.size,
ONBOARD_PK: user.onboarding_package_requested.to_s,
EXPERIENCE: user.experience_level || 666
}
},
)
success = true
rescue Gibbon::MailChimpError => e
# If user was previously subscribed, set their status to "pending"
return resubscribe_to_newsletter if previously_subcribed?(e)
report_error(e)
end
success
end
def resubscribe_to_newsletter
success = false
begin
gibbon.lists(SiteConfig.mailchimp_newsletter_id).members(target_md5_email).upsert(
body: { status: "pending" },
)
success = true
rescue Gibbon::MailChimpError => e
report_error(e)
end
success
end
def manage_community_moderator_list
return false unless user.has_role?(:trusted)
success = false
status = user.email_community_mod_newsletter ? "subscribed" : "unsubscribed"
begin
gibbon.lists(SiteConfig.mailchimp_community_moderators_id).members(target_md5_email).upsert(
body: {
email_address: user.email,
status: status,
merge_fields: {
NAME: user.name.to_s,
USERNAME: user.username.to_s,
TWITTER: user.twitter_username.to_s,
GITHUB: user.github_username.to_s,
IMAGE_URL: user.profile_image_url.to_s
}
},
)
success = true
rescue Gibbon::MailChimpError => e
report_error(e)
end
success
end
def manage_tag_moderator_list
return false unless user.tag_moderator?
success = false
tag_ids = user.roles.where(name: "tag_moderator").pluck(:resource_id)
tag_names = Tag.where(id: tag_ids).pluck(:name)
status = user.email_tag_mod_newsletter ? "subscribed" : "unsubscribed"
begin
gibbon.lists(SiteConfig.mailchimp_tag_moderators_id).members(target_md5_email).upsert(
body: {
email_address: user.email,
status: status,
merge_fields: {
NAME: user.name.to_s,
USERNAME: user.username.to_s,
TWITTER: user.twitter_username.to_s,
GITHUB: user.github_username.to_s,
IMAGE_URL: user.profile_image_url.to_s,
TAGS: tag_names.join(", ")
}
},
)
success = true
rescue Gibbon::MailChimpError => e
report_error(e)
end
success
end
def unsub_sustaining_member
return unless user.tag_moderator?
gibbon.lists(SiteConfig.mailchimp_tag_moderators_id).members(target_md5_email).update(
body: {
status: "unsubscribed"
},
)
end
def unsub_community_mod
return unless user.has_role?(:trusted)
gibbon.lists(SiteConfig.mailchimp_community_moderators_id).members(target_md5_email).update(
body: {
status: "unsubscribed"
},
)
end
def unsub_tag_mod
return unless a_sustaining_member?
gibbon.lists(SiteConfig.mailchimp_sustaining_members_id).members(target_md5_email).update(
body: {
status: "unsubscribed"
},
)
end
def unsubscribe_all_newsletters
success = false
begin
gibbon.lists(SiteConfig.mailchimp_newsletter_id).members(target_md5_email).update(
body: {
status: "unsubscribed"
},
)
unsub_tag_mod
unsub_sustaining_member
unsub_community_mod
success = true
rescue Gibbon::MailChimpError => e
report_error(e)
end
success
end
private
def a_sustaining_member?
# Reasoning for including => saved_changes["monthly_dues"]
# Is that mailchimp should be updated if a user decides to
# unsubscribes
user.monthly_dues.positive? || saved_changes["monthly_dues"]
end
def md5_email(email)
Digest::MD5.hexdigest(email.downcase)
end
def report_error(exception)
Rails.logger.error(exception)
DatadogStatsClient.increment("mailchimp.errors",
tags: ["action:failed", "user_id:#{user.id}", "source:gibbon-gem"])
end
def target_md5_email
email = saved_changes["unconfirmed_email"] ? saved_changes["email"][0] : user.email
md5_email(email)
end
def previously_subcribed?(error)
error.title.include?("Member In Compliance State")
end
end
end

View file

@ -35,14 +35,14 @@ module Moderator
@user.remove_role :trusted
@user.remove_role :tag_moderator
@user.update(email_tag_mod_newsletter: false)
MailchimpBot.new(user).manage_tag_moderator_list
Mailchimp::Bot.new(user).manage_tag_moderator_list
@user.update(email_community_mod_newsletter: false)
MailchimpBot.new(user).manage_community_moderator_list
Mailchimp::Bot.new(user).manage_community_moderator_list
end
def remove_tag_moderator_role
@user.remove_role :tag_moderator
MailchimpBot.new(user).manage_tag_moderator_list
Mailchimp::Bot.new(user).manage_tag_moderator_list
end
def create_note(reason, content)

View file

@ -54,7 +54,7 @@ module TagModerators
Rails.cache.delete("user-#{user.id}/tag_moderators_list")
return unless tag_mod_newsletter_enabled?
MailchimpBot.new(user).manage_tag_moderator_list
Mailchimp::Bot.new(user).manage_tag_moderator_list
end
def tag_mod_newsletter_enabled?

View file

@ -9,7 +9,7 @@ module TagModerators
NotifyMailer.with(user: user).trusted_role_email.deliver_now
return unless community_mod_newsletter_enabled?
MailchimpBot.new(user).manage_community_moderator_list
Mailchimp::Bot.new(user).manage_community_moderator_list
end
def self.community_mod_newsletter_enabled?

View file

@ -6,7 +6,7 @@ module TagModerators
Rails.cache.delete("user-#{user.id}/tag_moderators_list")
return unless tag_mod_newsletter_enabled?
MailchimpBot.new(user).manage_tag_moderator_list
Mailchimp::Bot.new(user).manage_tag_moderator_list
end
def self.tag_mod_newsletter_enabled?

View file

@ -6,7 +6,7 @@ module Users
def perform(user_id)
user = User.find_by(id: user_id)
MailchimpBot.new(user).upsert if user
Mailchimp::Bot.new(user).upsert if user
end
end
end

View file

@ -0,0 +1,141 @@
require "rails_helper"
RSpec.describe Mailchimp::Bot, type: :service do
let(:user) { create(:user, :with_newsletters, :ignore_mailchimp_subscribe_callback) }
let(:article) { create(:article, user_id: user.id) }
let(:my_gibbon_client) { class_double Gibbon::Request }
let(:tag) do
create(:tag,
name: "tagname",
bg_color_hex: Faker::Color.hex_color,
text_color_hex: Faker::Color.hex_color,
supported: true)
end
before do
allow(Gibbon::Request).to receive(:new) { my_gibbon_client }
allow(my_gibbon_client).to receive(:lists) { my_gibbon_client }
allow(my_gibbon_client).to receive(:tag_mods) { my_gibbon_client }
allow(my_gibbon_client).to receive(:community_mods) { my_gibbon_client }
allow(my_gibbon_client).to receive(:members) { my_gibbon_client }
allow(my_gibbon_client).to receive(:upsert).and_return(true)
end
def matcher
{
body: {
email_address: user.email,
status: "subscribed",
merge_fields: {
NAME: user.name.to_s,
USERNAME: user.username.to_s,
TWITTER: user.twitter_username.to_s,
GITHUB: user.github_username.to_s,
IMAGE_URL: user.profile_image_url.to_s,
ARTICLES: user.articles.size,
COMMENTS: user.comments.size,
ONBOARD_PK: user.onboarding_package_requested.to_s,
EXPERIENCE: user.experience_level || 666
}
}
}
end
describe "#upsert" do
it "works" do
described_class.new(user).upsert
expect(my_gibbon_client).to have_received(:upsert)
end
end
describe "#upsert_to_newsletter" do
it "sends proper information" do
described_class.new(user).upsert_to_newsletter
expect(my_gibbon_client).to have_received(:upsert).with(matcher)
end
it "unsubscribes properly" do
user.update(email_newsletter: false)
described_class.new(user).upsert_to_newsletter
expect(my_gibbon_client).to have_received(:upsert)
.with(hash_including(body: hash_including(status: "unsubscribed")))
end
it "subscribes properly" do
user.update(email_newsletter: false)
user.update(email_newsletter: true)
described_class.new(user).upsert_to_newsletter
expect(my_gibbon_client).to have_received(:upsert)
.with(hash_including(body: hash_including(status: "subscribed")))
end
it "updates email properly" do
user.update(email: Faker::Internet.email)
user.confirm
described_class.new(user).upsert_to_newsletter
expect(my_gibbon_client).to have_received(:upsert)
.with(hash_including(body: hash_including(email_address: user.email)))
end
it "tries to resubscribe the user if the user has previously been subscribed" do
user.update(email_newsletter: false)
mailchimp_bot = described_class.new(user)
mc_error =
Gibbon::MailChimpError.new("Error", status_code: 400, title: "Member In Compliance State")
allow(mailchimp_bot.gibbon).to receive(:upsert).and_raise(mc_error)
allow(mailchimp_bot).to receive(:resubscribe_to_newsletter)
mailchimp_bot.upsert_to_newsletter
expect(mailchimp_bot).to have_received(:resubscribe_to_newsletter)
end
end
describe "manage community moderator list" do
it "returns false if user isn't a community moderator" do
expect(described_class.new(user).manage_community_moderator_list).to be(false)
end
it "sends proper information" do
user.update(email_community_mod_newsletter: true)
user.add_role :trusted
described_class.new(user).manage_community_moderator_list
expect(my_gibbon_client).to have_received(:upsert)
.with(hash_including(
body: hash_including(
status: "subscribed",
),
))
end
end
describe "manage tag moderator list" do
it "returns false if user isn't a tag moderator" do
expect(described_class.new(user).manage_community_moderator_list).to be(false)
end
it "sends proper information" do
user.update(email_tag_mod_newsletter: true)
user.add_role(:tag_moderator, tag)
described_class.new(user).manage_tag_moderator_list
expect(my_gibbon_client).to have_received(:upsert)
.with(hash_including(
body: hash_including(
status: "subscribed",
),
))
end
end
describe "#unsubscribe_all_newsletters" do
context "when called" do
before { allow(my_gibbon_client).to receive(:update).and_return(true) }
it "unsubscribes the user from the weekly newsletter" do
described_class.new(user).unsubscribe_all_newsletters
expect(my_gibbon_client).to have_received(:update)
.with(hash_including(body: hash_including(status: "unsubscribed")))
end
end
end
end

View file

@ -9,7 +9,7 @@ RSpec.describe Users::SubscribeToMailchimpNewsletterWorker, type: :worker do
it "subscribes user to mailchimp newsletter" do
mailchimp_bot = double
allow(MailchimpBot).to receive(:new).and_return(mailchimp_bot)
allow(Mailchimp::Bot).to receive(:new).and_return(mailchimp_bot)
allow(mailchimp_bot).to receive(:upsert)
worker.perform(user.id)