Commit 4ed462d9 authored by Doug Stull's avatar Doug Stull

Add preview text to the invite email with goto action

- adds goto action and preview text experiment.
parent afc03791
......@@ -75,7 +75,10 @@ class InvitesController < ApplicationController
end
def track_invite_join_click
Gitlab::Tracking.event(self.class.name, 'join_clicked', label: 'invite_email', property: member.id.to_s) if member && initial_invite_email?
return unless member && initial_invite_email?
experiment(:invite_email_preview_text, actor: member).track(:join_clicked) if params[:experiment_name] == 'invite_email_preview_text'
Gitlab::Tracking.event(self.class.name, 'join_clicked', label: 'invite_email', property: member.id.to_s)
end
def authenticate_user!
......@@ -96,6 +99,7 @@ class InvitesController < ApplicationController
session[:invite_email] = member.invite_email
session[:originating_member_id] = member.id if initial_invite_email?
session[:invite_email_experiment_name] = params[:experiment_name] if initial_invite_email? && params[:experiment_name]
end
def initial_invite_email?
......
......@@ -199,6 +199,8 @@ class RegistrationsController < Devise::RegistrationsController
return unless member
experiment_name = session.delete(:invite_email_experiment_name)
experiment(:invite_email_preview_text, actor: member).track(:accepted) if experiment_name == 'invite_email_preview_text'
Gitlab::Tracking.event(self.class.name, 'accepted', label: 'invite_email', property: member.id.to_s)
end
......
......@@ -7,21 +7,9 @@ module EmailsHelper
# https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url)
name = action_title(url)
if name
data = {
"@context" => "http://schema.org",
"@type" => "EmailMessage",
"action" => {
"@type" => "ViewAction",
"name" => name,
"url" => url
}
}
return unless name
content_tag :script, type: 'application/ld+json' do
data.to_json.html_safe
end
end
gmail_goto_action(name, url)
end
def action_title(url)
......@@ -36,6 +24,22 @@ module EmailsHelper
nil
end
def gmail_goto_action(name, url)
data = {
"@context" => "http://schema.org",
"@type" => "EmailMessage",
"action" => {
"@type" => "ViewAction",
"name" => name,
"url" => url
}
}
content_tag :script, type: 'application/ld+json' do
data.to_json.html_safe
end
end
def sanitize_name(name)
if name =~ URI::DEFAULT_PARSER.regexp[:URI_REF]
name.tr('.', '_')
......
......@@ -14,6 +14,7 @@
= stylesheet_link_tag 'mailer.css'
%body
= yield :preview_text
%table#body{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
%tr.line
......
......@@ -4,17 +4,31 @@
project_or_group: member_source.model_name.singular,
br_tag: '<br/>'.html_safe,
role: member.human_access.downcase }
- join_text = s_('InviteEmail|Join now')
- join_url = invite_url(@token, invite_type: Emails::Members::INITIAL_INVITE, experiment_name: 'invite_email_preview_text')
- inviter_name = member.created_by.name if member.created_by
- experiment(:invite_email_preview_text, actor: member) do |experiment_instance|
- experiment_instance.use {}
- experiment_instance.candidate do
= content_for :preview_text do
%div{ style: "display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;" }
- if member.created_by
= s_('InviteEmail|Join your team on GitLab! %{inviter} invited you to %{project_or_group_name}') % { inviter: inviter_name, project_or_group_name: placeholders[:project_or_group_name] }
- else
= s_('InviteEmail|Join your team on GitLab! You are invited to %{project_or_group_name}') % { project_or_group_name: placeholders[:project_or_group_name] }
= gmail_goto_action(join_text, join_url)
%tr
%td.text-content{ colspan: 2 }
%img.mail-avatar{ height: "60", src: avatar_icon_for_user(member.created_by, 60, only_path: false), width: "60", alt: "" }
%p
- if member.created_by
= html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders.merge({ inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe })
= html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders.merge({ inviter: (link_to inviter_name, user_url(member.created_by)).html_safe })
- else
= html_escape(s_("InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders
%p.invite-actions
= link_to s_('InviteEmail|Join now'), invite_url(@token, invite_type: Emails::Members::INITIAL_INVITE), class: 'invite-btn-join'
= link_to join_text, join_url, class: 'invite-btn-join'
%tr.border-top
%td.text-content.mailer-align-left.half-width
%h4
......
---
name: invite_email_preview_text
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67236
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/421
milestone: '14.2'
type: experiment
group: group::expansion
default_enabled: false
......@@ -17999,6 +17999,12 @@ msgstr ""
msgid "InviteEmail|Join now"
msgstr ""
msgid "InviteEmail|Join your team on GitLab! %{inviter} invited you to %{project_or_group_name}"
msgstr ""
msgid "InviteEmail|Join your team on GitLab! You are invited to %{project_or_group_name}"
msgstr ""
msgid "InviteEmail|Projects are used to host and collaborate on code, track issues, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
msgstr ""
......
......@@ -42,6 +42,29 @@ RSpec.describe InvitesController do
)
end
context 'when it is part of the invite_email_preview_text experiment' do
let(:extra_params) { { invite_type: 'initial_email', experiment_name: 'invite_email_preview_text' } }
it 'tracks the initial join click from email' do
experiment = double(track: true)
allow(controller).to receive(:experiment).with(:invite_email_preview_text, actor: member).and_return(experiment)
request
expect(experiment).to have_received(:track).with(:join_clicked)
end
context 'when member does not exist' do
let(:raw_invite_token) { '_bogus_token_' }
it 'does not track the experiment' do
expect(controller).not_to receive(:experiment).with(:invite_email_preview_text, actor: member)
request
end
end
end
context 'when member does not exist' do
let(:raw_invite_token) { '_bogus_token_' }
......@@ -67,6 +90,14 @@ RSpec.describe InvitesController do
label: 'invite_email'
)
end
context 'when it is not part of our invite email experiment' do
it 'does not track via experiment' do
expect(controller).not_to receive(:experiment).with(:invite_email_preview_text, actor: member)
request
end
end
end
context 'when logged in' do
......
......@@ -159,11 +159,12 @@ RSpec.describe RegistrationsController do
let_it_be(:member) { create(:project_member, :invited, invite_email: user_params.dig(:user, :email)) }
let(:originating_member_id) { member.id }
let(:extra_session_params) { {} }
let(:session_params) do
{
invite_email: user_params.dig(:user, :email),
originating_member_id: originating_member_id
}
}.merge extra_session_params
end
context 'when member exists from the session key value' do
......@@ -192,6 +193,40 @@ RSpec.describe RegistrationsController do
)
end
end
context 'with the invite_email_preview_text experiment', :experiment do
let(:extra_session_params) { { invite_email_experiment_name: 'invite_email_preview_text' } }
context 'when member and invite_email_experiment_name exists from the session key value' do
it 'tracks the invite acceptance' do
expect(experiment(:invite_email_preview_text)).to track(:accepted)
.with_context(actor: member)
.on_next_instance
subject
end
end
context 'when member does not exist from the session key value' do
let(:originating_member_id) { -1 }
it 'does not track invite acceptance' do
expect(experiment(:invite_email_preview_text)).not_to track(:accepted)
subject
end
end
context 'when invite_email_experiment_name does not exist from the session key value' do
let(:extra_session_params) { {} }
it 'does not track invite acceptance' do
expect(experiment(:invite_email_preview_text)).not_to track(:accepted)
subject
end
end
end
end
context 'when invite email matches email used on registration' do
......
......@@ -172,6 +172,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
let(:invite_email) { new_user.email }
let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email, created_by: owner) }
let(:send_email_confirmation) { true }
let(:extra_params) { { invite_type: Emails::Members::INITIAL_INVITE } }
before do
stub_application_setting(send_user_confirmation_email: send_email_confirmation)
......@@ -179,7 +180,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
context 'when registering using invitation email' do
before do
visit invite_path(group_invite.raw_invite_token, invite_type: Emails::Members::INITIAL_INVITE)
visit invite_path(group_invite.raw_invite_token, extra_params)
end
context 'with admin approval required enabled' do
......@@ -232,6 +233,20 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end
end
context 'with invite email acceptance for the invite_email_preview_text experiment', :experiment do
let(:extra_params) do
{ invite_type: Emails::Members::INITIAL_INVITE, experiment_name: 'invite_email_preview_text' }
end
it 'tracks the accepted invite' do
expect(experiment(:invite_email_preview_text)).to track(:accepted)
.with_context(actor: group_invite)
.on_next_instance
fill_in_sign_up_form(new_user)
end
end
it 'signs up and redirects to the group activity page with all the project/groups invitation automatically accepted' do
fill_in_sign_up_form(new_user)
fill_in_welcome_form
......
......@@ -798,7 +798,10 @@ RSpec.describe Notify do
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
is_expected.to have_link('Join now', href: invite_url(project_member.invite_token, invite_type: Emails::Members::INITIAL_INVITE))
is_expected.to have_link('Join now',
href: invite_url(project_member.invite_token,
invite_type: Emails::Members::INITIAL_INVITE,
experiment_name: 'invite_email_preview_text'))
is_expected.to have_content("#{inviter.name} invited you to join the")
is_expected.to have_content('Project details')
is_expected.to have_content("What's it about?")
......@@ -813,7 +816,10 @@ RSpec.describe Notify do
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
is_expected.to have_link('Join now', href: invite_url(project_member.invite_token, invite_type: Emails::Members::INITIAL_INVITE))
is_expected.to have_link('Join now',
href: invite_url(project_member.invite_token,
invite_type: Emails::Members::INITIAL_INVITE,
experiment_name: 'invite_email_preview_text'))
is_expected.to have_content('Project details')
is_expected.to have_content("What's it about?")
end
......
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