Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
21b9c2fc
Commit
21b9c2fc
authored
Jan 28, 2021
by
Alex Buijs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add in product marketing emails service
For sending in product marketing emails
parent
449d67ac
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
275 additions
and
0 deletions
+275
-0
app/services/namespaces/in_product_marketing_emails_service.rb
...ervices/namespaces/in_product_marketing_emails_service.rb
+112
-0
app/services/notification_service.rb
app/services/notification_service.rb
+4
-0
spec/services/namespaces/in_product_marketing_emails_service_spec.rb
...es/namespaces/in_product_marketing_emails_service_spec.rb
+159
-0
No files found.
app/services/namespaces/in_product_marketing_emails_service.rb
0 → 100644
View file @
21b9c2fc
# frozen_string_literal: true
module
Namespaces
class
InProductMarketingEmailsService
include
Gitlab
::
Experimentation
::
GroupTypes
INTERVAL_DAYS
=
[
1
,
5
,
10
].
freeze
TRACKS
=
{
create: :git_write
,
verify: :pipeline_created
,
trial: :trial_started
,
team: :user_added
}.
freeze
def
self
.
send_for_all_tracks_and_intervals
TRACKS
.
each_key
do
|
track
|
INTERVAL_DAYS
.
each
do
|
interval
|
new
(
track
,
interval
).
execute
end
end
end
def
initialize
(
track
,
interval
)
@track
=
track
@interval
=
interval
@sent_email_user_ids
=
[]
end
def
execute
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_user_ids
def
send_email_for_group
(
group
)
experiment_enabled_for_group
=
experiment_enabled_for_group?
(
group
)
experiment_add_group
(
group
,
experiment_enabled_for_group
)
return
unless
experiment_enabled_for_group
users_for_group
(
group
).
each
do
|
user
|
send_email
(
user
,
group
)
if
can_perform_action?
(
user
,
group
)
end
end
def
experiment_enabled_for_group?
(
group
)
Gitlab
::
Experimentation
.
in_experiment_group?
(
:in_product_marketing_emails
,
subject:
group
)
end
def
experiment_add_group
(
group
,
experiment_enabled_for_group
)
variant
=
experiment_enabled_for_group
?
GROUP_EXPERIMENTAL
:
GROUP_CONTROL
Experiment
.
add_group
(
:in_product_marketing_emails
,
variant:
variant
,
group:
group
)
end
# rubocop: disable CodeReuse/ActiveRecord
def
groups_for_track
onboarding_progress_scope
=
OnboardingProgress
.
completed_actions_with_latest_in_range
(
completed_actions
,
range
)
.
incomplete_actions
(
incomplete_action
)
Group
.
joins
(
:onboarding_progress
).
merge
(
onboarding_progress_scope
)
end
def
users_for_group
(
group
)
group
.
users
.
where
(
email_opted_in:
true
)
.
where
.
not
(
id:
sent_email_user_ids
)
end
# rubocop: enable CodeReuse/ActiveRecord
def
can_perform_action?
(
user
,
group
)
case
track
when
:create
user
.
can?
(
:create_projects
,
group
)
when
:verify
user
.
can?
(
:create_projects
,
group
)
when
:trial
user
.
can?
(
:start_trial
,
group
)
when
:team
user
.
can?
(
:admin_group_member
,
group
)
else
raise
NotImplementedError
,
"No ability defined for track
#{
track
}
"
end
end
def
send_email
(
user
,
group
)
NotificationService
.
new
.
in_product_marketing
(
user
.
id
,
group
.
id
,
track
,
series
)
sent_email_user_ids
<<
user
.
id
end
def
completed_actions
index
=
TRACKS
.
keys
.
index
(
track
)
index
==
0
?
[
:created
]
:
TRACKS
.
values
[
0
..
index
-
1
]
end
def
range
(
interval
+
1
).
days
.
ago
.
beginning_of_day
..
(
interval
+
1
).
days
.
ago
.
end_of_day
end
def
incomplete_action
TRACKS
[
track
]
end
def
series
INTERVAL_DAYS
.
index
(
interval
)
end
end
end
app/services/notification_service.rb
View file @
21b9c2fc
...
...
@@ -664,6 +664,10 @@ class NotificationService
end
end
def
in_product_marketing
(
user_id
,
group_id
,
track
,
series
)
mailer
.
in_product_marketing_email
(
user_id
,
group_id
,
track
,
series
).
deliver_later
end
protected
def
new_resource_email
(
target
,
method
)
...
...
spec/services/namespaces/in_product_marketing_emails_service_spec.rb
0 → 100644
View file @
21b9c2fc
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Namespaces
::
InProductMarketingEmailsService
,
'#execute'
do
subject
(
:execute_service
)
{
described_class
.
new
(
track
,
interval
).
execute
}
let
(
:track
)
{
:create
}
let
(
:interval
)
{
1
}
let
(
:previous_action_completed_at
)
{
2
.
days
.
ago
.
middle_of_day
}
let
(
:current_action_completed_at
)
{
nil
}
let
(
:experiment_enabled
)
{
true
}
let
(
:user_can_perform_current_track_action
)
{
true
}
let
(
:actions_completed
)
{
{
created_at:
previous_action_completed_at
,
git_write_at:
current_action_completed_at
}
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:user
)
{
create
(
:user
,
email_opted_in:
true
)
}
before
do
create
(
:onboarding_progress
,
namespace:
group
,
**
actions_completed
)
group
.
add_developer
(
user
)
stub_experiment_for_subject
(
in_product_marketing_emails:
experiment_enabled
)
allow
(
Ability
).
to
receive
(
:allowed?
).
with
(
user
,
anything
,
anything
).
and_return
(
user_can_perform_current_track_action
)
allow
(
Notify
).
to
receive
(
:in_product_marketing_email
).
and_return
(
double
(
deliver_later:
nil
))
end
RSpec
::
Matchers
.
define
:send_in_product_marketing_email
do
|*
args
|
match
do
expect
(
Notify
).
to
have_received
(
:in_product_marketing_email
).
with
(
*
args
).
once
end
match_when_negated
do
expect
(
Notify
).
not_to
have_received
(
:in_product_marketing_email
)
end
end
context
'for each track and series with the right conditions'
do
using
RSpec
::
Parameterized
::
TableSyntax
where
(
:track
,
:interval
,
:actions_completed
)
do
:create
|
1
|
{
created_at:
2
.
days
.
ago
.
middle_of_day
}
:create
|
5
|
{
created_at:
6
.
days
.
ago
.
middle_of_day
}
:create
|
10
|
{
created_at:
11
.
days
.
ago
.
middle_of_day
}
:verify
|
1
|
{
created_at:
2
.
days
.
ago
.
middle_of_day
,
git_write_at:
2
.
days
.
ago
.
middle_of_day
}
:verify
|
5
|
{
created_at:
6
.
days
.
ago
.
middle_of_day
,
git_write_at:
6
.
days
.
ago
.
middle_of_day
}
:verify
|
10
|
{
created_at:
11
.
days
.
ago
.
middle_of_day
,
git_write_at:
11
.
days
.
ago
.
middle_of_day
}
:trial
|
1
|
{
created_at:
2
.
days
.
ago
.
middle_of_day
,
git_write_at:
2
.
days
.
ago
.
middle_of_day
,
pipeline_created_at:
2
.
days
.
ago
.
middle_of_day
}
:trial
|
5
|
{
created_at:
6
.
days
.
ago
.
middle_of_day
,
git_write_at:
6
.
days
.
ago
.
middle_of_day
,
pipeline_created_at:
6
.
days
.
ago
.
middle_of_day
}
:trial
|
10
|
{
created_at:
11
.
days
.
ago
.
middle_of_day
,
git_write_at:
11
.
days
.
ago
.
middle_of_day
,
pipeline_created_at:
11
.
days
.
ago
.
middle_of_day
}
:team
|
1
|
{
created_at:
2
.
days
.
ago
.
middle_of_day
,
git_write_at:
2
.
days
.
ago
.
middle_of_day
,
pipeline_created_at:
2
.
days
.
ago
.
middle_of_day
,
trial_started_at:
2
.
days
.
ago
.
middle_of_day
}
:team
|
5
|
{
created_at:
6
.
days
.
ago
.
middle_of_day
,
git_write_at:
6
.
days
.
ago
.
middle_of_day
,
pipeline_created_at:
6
.
days
.
ago
.
middle_of_day
,
trial_started_at:
6
.
days
.
ago
.
middle_of_day
}
:team
|
10
|
{
created_at:
11
.
days
.
ago
.
middle_of_day
,
git_write_at:
11
.
days
.
ago
.
middle_of_day
,
pipeline_created_at:
11
.
days
.
ago
.
middle_of_day
,
trial_started_at:
11
.
days
.
ago
.
middle_of_day
}
end
with_them
do
it
{
is_expected
.
to
send_in_product_marketing_email
(
user
.
id
,
group
.
id
,
track
,
described_class
::
INTERVAL_DAYS
.
index
(
interval
))
}
end
end
context
'when initialized with a different track'
do
let
(
:track
)
{
:verify
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
context
'when the previous track actions have been completed'
do
let
(
:current_action_completed_at
)
{
2
.
days
.
ago
.
middle_of_day
}
it
{
is_expected
.
to
send_in_product_marketing_email
(
user
.
id
,
group
.
id
,
:verify
,
0
)
}
end
end
context
'when initialized with a different interval'
do
let
(
:interval
)
{
5
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
context
'when the previous track action was completed within the intervals range'
do
let
(
:previous_action_completed_at
)
{
6
.
days
.
ago
.
middle_of_day
}
it
{
is_expected
.
to
send_in_product_marketing_email
(
user
.
id
,
group
.
id
,
:create
,
1
)
}
end
end
describe
'experimentation'
do
context
'when the experiment is enabled'
do
it
'adds the group as an experiment subject in the experimental group'
do
expect
(
Experiment
).
to
receive
(
:add_group
)
.
with
(
:in_product_marketing_emails
,
variant: :experimental
,
group:
group
)
execute_service
end
end
context
'when the experiment is disabled'
do
let
(
:experiment_enabled
)
{
false
}
it
'adds the group as an experiment subject in the control group'
do
expect
(
Experiment
).
to
receive
(
:add_group
)
.
with
(
:in_product_marketing_emails
,
variant: :control
,
group:
group
)
execute_service
end
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
end
end
context
'when the previous track action is not yet completed'
do
let
(
:previous_action_completed_at
)
{
nil
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
end
context
'when the previous track action is completed outside the intervals range'
do
let
(
:previous_action_completed_at
)
{
3
.
days
.
ago
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
end
context
'when the current track action is completed'
do
let
(
:current_action_completed_at
)
{
Time
.
current
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
end
context
"when the user cannot perform the current track's action"
do
let
(
:user_can_perform_current_track_action
)
{
false
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
end
context
'when the user has not opted into marketing emails'
do
let
(
:user
)
{
create
(
:user
,
email_opted_in:
false
)
}
it
{
is_expected
.
not_to
send_in_product_marketing_email
}
end
context
'when the user has already received a marketing email as part of another group'
do
before
do
other_group
=
create
(
:group
)
other_group
.
add_developer
(
user
)
create
(
:onboarding_progress
,
namespace:
other_group
,
created_at:
previous_action_completed_at
,
git_write_at:
current_action_completed_at
)
end
# For any group Notify is called exactly once
it
{
is_expected
.
to
send_in_product_marketing_email
(
user
.
id
,
anything
,
:create
,
0
)
}
end
context
'when invoked with a non existing track'
do
let
(
:track
)
{
:foo
}
before
do
stub_const
(
"
#{
described_class
}
::TRACKS"
,
{
foo: :git_write
})
end
it
{
expect
{
subject
}.
to
raise_error
(
NotImplementedError
,
'No ability defined for track foo'
)
}
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment