Skip to content

Commit 0810d36

Browse files
committed
Merge pull request #189 from just3ws/master
Updated Popular Protips emailer with New Relic promotion option
2 parents 5120c8b + ae2a0b4 commit 0810d36

36 files changed

+818
-200
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ gem 'awesome_print'
9797
gem 'faraday', '~> 0.8.1'
9898
gem 'metamagic'
9999

100+
gem "mail_view", "~> 2.0.4"
101+
100102
# ----------------
101103

102104

Gemfile.lock

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
GIT
2-
remote: git://github.com/emberjs/ember-rails.git
3-
revision: 5e5a398f3c67c3a3b84b7513b93b22bf81055cc9
4-
specs:
5-
ember-rails (0.15.0)
6-
active_model_serializers
7-
barber (>= 0.4.1)
8-
ember-data-source (>= 1.0.0.beta.5)
9-
ember-source (>= 1.1.0)
10-
execjs (>= 1.2)
11-
handlebars-source (> 1.0.0)
12-
jquery-rails (>= 1.0.17)
13-
railties (>= 3.1)
14-
151
GIT
162
remote: git://github.com/nixme/jazz_hands.git
173
revision: 5e4b48f145883ecb14b55bf04eacc28ac9662676
@@ -38,6 +24,20 @@ GIT
3824
mime-types (>= 1.25, < 3.0)
3925
rest-client (~> 1.4)
4026

27+
GIT
28+
remote: git://github.com/emberjs/ember-rails.git
29+
revision: 5e5a398f3c67c3a3b84b7513b93b22bf81055cc9
30+
specs:
31+
ember-rails (0.15.0)
32+
active_model_serializers
33+
barber (>= 0.4.1)
34+
ember-data-source (>= 1.0.0.beta.5)
35+
ember-source (>= 1.1.0)
36+
execjs (>= 1.2)
37+
handlebars-source (> 1.0.0)
38+
jquery-rails (>= 1.0.17)
39+
railties (>= 3.1)
40+
4141
GEM
4242
remote: https://rubygems.org/
4343
remote: https://rails-assets.org/
@@ -350,6 +350,8 @@ GEM
350350
mail (2.5.4)
351351
mime-types (~> 1.16)
352352
treetop (~> 1.4.8)
353+
mail_view (2.0.4)
354+
tilt
353355
memoizable (0.4.2)
354356
thread_safe (~> 0.3, >= 0.3.1)
355357
metamagic (3.1.6)
@@ -753,6 +755,7 @@ DEPENDENCIES
753755
launchy
754756
linkedin
755757
local_time
758+
mail_view (~> 2.0.4)
756759
metamagic
757760
mini_magick
758761
mixpanel
Loading
Loading

app/clock.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
RefreshStaleUsersWorker.perform_async
1616
end
1717

18+
# On the first of every month send the popular protips from the previous month.
19+
every(1.day, 'protip_mailer:popular_protips', if: ->(t){ t.day == 1 }) do
20+
last_month = 1.month.ago
21+
ProtipMailerPopularProtipsWorker.perform_async(last_month.beginning_of_month, last_month.end_of_month)
22+
end
23+
1824
every(1.day, 'cleanup:protips:associate_zombie_upvotes', at: '00:00') {}
1925
every(1.day, 'clear_expired_sessions', at: '00:00') {}
2026
every(1.day, 'facts:system', at: '00:00') {}

app/helpers/protips_helper.rb

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -294,36 +294,35 @@ def user_upvoted?(protip)
294294
current_user && current_user_upvotes.include?(protip.public_id)
295295
end
296296

297-
def protip_stat_class(protip)
298-
class_name = best_stat_name(protip)
299-
#class_name << " " << (user_upvoted?(protip) ? "upvoted" : "")
300-
end
301-
302297
def formatted_best_stat_value(protip)
303-
value =
304-
case best_stat_name(protip).to_sym
305-
when :views
306-
views_stat_value(protip)
307-
else
308-
best_stat_value(protip)
309-
end
310-
number_to_human(value, units: {unit: "", thousand: "k"})
298+
value = case best_stat_name(protip).to_sym
299+
when :views
300+
views_stat_value(protip)
301+
else
302+
best_stat_value(protip)
303+
end
304+
305+
number_to_human(value, units: { unit: '', thousand: 'k' })
311306
end
312307

313-
def blur_protips?
314-
params[:show_all].nil? && !signed_in?
308+
def best_stat_name(protip)
309+
protip.best_stat.is_a?(Tire::Results::Item) ? protip.best_stat.name : protip.best_stat[0]
315310
end
316311

317-
def followings_fragment_cache_key(user_id)
318-
['v1', 'followings_panel', user_id]
312+
def views_stat_value(protip)
313+
best_stat_value(protip) * Protip::COUNTABLE_VIEWS_CHUNK
319314
end
320315

321316
def best_stat_value(protip)
322317
protip.best_stat.is_a?(Tire::Results::Item) ? protip.best_stat.value.to_i : protip.best_stat[1]
323318
end
324319

325-
def best_stat_name(protip)
326-
protip.best_stat.is_a?(Tire::Results::Item) ? protip.best_stat.name : protip.best_stat[0]
320+
def blur_protips?
321+
params[:show_all].nil? && !signed_in?
322+
end
323+
324+
def followings_fragment_cache_key(user_id)
325+
['v1', 'followings_panel', user_id]
327326
end
328327

329328
def protip_networks(protip)
@@ -354,10 +353,6 @@ def protip_display_mode
354353
mobile_device? ? "fullpage" : "popup"
355354
end
356355

357-
def views_stat_value(protip)
358-
best_stat_value(protip) * Protip::COUNTABLE_VIEWS_CHUNK
359-
end
360-
361356
def display_protip_stats?(protip)
362357
stat_name = best_stat_name(protip)
363358
# if stat is present, and the stat we're displaying is views over 50, display.

app/helpers/users_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def users_image_tag(user, options = {})
3030

3131
#TODO Remove
3232
def users_image_path(user)
33+
return ''
3334
user.avatar.url
3435
end
3536

app/mailers/mail_preview.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class MailPreview < MailView
2+
USERNAME = 'just3ws'
3+
4+
def popular_protips
5+
from = 60.days.ago
6+
to = 0.days.ago
7+
user = User.with_username(USERNAME)
8+
protips = ProtipMailer::Queries.popular_protips(from, to)
9+
ProtipMailer.popular_protips(user, protips, from, to).deliver
10+
end
11+
12+
def old_weekly_digest
13+
WeeklyDigestMailer.weekly_digest(USERNAME)
14+
end
15+
end

app/mailers/protip_mailer.rb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
class ProtipMailer < ActionMailer::Base
2+
include ActionView::Helpers::TextHelper
3+
4+
add_template_helper(UsersHelper)
5+
add_template_helper(ProtipsHelper)
6+
add_template_helper(ApplicationHelper)
7+
8+
default_url_options[:host] = 'coderwall.com'
9+
default_url_options[:only_path] = false
10+
default from: '"Coderwall" <support@coderwall.com>'
11+
12+
SPAM_NOTICE = "You're receiving this email because you signed up for Coderwall. We hate spam and make an effort to keep notifications to a minimum. To change your notification preferences, you can update your email settings here: http://coderwall.com/settings#email or immediately unsubscribe by clicking this link %unsubscribe_url%"
13+
STARS = {
14+
protip_upvotes: 'pro tip upvotes',
15+
followers: 'followers',
16+
endorsements: 'endorsements',
17+
protips_count: 'protips'
18+
}
19+
ACTIVITY_SUBJECT_PREFIX = '[Coderwall]'
20+
21+
#################################################################################
22+
def popular_protips(user, protips, from, to)
23+
fail 'Protips are required.' if protips.nil? || protips.empty?
24+
fail 'User is required.' unless user
25+
fail 'From date is required.' unless from
26+
fail 'To date is required.' unless to
27+
28+
headers['X-Mailgun-Campaign-Id'] = 'protip_mailer-popular_protips'
29+
30+
@user = user
31+
@protips = protips
32+
@team, @job = get_team_and_job_for(@user)
33+
@issue = campaign_params
34+
35+
stars = @user.following_users.where('last_request_at > ?', 1.month.ago)
36+
@star_stat = star_stat_for_this_week
37+
@star_stat_string = STARS[@star_stat]
38+
39+
@most = star_stats(stars).sort_by do |star|
40+
-star[@star_stat]
41+
end.first
42+
@most = nil if @most && (@most[@star_stat] <= 0)
43+
44+
mail(to: @user.email, subject: "#{ACTIVITY_SUBJECT_PREFIX} Popular Protips on Coderwall")
45+
rescue Exception => ex
46+
abort_delivery(ex)
47+
end
48+
#################################################################################
49+
50+
def abort_delivery(ex)
51+
Rails.logger.error("[ProtipMailer.popular_protips] Aborted email '#{ex}' >>\n#{ex.backtrace.join("\n ")}")
52+
end
53+
54+
def campaign_params
55+
{
56+
utm_campaign: 'coderwall-popular_protips',
57+
utm_content: Date.today.midnight,
58+
utm_medium: 'email'
59+
}
60+
end
61+
62+
def star_stat_for_this_week
63+
STARS.keys[week_of_the_month % 4]
64+
end
65+
66+
def star_stats(stars, since=1.week.ago)
67+
stars.collect { |star| star.activity_stats(since, true) }.each_with_index.map { |stat, index| stat.merge(user: stars[index]) }
68+
end
69+
70+
def week_of_the_month
71+
Date.today.cweek - Date.today.at_beginning_of_month.cweek
72+
end
73+
74+
def get_team_and_job_for(user)
75+
if user.team.try(:hiring?)
76+
[user.team, user.team.jobs.sample]
77+
else
78+
teams = teams_for_user(user)
79+
teams.each do |team|
80+
best_job = team.best_positions_for(user).detect do |job|
81+
job.team_document_id == user.team_document_id
82+
end
83+
return [team, best_job] unless best_job.nil?
84+
end
85+
end
86+
[nil, nil]
87+
end
88+
89+
def teams_for_user(user)
90+
Team.most_relevant_featured_for(user).select do |team|
91+
team.hiring?
92+
end
93+
end
94+
95+
module Queries
96+
def self.popular_protips(from, to)
97+
search_results = ProtipMailer::Queries.search_for_popular_protips(from, to)
98+
public_ids = search_results.map { |protip| protip['public_id'] }
99+
100+
Protip.eager_load(:user, :comments).where('public_id in (?)', public_ids)
101+
end
102+
103+
def self.search_for_popular_protips(from, to, max_results=10)
104+
url = "#{ENV['ELASTICSEARCH_URL']}/#{ENV['ELASTICSEARCH_PROTIPS_INDEX']}/_search"
105+
query = {
106+
'query' => {
107+
'bool' => {
108+
'must' => [
109+
{
110+
'range' => {
111+
'protip.created_at' => {
112+
'from' => from.strftime('%Y-%m-%d'),
113+
'to' => to.strftime('%Y-%m-%d')
114+
}
115+
}
116+
}
117+
]
118+
}
119+
},
120+
'size' => max_results,
121+
'sort' => [
122+
{
123+
'protip.popular_score' => {
124+
'order' => 'desc'
125+
}
126+
}
127+
]
128+
}
129+
response = RestClient.post(url, MultiJson.dump(query), content_type: :json, accept: :json)
130+
# TODO: check for response code
131+
MultiJson.load(response.body)['hits']['hits'].map { |protip| protip['_source'] }
132+
end
133+
end
134+
end

app/mailers/weekly_digest_mailer.rb

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
class WeeklyDigestMailer < ActionMailer::Base
44
include ActionView::Helpers::TextHelper
55
include ActiveSupport::Benchmarkable
6+
67
add_template_helper(UsersHelper)
78
add_template_helper(ProtipsHelper)
89
add_template_helper(ApplicationHelper)
@@ -17,54 +18,52 @@ def self.queue
1718

1819
SPAM_NOTICE = "You're receiving this email because you signed up for Coderwall. We hate spam and make an effort to keep notifications to a minimum. To change your notification preferences, you can update your email settings here: http://coderwall.com/settings#email or immediately unsubscribe by clicking this link %unsubscribe_url%"
1920

20-
2121
WEEKLY_DIGEST_EVENT = 'weekly_digest'
2222
ACTIVITY_SUBJECT_PREFIX = "[Coderwall]"
2323

24+
#################################################################################
2425
def weekly_digest(username)
2526
headers['X-Mailgun-Variables'] = {email_type: WEEKLY_DIGEST_EVENT}.to_json
2627
track_campaign(WEEKLY_DIGEST_EVENT)
2728

2829
@user = User.find_by_username(username)
2930
since = [@user.last_request_at || Time.at(0), 1.week.ago].min
3031

31-
benchmark "digest:stats" do
32-
@stats = @user.activity_stats(since, true).sort_by { |stat, count| -(count || 0) }
33-
end
32+
# benchmark "digest:stats" do
33+
@stats = @user.activity_stats(since, true).sort_by { |stat, count| -(count || 0) }
3434

3535
#@networks = @user.following_networks.most_protips
3636
@user.touch(:last_email_sent)
3737
@issue = weekly_digest_utm
38-
benchmark "digest:protips" do
39-
@protips = protips_for(@user, 6)
40-
end
38+
#
39+
# benchmark "digest:protips" do
40+
@protips = protips_for(@user, 6)
4141

4242
abort_delivery if @protips.blank? || @protips.count < 4
4343

44-
benchmark "digest:stars" do
45-
@stars = @user.following_users.where('last_request_at > ?', 1.month.ago)
46-
@star_stat = star_stat_for_this_week
47-
@star_stat_string = STARS[@star_stat]
48-
@most = star_stats(@stars).sort_by { |star| -star[@star_stat] }.first
49-
@most = nil if @most && (@most[@star_stat] <= 0)
50-
end
44+
# benchmark "digest:stars" do
45+
stars = @user.following_users.where('last_request_at > ?', 1.month.ago)
46+
@star_stat = star_stat_for_this_week
47+
@star_stat_string = STARS[@star_stat]
48+
@most = star_stats(stars).sort_by { |star| -star[@star_stat] }.first
49+
@most = nil if @most && (@most[@star_stat] <= 0)
5150

52-
benchmark "digest:team" do
53-
@team, @job = get_team_and_job_for(@user)
54-
end
51+
# benchmark "digest:team" do
52+
@team, @job = get_team_and_job_for(@user)
5553

56-
benchmark "digest:mark_sent" do
57-
mark_sent(@job) unless @job.nil?
58-
end
54+
# benchmark "digest:mark_sent" do
55+
mark_sent(@job) unless @job.nil?
5956

6057
mail to: @user.email, subject: "#{ACTIVITY_SUBJECT_PREFIX} #{weekly_digest_subject_for(@user, @stats, @most)}"
58+
6159
rescue Exception => e
62-
abort_delivery(e.message)
60+
abort_delivery(e)
6361
end
62+
#################################################################################
6463

65-
def abort_delivery(message="")
64+
def abort_delivery(error=nil)
6665
#self.perform_deliveries = false
67-
Rails.logger.error "sending bad email:#{message}"
66+
Rails.logger.error "sending bad email:#{error.message}"
6867
end
6968

7069
private
@@ -91,7 +90,7 @@ def protips_for(user, how_many=6)
9190
protips = Protip.trending_for_user(user).first(how_many)
9291
protips += Protip.trending.first(how_many-protips.count) if protips.count < how_many
9392
else
94-
protips =Protip.hawt_for_user(user).results.first(how_many)
93+
protips = Protip.hawt_for_user(user).results.first(how_many)
9594
protips +=Protip.hawt.results.first(how_many) if protips.count < how_many
9695
end
9796
protips

0 commit comments

Comments
 (0)