Commit 9e6c4d65 authored by Imre Farkas's avatar Imre Farkas

Merge branch 'add-ease-score-onboarding-email' into 'master'

Add ease score onboarding in product marketing email

See merge request gitlab-org/gitlab!61347
parents 633eb4fd 05afe03a
......@@ -16,7 +16,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController
def track_click
if Gitlab.com?
message = Gitlab::Email::Message::InProductMarketing.for(@track).new(group: group, series: @series)
message = Gitlab::Email::Message::InProductMarketing.for(@track).new(group: group, user: current_user, series: @series)
data = {
namespace_id: group.id,
......@@ -58,8 +58,9 @@ class Groups::EmailCampaignsController < Groups::ApplicationController
@series = params[:series]&.to_i
track_valid = @track.in?(Namespaces::InProductMarketingEmailsService::TRACKS.keys)
series_valid = @series.in?(0..Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.size - 1)
return render_404 unless track_valid
render_404 unless track_valid && series_valid
series_valid = @series.in?(0..Namespaces::InProductMarketingEmailsService::TRACKS[@track][:interval_days].size - 1)
render_404 unless series_valid
end
end
......@@ -14,8 +14,9 @@ module Emails
def in_product_marketing_email(recipient_id, group_id, track, series)
group = Group.find(group_id)
email = User.find(recipient_id).notification_email_for(group)
@message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, series: series)
user = User.find(recipient_id)
email = user.notification_email_for(group)
@message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, user: user, series: series)
mail_to(to: email, subject: @message.subject_line)
end
......
......@@ -85,6 +85,10 @@ class OnboardingProgress < ApplicationRecord
end
end
def number_of_completed_actions
attributes.extract!(*ACTIONS.map { |action| self.class.column_name(action).to_s }).compact!.size
end
private
def namespace_is_root_namespace
......
......@@ -18,7 +18,8 @@ module Users
create: 0,
verify: 1,
trial: 2,
team: 3
team: 3,
experience: 4
}, _suffix: true
scope :without_track_and_series, -> (track, series) do
......
......@@ -4,17 +4,37 @@ 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
create: {
interval_days: [1, 5, 10],
completed_actions: [:created],
incomplete_actions: [:git_write]
},
verify: {
interval_days: [1, 5, 10],
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.send_for_all_tracks_and_intervals
TRACKS.each_key do |track|
INTERVAL_DAYS.each do |interval|
TRACKS[track][:interval_days].each do |interval|
new(track, interval).execute
end
end
......@@ -69,7 +89,7 @@ module Namespaces
def groups_for_track
onboarding_progress_scope = OnboardingProgress
.completed_actions_with_latest_in_range(completed_actions, range)
.incomplete_actions(incomplete_action)
.incomplete_actions(incomplete_actions)
# Filtering out sub-groups is a temporary fix to prevent calling
# `.root_ancestor` on groups that are not root groups.
......@@ -103,6 +123,8 @@ module Namespaces
user.can?(:start_trial, group)
when :team
user.can?(:admin_group_member, group)
when :experience
true
end
end
......@@ -111,8 +133,7 @@ module Namespaces
end
def completed_actions
index = TRACKS.keys.index(track)
index == 0 ? [:created] : TRACKS.values[0..index - 1]
TRACKS[track][:completed_actions]
end
def range
......@@ -120,12 +141,12 @@ module Namespaces
date.beginning_of_day..date.end_of_day
end
def incomplete_action
TRACKS[track]
def incomplete_actions
TRACKS[track][:incomplete_actions]
end
def series
INTERVAL_DAYS.index(interval)
TRACKS[track][:interval_days].index(interval)
end
def save_tracked_emails!
......
......@@ -184,9 +184,32 @@
- @message.body_line2&.tap do |line|
%p{ style: "margin: 0 0 20px 0;" }
= line.html_safe
%tr
%td{ align: "center", style: "padding: 10px 20px 80px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif;" }
.cta_link= @message.cta_link
- if @message.cta_text
%tr
%td{ align: "center", style: "padding: 10px 20px 80px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif;" }
.cta_link= @message.cta_link
- else
%tr
%td{ style: "padding: 10px 20px 10px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; color:#000000; font-size: 16px; line-height: 20px;" }
%table{ border: "0", cellpadding: "0", cellspacing: "0", width: "100%", style: "width: 100%; min-width: 100%;" }
%tr
%td{ width: "50%", style: "width: 50%; min-width: 50%; color: #000000; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; font-size: 16px; line-height: 100%; padding-bottom: 16px; text-align: left;", align: "left" }
= @message.feedback_ratings(1)
%td{ width: "50%", style: "width: 50%; min-width: 50%; color: #000000; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; font-size: 16px; line-height: 100%; padding-bottom: 16px; text-align: right;", align: "right" }
= @message.feedback_ratings(5)
%tr
%td{ align: "center", style: "padding: 10px 1px 30px 1px;" }
%table{ align: "center", cellpadding: "5", cellspacing: "0", width: "100%", style: "width: 100%; min-width: 100%; border: 1px solid #dae0ea; border-radius: 0; min-width: 100%; text-align: center; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; font-size: 16px;" }
%tr
- (1..5).each do |rating|
%td{ height: "54", style: "border-left: 1px solid #dae0ea; padding-bottom: 0; width: 9% !important;", width: "9%" }
%a{ href: @message.feedback_link(rating), style: "color: #424242; display: block; text-decoration: none;" }
%span{ height: "54", style: "display: block; font-size: 18px; height: 22px; line-height: 22px; padding: 16px 0; width: 100%; text-decoration: none;" }
= rating
%tr
%td{ style: "padding: 10px 20px 30px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; color:#000000; font-size: 18px; line-height: 24px;" }
%p{ style: "margin: 0 0 50px 0;" }
= @message.feedback_thanks
%tr{ style: "background-color: #ffffff;" }
%td{ align: "center", style: "padding:75px 20px 25px;" }
= about_link('gitlab_logo.png', 80)
......
......@@ -8,10 +8,19 @@
<%= @message.body_line2 %>
<% if @message.cta_text %>
<%= @message.cta_link %>
<% else %>
<% (1..5).each do |rating| %>
<%= "#{rating} - #{@message.feedback_ratings(rating).upcase} - #{@message.feedback_link(rating)}" %>
<% end %>
<%= @message.feedback_thanks %>
<% end %>
......
---
title: Add ease score onboarding in-product marketing email
merge_request: 61347
author:
type: changed
---
key_path: counts.in_product_marketing_email_experience_0_sent
name: "count_sent_first_email_of_the_experience_track_for_in_product_marketing_emails"
description: Total sent emails of the experience track's first email
product_section:
product_stage: growth
product_group: group::activation
product_category: onboarding
value_type: number
status: implemented
milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61347
time_frame: all
data_source: database
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
......@@ -2446,6 +2446,18 @@ Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_experience_0_sent`
Total sent emails of the experience track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml)
Group: `group::activation`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_review_folder`
Missing description
......
# frozen_string_literal: true
class SurveyResponsesController < ApplicationController
SURVEY_RESPONSE_SCHEMA_URL = 'iglu:com.gitlab/survey_response/jsonschema/1-0-0'
SURVEY_RESPONSE_SCHEMA_URL = 'iglu:com.gitlab/survey_response/jsonschema/1-0-1'
CALENDLY_INVITE_LINK = 'https://calendly.com/mkarampalas/gitlab-user-onboarding-research'
before_action :track_response, only: :index
......@@ -27,7 +27,8 @@ class SurveyResponsesController < ApplicationController
email: params[:email],
name: params[:name],
username: params[:username],
response: params[:response]
response: params[:response],
onboarding_progress: to_number(params[:onboarding_progress])
}.compact
context = SnowplowTracker::SelfDescribingJson.new(SURVEY_RESPONSE_SCHEMA_URL, data)
......
......@@ -17,7 +17,8 @@ RSpec.describe SurveyResponsesController do
instance_id: 'foo',
response: 'response text',
bla: 'bar',
show_invite_link: 'true'
show_invite_link: 'true',
onboarding_progress: '4'
}
end
......@@ -43,11 +44,14 @@ RSpec.describe SurveyResponsesController do
data:
{
survey_id: 123,
response: 'response text'
response: 'response text',
onboarding_progress: 4
}
}
]
)
match_snowplow_context_schema(schema_path: 'survey_response_schema', context: { response: 'response text', survey_id: 123, onboarding_progress: 4 } )
end
end
end
......
......@@ -6,10 +6,8 @@ module Gitlab
module InProductMarketing
UnknownTrackError = Class.new(StandardError)
TRACKS = [:create, :verify, :team, :trial].freeze
def self.for(track)
raise UnknownTrackError unless TRACKS.include?(track)
raise UnknownTrackError unless Namespaces::InProductMarketingEmailsService::TRACKS.key?(track)
"Gitlab::Email::Message::InProductMarketing::#{track.to_s.classify}".constantize
end
......
......@@ -10,10 +10,11 @@ module Gitlab
attr_accessor :format
def initialize(group:, series:, format: :html)
def initialize(group:, user:, series:, format: :html)
raise ArgumentError, "Only #{total_series} series available for this track." unless series.between?(0, total_series - 1)
@group = group
@user = user
@series = series
@format = format
end
......@@ -103,11 +104,7 @@ module Gitlab
protected
attr_reader :group, :series
def total_series
3
end
attr_reader :group, :user, :series
private
......@@ -115,6 +112,10 @@ module Gitlab
self.class.name.demodulize.downcase.to_sym
end
def total_series
Namespaces::InProductMarketingEmailsService::TRACKS[track][:interval_days].size
end
def unsubscribe_com
[
s_('InProductMarketing|If you no longer wish to receive marketing emails from us,'),
......
# frozen_string_literal: true
module Gitlab
module Email
module Message
module InProductMarketing
class Experience < Base
include Gitlab::Utils::StrongMemoize
EASE_SCORE_SURVEY_ID = 1
def subject_line
s_('InProductMarketing|Do you have a minute?')
end
def tagline
end
def title
s_('InProductMarketing|We want your GitLab experience to be great')
end
def subtitle
s_('InProductMarketing|Take this 1-question survey!')
end
def body_line1
s_('InProductMarketing|%{strong_start}Overall, how difficult or easy was it to get started with GitLab?%{strong_end}').html_safe % strong_options
end
def body_line2
s_('InProductMarketing|Click on the number below that corresponds with your answer — 1 being very difficult, 5 being very easy.')
end
def cta_text
end
def feedback_link(rating)
params = {
onboarding_progress: onboarding_progress,
response: rating,
show_invite_link: show_invite_link,
survey_id: EASE_SCORE_SURVEY_ID
}
"#{Gitlab::COM_URL}/-/survey_responses?#{params.to_query}"
end
def feedback_ratings(rating)
[
s_('InProductMarketing|Very difficult'),
s_('InProductMarketing|Difficult'),
s_('InProductMarketing|Neutral'),
s_('InProductMarketing|Easy'),
s_('InProductMarketing|Very easy')
][rating - 1]
end
def feedback_thanks
s_('InProductMarketing|Feedback from users like you really improves our product. Thanks for your help!')
end
private
def onboarding_progress
strong_memoize(:onboarding_progress) do
group.onboarding_progress.number_of_completed_actions
end
end
def show_invite_link
strong_memoize(:show_invite_link) do
group.member_count > 1 && group.max_member_access_for_user(user) >= GroupMember::DEVELOPER && user.preferred_language == 'en'
end
end
end
end
end
end
end
......@@ -852,17 +852,16 @@ module Gitlab
sent_emails = count(Users::InProductMarketingEmail.group(:track, :series))
clicked_emails = count(Users::InProductMarketingEmail.where.not(cta_clicked_at: nil).group(:track, :series))
series_amount = Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.count
Users::InProductMarketingEmail.tracks.keys.each_with_object({}) do |track, result|
# rubocop: enable UsageData/LargeTable:
series_amount = Namespaces::InProductMarketingEmailsService::TRACKS[track.to_sym][:interval_days].count
0.upto(series_amount - 1).map do |series|
# When there is an error with the query and it's not the Hash we expect, we return what we got from `count`.
sent_count = sent_emails.is_a?(Hash) ? sent_emails.fetch([track, series], 0) : sent_emails
clicked_count = clicked_emails.is_a?(Hash) ? clicked_emails.fetch([track, series], 0) : clicked_emails
result["in_product_marketing_email_#{track}_#{series}_sent"] = sent_count
result["in_product_marketing_email_#{track}_#{series}_cta_clicked"] = clicked_count
result["in_product_marketing_email_#{track}_#{series}_cta_clicked"] = clicked_count unless track == 'experience'
end
end
end
......
......@@ -16917,6 +16917,9 @@ msgstr ""
msgid "InProductMarketing|%{strong_start}Multiple approval roles%{strong_end} — including code owners and required merge approvals"
msgstr ""
msgid "InProductMarketing|%{strong_start}Overall, how difficult or easy was it to get started with GitLab?%{strong_end}"
msgstr ""
msgid "InProductMarketing|*GitLab*, noun: a synonym for efficient teams"
msgstr ""
......@@ -16950,6 +16953,9 @@ msgstr ""
msgid "InProductMarketing|By enabling code owners and required merge approvals the right person will review the right MR. This is a win-win: cleaner code and a more efficient review process."
msgstr ""
msgid "InProductMarketing|Click on the number below that corresponds with your answer — 1 being very difficult, 5 being very easy."
msgstr ""
msgid "InProductMarketing|Code owners and required merge approvals are part of the paid tiers of GitLab. You can start a free 30-day trial of GitLab Ultimate and enable these features in less than 5 minutes with no credit card required."
msgstr ""
......@@ -16962,9 +16968,18 @@ msgstr ""
msgid "InProductMarketing|Did you know teams that use GitLab are far more efficient?"
msgstr ""
msgid "InProductMarketing|Difficult"
msgstr ""
msgid "InProductMarketing|Dig in and create a project and a repo"
msgstr ""
msgid "InProductMarketing|Do you have a minute?"
msgstr ""
msgid "InProductMarketing|Easy"
msgstr ""
msgid "InProductMarketing|Explore GitLab CI/CD"
msgstr ""
......@@ -16977,6 +16992,9 @@ msgstr ""
msgid "InProductMarketing|Facebook"
msgstr ""
msgid "InProductMarketing|Feedback from users like you really improves our product. Thanks for your help!"
msgstr ""
msgid "InProductMarketing|Feel the need for speed?"
msgstr ""
......@@ -17103,6 +17121,9 @@ msgstr ""
msgid "InProductMarketing|Need an alternative to importing?"
msgstr ""
msgid "InProductMarketing|Neutral"
msgstr ""
msgid "InProductMarketing|Our tool brings all the things together"
msgstr ""
......@@ -17142,6 +17163,9 @@ msgstr ""
msgid "InProductMarketing|Streamline code review, know at a glance who's unavailable, communicate in comments or in email and integrate with Slack so everyone's on the same page."
msgstr ""
msgid "InProductMarketing|Take this 1-question survey!"
msgstr ""
msgid "InProductMarketing|Take your first steps with GitLab"
msgstr ""
......@@ -17196,9 +17220,18 @@ msgstr ""
msgid "InProductMarketing|Use GitLab CI/CD"
msgstr ""
msgid "InProductMarketing|Very difficult"
msgstr ""
msgid "InProductMarketing|Very easy"
msgstr ""
msgid "InProductMarketing|We know a thing or two about efficiency and we don't want to keep that to ourselves. Sign up for a free trial of GitLab Ultimate and your teams will be on it from day one."
msgstr ""
msgid "InProductMarketing|We want your GitLab experience to be great"
msgstr ""
msgid "InProductMarketing|What does our value stream timeline look like from product to development to review and production?"
msgstr ""
......
......@@ -3,7 +3,7 @@
"self": {
"vendor": "com.gitlab",
"name": "survey_response",
"version": "1-0-0",
"version": "1-0-1",
"format": "jsonschema"
},
"type": "object",
......@@ -47,6 +47,12 @@
"description": "Username",
"type": ["string", "null"],
"maxLength": 255
},
"onboarding_progress": {
"description": "Onboarding progress",
"type": ["integer", "null"],
"minimum": 0,
"maximum": 2147483647
}
}
}
......@@ -4,12 +4,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
let(:series) { 0 }
let(:test_class) { Gitlab::Email::Message::InProductMarketing::Create }
describe 'initialize' do
subject { test_class.new(group: group, series: series) }
subject { test_class.new(group: group, user: user, series: series) }
context 'when series does not exist' do
let(:series) { 3 }
......@@ -29,13 +30,13 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do
end
describe '#logo_path' do
subject { test_class.new(group: group, series: series).logo_path }
subject { test_class.new(group: group, user: user, series: series).logo_path }
it { is_expected.to eq('mailers/in_product_marketing/create-0.png') }
end
describe '#unsubscribe' do
subject { test_class.new(group: group, series: series).unsubscribe }
subject { test_class.new(group: group, user: user, series: series).unsubscribe }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
......@@ -55,7 +56,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do
end
describe '#cta_link' do
subject(:cta_link) { test_class.new(group: group, series: series).cta_link }
subject(:cta_link) { test_class.new(group: group, user: user, series: series).cta_link }
it 'renders link' do
expect(CGI.unescapeHTML(cta_link)).to include(Gitlab::Routing.url_helpers.group_email_campaigns_url(group, track: :create, series: series))
......@@ -63,7 +64,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do
end
describe '#progress' do
subject { test_class.new(group: group, series: series).progress }
subject { test_class.new(group: group, user: user, series: series).progress }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
......
......@@ -6,8 +6,9 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Create do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
subject(:message) { described_class.new(group: group, series: series)}
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe "public methods" do
where(series: [0, 1, 2])
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Email::Message::InProductMarketing::Experience do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe 'public methods' do
context 'with series 0' do
let(:series) { 0 }
it 'returns value for series', :aggregate_failures do
expect(message.subject_line).to be_present
expect(message.tagline).to be_nil
expect(message.title).to be_present
expect(message.subtitle).to be_present
expect(message.body_line1).to be_present
expect(message.body_line2).to be_present
expect(message.cta_text).to be_nil
end
describe '#feedback_link' do
let(:member_count) { 2 }
let(:user_access) { GroupMember::DEVELOPER }
let(:preferred_language) { 'en' }
before do
allow(message).to receive(:onboarding_progress).and_return(1)
allow(group).to receive(:member_count).and_return(member_count)
allow(group).to receive(:max_member_access_for_user).and_return(user_access)
allow(user).to receive(:preferred_language).and_return(preferred_language)
end
subject do
uri = URI.parse(message.feedback_link(1))
Rack::Utils.parse_query(uri.query).with_indifferent_access[:show_invite_link]
end
it { is_expected.to eq('true') }
context 'with only one member' do
let(:member_count) { 1 }
it { is_expected.to eq('false') }
end
context 'with less than developer access' do
let(:user_access) { GroupMember::GUEST }
it { is_expected.to eq('false') }
end
context 'with preferred language other than English' do
let(:preferred_language) { 'nl' }
it { is_expected.to eq('false') }
end
end
end
end
end
......@@ -6,8 +6,9 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Team do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
subject(:message) { described_class.new(group: group, series: series)}
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe "public methods" do
where(series: [0, 1])
......
......@@ -6,8 +6,9 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Trial do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
subject(:message) { described_class.new(group: group, series: series)}
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe "public methods" do
where(series: [0, 1, 2])
......
......@@ -4,8 +4,9 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Message::InProductMarketing::Verify do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
subject(:message) { described_class.new(group: group, series: series)}
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe "public methods" do
context 'with series 0' do
......
......@@ -1499,7 +1499,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_team_1_sent" => -1,
"in_product_marketing_email_team_1_cta_clicked" => -1,
"in_product_marketing_email_team_2_sent" => -1,
"in_product_marketing_email_team_2_cta_clicked" => -1
"in_product_marketing_email_team_2_cta_clicked" => -1,
"in_product_marketing_email_experience_0_sent" => -1
}
expect(subject).to eq(expected_data)
......@@ -1537,7 +1538,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_team_1_sent" => 0,
"in_product_marketing_email_team_1_cta_clicked" => 0,
"in_product_marketing_email_team_2_sent" => 0,
"in_product_marketing_email_team_2_cta_clicked" => 0
"in_product_marketing_email_team_2_cta_clicked" => 0,
"in_product_marketing_email_experience_0_sent" => 0
}
expect(subject).to eq(expected_data)
......
......@@ -9,6 +9,8 @@ RSpec.describe Emails::InProductMarketing do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let!(:onboarding_progress) { create(:onboarding_progress, namespace: group) }
describe '#in_product_marketing_email' do
using RSpec::Parameterized::TableSyntax
......@@ -45,29 +47,35 @@ RSpec.describe Emails::InProductMarketing do
end
where(:track, :series) do
:create | 0
:create | 1
:create | 2
:verify | 0
:verify | 1
:verify | 2
:trial | 0
:trial | 1
:trial | 2
:team | 0
:team | 1
:team | 2
:create | 0
:create | 1
:create | 2
:verify | 0
:verify | 1
:verify | 2
:trial | 0
:trial | 1
:trial | 2
:team | 0
:team | 1
:team | 2
:experience | 0
end
with_them do
it 'has the correct subject and content' do
message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, series: series)
message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, user: user, series: series)
aggregate_failures do
is_expected.to have_subject(message.subject_line)
is_expected.to have_body_text(message.title)
is_expected.to have_body_text(message.subtitle)
is_expected.to have_body_text(CGI.unescapeHTML(message.cta_link))
if track == :experience
is_expected.to have_body_text(CGI.unescapeHTML(message.feedback_link(1)))
else
is_expected.to have_body_text(CGI.unescapeHTML(message.cta_link))
end
end
end
end
......
......@@ -211,4 +211,26 @@ RSpec.describe OnboardingProgress do
it { is_expected.to eq(:subscription_created_at) }
end
describe '#number_of_completed_actions' do
subject { build(:onboarding_progress, actions.map { |x| { x => Time.current } }.inject(:merge)).number_of_completed_actions }
context '0 completed actions' do
let(:actions) { [:created_at, :updated_at] }
it { is_expected.to eq(0) }
end
context '1 completed action' do
let(:actions) { [:created_at, :subscription_created_at] }
it { is_expected.to eq(1) }
end
context '2 completed actions' do
let(:actions) { [:subscription_created_at, :git_write_at] }
it { is_expected.to eq(2) }
end
end
end
......@@ -9,10 +9,11 @@ RSpec.describe Groups::EmailCampaignsController do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
let(:track) { 'create' }
let(:series) { '0' }
let(:schema) { described_class::EMAIL_CAMPAIGNS_SCHEMA_URL }
let(:subject_line_text) { Gitlab::Email::Message::InProductMarketing.for(track.to_sym).new(group: group, series: series.to_i).subject_line }
let(:subject_line_text) { Gitlab::Email::Message::InProductMarketing.for(track.to_sym).new(group: group, user: user, series: series.to_i).subject_line }
let(:data) do
{
namespace_id: group.id,
......@@ -91,7 +92,7 @@ RSpec.describe Groups::EmailCampaignsController do
describe 'track parameter' do
context 'when valid' do
where(track: Namespaces::InProductMarketingEmailsService::TRACKS.keys)
where(track: Namespaces::InProductMarketingEmailsService::TRACKS.keys.without(:experience))
with_them do
it_behaves_like 'track and redirect'
......@@ -109,7 +110,7 @@ RSpec.describe Groups::EmailCampaignsController do
describe 'series parameter' do
context 'when valid' do
where(series: (0..Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.length - 1).to_a)
where(series: (0..Namespaces::InProductMarketingEmailsService::TRACKS[:create][:interval_days].length - 1).to_a)
with_them do
it_behaves_like 'track and redirect'
......@@ -117,7 +118,7 @@ RSpec.describe Groups::EmailCampaignsController do
end
context 'when invalid' do
where(series: [-1, nil, Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.length])
where(series: [-1, nil, Namespaces::InProductMarketingEmailsService::TRACKS[:create][:interval_days].length])
with_them do
it_behaves_like 'no track and 404'
......
......@@ -41,22 +41,23 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do
using RSpec::Parameterized::TableSyntax
where(:track, :interval, :actions_completed) do
:create | 1 | { created_at: frozen_time - 2.days }
:create | 5 | { created_at: frozen_time - 6.days }
:create | 10 | { created_at: frozen_time - 11.days }
:verify | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days }
:verify | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days }
:verify | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days }
:trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days }
:trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days }
:trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days }
:team | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days, trial_started_at: frozen_time - 2.days }
:team | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days, trial_started_at: frozen_time - 6.days }
:team | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days, trial_started_at: frozen_time - 11.days }
:create | 1 | { created_at: frozen_time - 2.days }
:create | 5 | { created_at: frozen_time - 6.days }
:create | 10 | { created_at: frozen_time - 11.days }
:verify | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days }
:verify | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days }
:verify | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days }
:trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days }
:trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days }
:trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days }
:team | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days, trial_started_at: frozen_time - 2.days }
:team | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days, trial_started_at: frozen_time - 6.days }
:team | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days, trial_started_at: frozen_time - 11.days }
:experience | 30 | { created_at: frozen_time - 31.days, git_write_at: frozen_time - 31.days }
end
with_them do
it { is_expected.to send_in_product_marketing_email(user.id, group.id, track, described_class::INTERVAL_DAYS.index(interval)) }
it { is_expected.to send_in_product_marketing_email(user.id, group.id, track, described_class::TRACKS[track][:interval_days].index(interval)) }
end
end
......@@ -235,7 +236,7 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do
let(:track) { :foo }
before do
stub_const("#{described_class}::TRACKS", { bar: :git_write })
stub_const("#{described_class}::TRACKS", { bar: {} })
end
it { expect { subject }.to raise_error(ArgumentError, 'Track foo not defined') }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment