1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# frozen_string_literal: true
module Namespaces
class InProductMarketingEmailsService
TRACKS = {
create: {
interval_days: [1, 5, 10],
completed_actions: [:created],
incomplete_actions: [:git_write]
},
team_short: {
interval_days: [1],
completed_actions: [:git_write],
incomplete_actions: [:user_added]
},
trial_short: {
interval_days: [2],
completed_actions: [:git_write],
incomplete_actions: [:trial_started]
},
admin_verify: {
interval_days: [3],
completed_actions: [:git_write],
incomplete_actions: [:pipeline_created]
},
verify: {
interval_days: [4, 8, 13],
completed_actions: [:git_write],
incomplete_actions: [:pipeline_created]
},
trial: {
interval_days: [1, 5, 10],
completed_actions: [:git_write, :pipeline_created],
incomplete_actions: [:trial_started]
},
team: {
interval_days: [1, 5, 10],
completed_actions: [:git_write, :pipeline_created, :trial_started],
incomplete_actions: [:user_added]
},
experience: {
interval_days: [30],
completed_actions: [:created, :git_write],
incomplete_actions: []
}
}.freeze
def self.email_count_for_track(track)
interval_days = TRACKS.dig(track.to_sym, :interval_days)
interval_days&.count || 0
end
def self.send_for_all_tracks_and_intervals
TRACKS.each_key do |track|
TRACKS[track][:interval_days].each do |interval|
new(track, interval).execute
end
end
end
def initialize(track, interval)
@track = track
@interval = interval
@sent_email_records = ::Users::InProductMarketingEmailRecords.new
end
def execute
raise ArgumentError, "Track #{track} not defined" unless TRACKS.key?(track)
groups_for_track.each_batch do |groups|
groups.each do |group|
send_email_for_group(group)
end
end
end
private
attr_reader :track, :interval, :sent_email_records
def send_email(user, group)
NotificationService.new.in_product_marketing(user.id, group.id, track, series)
end
def send_email_for_group(group)
users_for_group(group).each do |user|
if can_perform_action?(user, group)
send_email(user, group)
sent_email_records.add(user, track: track, series: series)
end
end
sent_email_records.save!
end
def groups_for_track
onboarding_progress_scope = OnboardingProgress
.completed_actions_with_latest_in_range(completed_actions, range)
.incomplete_actions(incomplete_actions)
# Filtering out sub-groups is a temporary fix to prevent calling
# `.root_ancestor` on groups that are not root groups.
# See https://gitlab.com/groups/gitlab-org/-/epics/5594 for more information.
Group
.top_most
.with_onboarding_progress
.merge(onboarding_progress_scope)
.merge(subscription_scope)
end
def subscription_scope
{}
end
# rubocop: disable CodeReuse/ActiveRecord
def users_for_group(group)
group.users
.where(email_opted_in: true)
.merge(Users::InProductMarketingEmail.without_track_and_series(track, series))
end
# rubocop: enable CodeReuse/ActiveRecord
def can_perform_action?(user, group)
case track
when :create, :verify
user.can?(:create_projects, group)
when :trial, :trial_short
user.can?(:start_trial, group)
when :team, :team_short
user.can?(:admin_group_member, group)
when :admin_verify
user.can?(:admin_group, group)
when :experience
true
end
end
def completed_actions
TRACKS[track][:completed_actions]
end
def range
date = (interval + 1).days.ago
date.beginning_of_day..date.end_of_day
end
def incomplete_actions
TRACKS[track][:incomplete_actions]
end
def series
TRACKS[track][:interval_days].index(interval)
end
end
end
Namespaces::InProductMarketingEmailsService.prepend_mod
|