Commit b8be76f7 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'gitlab-org-growth-product-843-add-upgrade-to-user-dropdown' into 'master'

Add Upgrade option to user dropdown

Closes gitlab-org/growth/product#843

See merge request gitlab-org/gitlab!29428
parents b5511297 6bfe3fe4
...@@ -74,20 +74,27 @@ function initStatusTriggers() { ...@@ -74,20 +74,27 @@ function initStatusTriggers() {
} }
} }
function trackShowUserDropdownLink(trackEvent, elToTrack, el) {
const { trackLabel, trackProperty } = elToTrack.dataset;
$(el).on('shown.bs.dropdown', () => {
Tracking.event(document.body.dataset.page, trackEvent, {
label: trackLabel,
property: trackProperty,
});
});
}
export function initNavUserDropdownTracking() { export function initNavUserDropdownTracking() {
const el = document.querySelector('.js-nav-user-dropdown'); const el = document.querySelector('.js-nav-user-dropdown');
const buyEl = document.querySelector('.js-buy-ci-minutes-link'); const buyEl = document.querySelector('.js-buy-ci-minutes-link');
const upgradeEl = document.querySelector('.js-upgrade-plan-link');
if (el && buyEl) { if (el && buyEl) {
const { trackLabel, trackProperty } = buyEl.dataset; trackShowUserDropdownLink('show_buy_ci_minutes', buyEl, el);
const trackEvent = 'show_buy_ci_minutes'; }
$(el).on('shown.bs.dropdown', () => { if (el && upgradeEl) {
Tracking.event(undefined, trackEvent, { trackShowUserDropdownLink('show_upgrade_link', upgradeEl, el);
label: trackLabel,
property: trackProperty,
});
});
} }
} }
......
...@@ -553,6 +553,7 @@ ...@@ -553,6 +553,7 @@
vertical-align: text-top; vertical-align: text-top;
} }
a.upgrade-plan-link gl-emoji,
a.ci-minutes-emoji gl-emoji, a.ci-minutes-emoji gl-emoji,
a.trial-link gl-emoji { a.trial-link gl-emoji {
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
%li %li
= link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' } = link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' }
= render_if_exists 'layouts/header/buy_ci_minutes', project: @project, namespace: @group = render_if_exists 'layouts/header/buy_ci_minutes', project: @project, namespace: @group
= render_if_exists 'layouts/header/upgrade'
- if current_user_menu?(:help) - if current_user_menu?(:help)
%li.divider.d-md-none %li.divider.d-md-none
......
...@@ -20,6 +20,16 @@ module EE ...@@ -20,6 +20,16 @@ module EE
end end
end end
def show_upgrade_link?(user)
return unless user
return unless ::Gitlab.com?
return unless experiment_enabled?(:upgrade_link_in_user_menu_a)
Rails.cache.fetch(['users', user.id, 'show_upgrade_link?'], expires_in: 10.minutes) do
user.owns_upgradeable_namespace?
end
end
private private
def trials_allowed?(user) def trials_allowed?(user)
......
...@@ -243,17 +243,17 @@ module EE ...@@ -243,17 +243,17 @@ module EE
::Namespace ::Namespace
.from("(#{namespace_union_for_reporter_developer_maintainer_owned}) #{::Namespace.table_name}") .from("(#{namespace_union_for_reporter_developer_maintainer_owned}) #{::Namespace.table_name}")
.include_gitlab_subscription .include_gitlab_subscription
.where(gitlab_subscriptions: { hosted_plan: ::Plan.where(name: Plan::PAID_HOSTED_PLANS) }) .where(gitlab_subscriptions: { hosted_plan: ::Plan.where(name: ::Plan::PAID_HOSTED_PLANS) })
.any? .any?
end end
# Returns true if the user is an Owner on any namespace currently on # Returns true if the user is an Owner on any namespace currently on
# a paid plan # a paid plan
def owns_paid_namespace? def owns_paid_namespace?(plans: ::Plan::PAID_HOSTED_PLANS)
::Namespace ::Namespace
.from("(#{namespace_union_for_owned}) #{::Namespace.table_name}") .from("(#{namespace_union_for_owned}) #{::Namespace.table_name}")
.include_gitlab_subscription .include_gitlab_subscription
.where(gitlab_subscriptions: { hosted_plan: ::Plan.where(name: Plan::PAID_HOSTED_PLANS) }) .where(gitlab_subscriptions: { hosted_plan: ::Plan.where(name: plans) })
.any? .any?
end end
...@@ -363,6 +363,11 @@ module EE ...@@ -363,6 +363,11 @@ module EE
InstanceSecurityDashboard.new(self) InstanceSecurityDashboard.new(self)
end end
def owns_upgradeable_namespace?
!owns_paid_namespace?(plans: [::Plan::GOLD]) &&
owns_paid_namespace?(plans: [::Plan::BRONZE, ::Plan::SILVER])
end
protected protected
override :password_required? override :password_required?
......
- if show_upgrade_link?(current_user)
%li
= link_to EE::SUBSCRIPTIONS_PLANS_URL,
class: 'upgrade-plan-link js-upgrade-plan-link',
data: { 'track-event': 'click_upgrade_link', 'track-label': current_user.namespace.actual_plan_name, 'track-property': 'user_dropdown' } do
= s_("CurrentUser|Upgrade")
= emoji_icon('rocket', 'aria-hidden': true)
...@@ -1204,4 +1204,49 @@ describe User do ...@@ -1204,4 +1204,49 @@ describe User do
expect(security_dashboard).to be_a(InstanceSecurityDashboard) expect(security_dashboard).to be_a(InstanceSecurityDashboard)
end end
end end
describe '#owns_upgradeable_namespace?' do
let_it_be(:user) { create(:user) }
subject { user.owns_upgradeable_namespace? }
using RSpec::Parameterized::TableSyntax
where(:hosted_plan, :result) do
:bronze_plan | true
:silver_plan | true
:gold_plan | false
:free_plan | false
:default_plan | false
end
with_them do
it 'returns the correct result for each plan on a personal namespace' do
plan = create(hosted_plan)
create(:gitlab_subscription, namespace: user.namespace, hosted_plan: plan)
expect(subject).to be result
end
it 'returns the correct result for each plan on a group owned by the user' do
create(:group_with_plan, plan: hosted_plan).add_owner(user)
expect(subject).to be result
end
end
it 'returns false when there is no subscription for the personal namespace' do
expect(subject).to be false
end
it 'returns false when the user has multiple groups and any group has gold' do
create(:group_with_plan, plan: :bronze_plan).add_owner(user)
create(:group_with_plan, plan: :silver_plan).add_owner(user)
create(:group_with_plan, plan: :gold_plan).add_owner(user)
user.namespace.plans.reload
expect(subject).to be false
end
end
end end
...@@ -4,31 +4,63 @@ require 'spec_helper' ...@@ -4,31 +4,63 @@ require 'spec_helper'
describe 'layouts/header/_current_user_dropdown' do describe 'layouts/header/_current_user_dropdown' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:need_minutes) { true }
before do describe 'Buy CI Minutes link in user dropdown' do
allow(view).to receive(:current_user).and_return(user) let(:need_minutes) { true }
allow(view).to receive(:show_buy_ci_minutes?).and_return(need_minutes)
render before do
end allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:show_upgrade_link?).and_return(false)
allow(view).to receive(:show_buy_ci_minutes?).and_return(need_minutes)
render
end
subject { rendered } subject { rendered }
context 'when ci minutes need bought' do context 'when ci minutes need bought' do
it 'has "Buy CI minutes" link with correct data properties', :aggregate_failures do it 'has "Buy CI minutes" link with correct data properties', :aggregate_failures do
expect(subject).to have_selector('[data-track-event="click_buy_ci_minutes"]') expect(subject).to have_selector('[data-track-event="click_buy_ci_minutes"]')
expect(subject).to have_selector("[data-track-label='#{user.namespace.actual_plan_name}']") expect(subject).to have_selector("[data-track-label='#{user.namespace.actual_plan_name}']")
expect(subject).to have_selector('[data-track-property="user_dropdown"]') expect(subject).to have_selector('[data-track-property="user_dropdown"]')
expect(subject).to have_link('Buy CI minutes') expect(subject).to have_link('Buy CI minutes')
end
end
context 'when ci minutes do not need bought' do
let(:need_minutes) { false }
it 'has no "Buy CI minutes" link' do
expect(subject).not_to have_link('Buy CI minutes')
end
end end
end end
context 'when ci minutes do not need bought' do describe 'Upgrade link in user dropdown' do
let(:need_minutes) { false } let(:on_upgradeable_plan) { true }
before do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:show_buy_ci_minutes?).and_return(false)
allow(view).to receive(:show_upgrade_link?).and_return(on_upgradeable_plan)
render
end
subject { rendered }
context 'when user is on an upgradeable plan' do
it 'displays the Upgrade link' do
expect(subject).to have_link('Upgrade')
end
end
context 'when user is not on an upgradeable plan' do
let(:on_upgradeable_plan) { false }
it 'has no "Buy CI minutes" link' do it 'does not display the Upgrade link' do
expect(subject).not_to have_link('Buy CI minutes') expect(subject).not_to have_link('Upgrade')
end
end end
end end
end end
...@@ -42,6 +42,9 @@ module Gitlab ...@@ -42,6 +42,9 @@ module Gitlab
}, },
buy_ci_minutes_version_a: { buy_ci_minutes_version_a: {
tracking_category: 'Growth::Expansion::Experiment::BuyCiMinutesVersionA' tracking_category: 'Growth::Expansion::Experiment::BuyCiMinutesVersionA'
},
upgrade_link_in_user_menu_a: {
tracking_category: 'Growth::Expansion::Experiment::UpgradeLinkInUserMenuA'
} }
}.freeze }.freeze
......
...@@ -6477,6 +6477,9 @@ msgstr "" ...@@ -6477,6 +6477,9 @@ msgstr ""
msgid "CurrentUser|Start a Gold trial" msgid "CurrentUser|Start a Gold trial"
msgstr "" msgstr ""
msgid "CurrentUser|Upgrade"
msgstr ""
msgid "Custom CI configuration path" msgid "Custom CI configuration path"
msgstr "" msgstr ""
......
...@@ -60,8 +60,8 @@ describe('Header', () => { ...@@ -60,8 +60,8 @@ describe('Header', () => {
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<li class="js-nav-user-dropdown"> <li class="js-nav-user-dropdown">
<a class="js-buy-ci-minutes-link" data-track-event="click_buy_ci_minutes" data-track-label="free" data-track-property="user_dropdown">Buy CI minutes <a class="js-buy-ci-minutes-link" data-track-event="click_buy_ci_minutes" data-track-label="free" data-track-property="user_dropdown">Buy CI minutes</a>
</a> <a class="js-upgrade-plan-link" data-track-event="click_upgrade_link" data-track-label="free" data-track-property="user_dropdown">Upgrade</a>
</li>`); </li>`);
trackingSpy = mockTracking('_category_', $('.js-nav-user-dropdown').element, jest.spyOn); trackingSpy = mockTracking('_category_', $('.js-nav-user-dropdown').element, jest.spyOn);
...@@ -77,8 +77,16 @@ describe('Header', () => { ...@@ -77,8 +77,16 @@ describe('Header', () => {
it('sends a tracking event when the dropdown is opened and contains Buy CI minutes link', () => { it('sends a tracking event when the dropdown is opened and contains Buy CI minutes link', () => {
$('.js-nav-user-dropdown').trigger('shown.bs.dropdown'); $('.js-nav-user-dropdown').trigger('shown.bs.dropdown');
expect(trackingSpy).toHaveBeenCalledTimes(1); expect(trackingSpy).toHaveBeenCalledWith('some:page', 'show_buy_ci_minutes', {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'show_buy_ci_minutes', { label: 'free',
property: 'user_dropdown',
});
});
it('sends a tracking event when the dropdown is opened and contains Upgrade link', () => {
$('.js-nav-user-dropdown').trigger('shown.bs.dropdown');
expect(trackingSpy).toHaveBeenCalledWith('some:page', 'show_upgrade_link', {
label: 'free', label: 'free',
property: 'user_dropdown', property: 'user_dropdown',
}); });
......
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