Commit 5b3c1dd0 authored by Dallas Reedy's avatar Dallas Reedy Committed by Scott Hampton

Highlight paid features during active trial

Part 1 of 4.

Sets the groundwork for the “Highlight paid features during active trial”
experiment. Mainly, it sets up a new experiment using the `gitlab-experiment`
gem (GLEX) and creates the initial badge component that will be used as the
building block for the rest of the experiment features.
parent 465d9868
.gl-badge.feature-highlight-badge {
background-color: $purple-light;
color: $purple;
&,
&.sm {
padding: 0.25rem;
}
}
import '~/pages/projects/merge_requests/creations/new/index';
import { initPaidFeatureCalloutBadge } from 'ee/paid_feature_callouts/index';
import UserCallout from '~/user_callout';
import initForm from '../../shared/init_form';
initForm();
initPaidFeatureCalloutBadge();
// eslint-disable-next-line no-new
new UserCallout();
import '~/pages/projects/merge_requests/edit/index';
import { initPaidFeatureCalloutBadge } from 'ee/paid_feature_callouts/index';
import initForm from '../shared/init_form';
initForm();
initPaidFeatureCalloutBadge();
<script>
import { GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Tracking from '~/tracking';
export default {
components: {
GlBadge,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
i18n: {
title: __('This feature is part of your GitLab Ultimate trial.'),
},
mounted() {
this.trackBadgeDisplayedForExperiment();
},
methods: {
trackBadgeDisplayedForExperiment() {
this.track('display_badge', {
label: 'feature_highlight_badge',
property: 'experiment:highlight_paid_features_during_active_trial',
});
},
},
};
</script>
<template>
<gl-badge
v-gl-tooltip
:title="$options.i18n.title"
tabindex="0"
size="sm"
class="feature-highlight-badge"
>
<gl-icon name="license" :size="12" />
</gl-badge>
</template>
import Vue from 'vue';
import PaidFeatureCalloutBadge from './components/paid_feature_callout_badge.vue';
export const initPaidFeatureCalloutBadge = () => {
const el = document.getElementById('js-paid-feature-badge');
if (!el) return undefined;
return new Vue({
el,
render: (createElement) => createElement(PaidFeatureCalloutBadge),
});
};
# frozen_string_literal: true
module PaidFeatureCalloutHelper
def run_highlight_paid_features_during_active_trial_experiment(group, &block)
experiment(:highlight_paid_features_during_active_trial, group: group) do |e|
e.exclude! unless billing_plans_and_trials_available?
e.exclude! unless group && eligible_for_trial_upgrade_callout?(group)
e.use { nil } # control gets nothing new added to the existing UI
e.try(&block)
end
end
end
......@@ -20,7 +20,7 @@ module TrialStatusWidgetHelper
end
def show_trial_status_widget?(group)
billing_plans_and_trials_available? && eligible_for_trial_status_widget?(group)
billing_plans_and_trials_available? && eligible_for_trial_upgrade_callout?(group)
end
private
......@@ -29,7 +29,7 @@ module TrialStatusWidgetHelper
::Gitlab::CurrentSettings.should_check_namespace_plan?
end
def eligible_for_trial_status_widget?(group)
def eligible_for_trial_upgrade_callout?(group)
group.trial_active? && can?(current_user, :admin_namespace, group)
end
......
......@@ -6,6 +6,11 @@
- if !Feature.enabled?(:mr_collapsed_approval_rules, @project)
.form-group.row
.col-sm-2.col-form-label
= form.label :approver_ids, "Approval rules"
.gl-display-flex.gl-align-items-center.gl-justify-content-end
- root_group = @project.group&.root_ancestor
- run_highlight_paid_features_during_active_trial_experiment(root_group) do
.gl-mr-3.gl-mb-2
#js-paid-feature-badge
= form.label :approver_ids, "Approval rules"
.col-sm-10
= render_if_exists 'shared/issuable/approver_suggestion', issuable: issuable, presenter: presenter
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PaidFeatureCalloutBadge from 'ee/paid_feature_callouts/components/paid_feature_callout_badge.vue';
import { mockTracking } from 'helpers/tracking_helper';
describe('PaidFeatureCalloutBadge component', () => {
let trackingSpy;
let wrapper;
const findGlBadge = () => wrapper.findComponent(GlBadge);
const createComponent = () => {
return shallowMount(PaidFeatureCalloutBadge);
};
beforeEach(() => {
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the title', () => {
expect(findGlBadge().attributes('title')).toBe(
'This feature is part of your GitLab Ultimate trial.',
);
});
it('tracks that the badge has been displayed when mounted', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'display_badge', {
label: 'feature_highlight_badge',
property: 'experiment:highlight_paid_features_during_active_trial',
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe PaidFeatureCalloutHelper do
describe '#run_highlight_paid_features_during_active_trial_experiment', :experiment do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let_it_be(:candidate_block) { proc { 'in candidate' } }
let(:trials_available) { true }
let(:user_can_admin_group) { true }
let(:group_has_active_trial) { true }
before do
stub_application_setting(check_namespace_plan: trials_available)
stub_experiments(highlight_paid_features_during_active_trial: variant)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :admin_namespace, group).and_return(user_can_admin_group)
allow(group).to receive(:trial_active?).and_return(group_has_active_trial)
end
subject { helper.run_highlight_paid_features_during_active_trial_experiment(group, &candidate_block).run }
context 'when the user is in the candidate' do
let(:variant) { :candidate }
it { is_expected.to eq('in candidate') }
end
shared_examples 'user receives control experience' do
it { is_expected.to be_nil }
end
context 'when the user is in the control' do
let(:variant) { :control }
include_examples 'user receives control experience'
end
context 'when the user would be in the candidate' do
let(:variant) { :candidate }
context 'but trials are not available' do
let(:trials_available) { false }
include_examples 'user receives control experience'
end
context 'but the group is not in an active trial' do
let(:group_has_active_trial) { false }
include_examples 'user receives control experience'
end
context 'but the user is not an admin of the group' do
let(:user_can_admin_group) { false }
include_examples 'user receives control experience'
end
end
end
end
......@@ -66,4 +66,34 @@ RSpec.describe 'shared/issuable/_approvals.html.haml' do
end
end
end
context 'when running the highlight paid features experiment', :experiment do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
before do
create(:gitlab_subscription, :active_trial, namespace: group)
group.add_maintainer(user)
stub_application_setting(check_namespace_plan: true)
stub_feature_flags(mr_collapsed_approval_rules: false)
stub_experiments(highlight_paid_features_during_active_trial: variant)
render 'shared/issuable/approvals', form: form, issuable: merge_request, project: project, presenter: presenter
end
context 'when user is in the control' do
let(:variant) { :control }
it 'does not render the paid feature badge' do
expect(rendered).not_to have_css('#js-paid-feature-badge')
end
end
context 'when user is in the candidate' do
let(:variant) { :candidate }
it 'renders the paid feature badge' do
expect(rendered).to have_css('#js-paid-feature-badge')
end
end
end
end
......@@ -31741,6 +31741,9 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
msgid "This feature is part of your GitLab Ultimate trial."
msgstr ""
msgid "This feature requires local storage to be enabled"
msgstr ""
......
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