Commit 8bba4e5a authored by Dallas Reedy's avatar Dallas Reedy Committed by Rémy Coutable

Add tracking for active trial status reminders experiment

parent cb26a88c
......@@ -4,7 +4,9 @@ const CLICK_BUTTON_ACTION = 'click_button';
const RESIZE_EVENT_DEBOUNCE_MS = 150;
export const RESIZE_EVENT = 'resize';
export const TRACKING_PROPERTY = 'experiment:show_trial_status_in_sidebar';
export const EXPERIMENT_KEY = 'forcibly_show_trial_status_popover';
export const TRACKING_PROPERTY_WHEN_FORCED = 'forced';
export const TRACKING_PROPERTY_WHEN_VOLUNTARY = 'voluntary';
export const WIDGET = {
i18n: {
......@@ -19,7 +21,11 @@ export const WIDGET = {
},
},
trackingEvents: {
widgetClick: { action: 'click_link', label: 'trial_status_widget' },
widgetClick: {
action: 'click_link',
label: 'trial_status_widget',
property: TRACKING_PROPERTY_WHEN_VOLUNTARY,
},
},
};
......
......@@ -6,7 +6,13 @@ import axios from '~/lib/utils/axios_utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import { sprintf } from '~/locale';
import Tracking from '~/tracking';
import { POPOVER, RESIZE_EVENT, TRACKING_PROPERTY } from './constants';
import {
POPOVER,
RESIZE_EVENT,
EXPERIMENT_KEY,
TRACKING_PROPERTY_WHEN_FORCED,
TRACKING_PROPERTY_WHEN_VOLUNTARY,
} from './constants';
const {
i18n,
......@@ -15,7 +21,7 @@ const {
resizeEventDebounceMS,
disabledBreakpoints,
} = POPOVER;
const trackingMixin = Tracking.mixin({ property: TRACKING_PROPERTY });
const trackingMixin = Tracking.mixin({ experiment: EXPERIMENT_KEY });
export default {
components: {
......@@ -26,6 +32,7 @@ export default {
mixins: [trackingMixin],
inject: {
containerId: {},
daysRemaining: {}, // for tracking purposes
groupName: {},
planName: {},
plansHref: {},
......@@ -45,7 +52,6 @@ export default {
};
},
i18n,
trackingEvents,
computed: {
formattedTrialEndDate() {
return formatDate(this.trialEndDate, trialEndDateFormatString);
......@@ -79,8 +85,8 @@ export default {
this.forciblyShowing = false;
this.show = false;
const { action, ...options } = this.$options.trackingEvents.closeBtnClick;
this.track(action, options);
const { action, ...options } = trackingEvents.closeBtnClick;
this.track(action, { ...options, ...this.trackingPropertyAndValue() });
},
onForciblyShown() {
if (this.userCalloutsPath && this.userCalloutsFeatureId) {
......@@ -98,16 +104,24 @@ export default {
this.updateDisabledState();
},
onShown() {
const { action, ...options } = this.$options.trackingEvents.popoverShown;
this.track(action, options);
const { action, ...options } = trackingEvents.popoverShown;
this.track(action, { ...options, ...this.trackingPropertyAndValue() });
},
onUpgradeBtnClick() {
const { action, ...options } = this.$options.trackingEvents.upgradeBtnClick;
this.track(action, options);
const { action, ...options } = trackingEvents.upgradeBtnClick;
this.track(action, { ...options, ...this.trackingPropertyAndValue() });
},
onCompareBtnClick() {
const { action, ...options } = this.$options.trackingEvents.compareBtnClick;
this.track(action, options);
const { action, ...options } = trackingEvents.compareBtnClick;
this.track(action, { ...options, ...this.trackingPropertyAndValue() });
},
trackingPropertyAndValue() {
return {
property: this.forciblyShowing
? TRACKING_PROPERTY_WHEN_FORCED
: TRACKING_PROPERTY_WHEN_VOLUNTARY,
value: this.daysRemaining,
};
},
updateDisabledState() {
this.disabled = disabledBreakpoints.includes(bp.getBreakpointSize());
......
......@@ -2,10 +2,10 @@
import { GlLink, GlProgressBar } from '@gitlab/ui';
import { sprintf } from '~/locale';
import Tracking from '~/tracking';
import { TRACKING_PROPERTY, WIDGET } from './constants';
import { EXPERIMENT_KEY, WIDGET } from './constants';
const { i18n, trackingEvents } = WIDGET;
const trackingMixin = Tracking.mixin({ property: TRACKING_PROPERTY });
const trackingMixin = Tracking.mixin({ experiment: EXPERIMENT_KEY });
export default {
components: {
......@@ -21,13 +21,9 @@ export default {
planName: {},
plansHref: {},
},
i18n,
trackingEvents,
computed: {
widgetTitle() {
const i18nWidgetTitle = this.$options.i18n.widgetTitle.countableTranslator(
this.daysRemaining,
);
const i18nWidgetTitle = i18n.widgetTitle.countableTranslator(this.daysRemaining);
return sprintf(i18nWidgetTitle, {
planName: this.planName,
......@@ -38,8 +34,8 @@ export default {
},
methods: {
onWidgetClick() {
const { action, ...options } = this.$options.trackingEvents.widgetClick;
this.track(action, options);
const { action, ...options } = trackingEvents.widgetClick;
this.track(action, { ...options, value: this.daysRemaining });
},
},
};
......
......@@ -37,6 +37,7 @@ export const initTrialStatusPopover = () => {
const {
containerId,
daysRemaining, // for tracking purposes
groupName,
planName,
plansHref,
......@@ -52,6 +53,7 @@ export const initTrialStatusPopover = () => {
el,
provide: {
containerId,
daysRemaining, // for tracking purposes
groupName,
planName,
plansHref,
......
......@@ -21,6 +21,7 @@ module TrialStatusWidgetHelper
def trial_status_popover_data_attrs(group)
base_attrs = trial_status_common_data_attrs(group)
base_attrs.merge(
days_remaining: group.trial_days_remaining, # for experiment tracking
group_name: group.name,
purchase_href: ultimate_subscription_path_for_group(group),
start_initially_shown: force_popover_to_be_shown?(group),
......@@ -57,6 +58,7 @@ module TrialStatusWidgetHelper
experiment(:forcibly_show_trial_status_popover, group: group) do |e|
e.use { false }
e.try { !dismissed_feature_callout?(current_user_callout_feature_id(group.trial_days_remaining)) }
e.record!
e.run
end
end
......
......@@ -2,7 +2,11 @@ import { GlPopover } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import { POPOVER, TRACKING_PROPERTY } from 'ee/contextual_sidebar/components/constants';
import {
POPOVER,
TRACKING_PROPERTY_WHEN_FORCED,
TRACKING_PROPERTY_WHEN_VOLUNTARY,
} from 'ee/contextual_sidebar/components/constants';
import TrialStatusPopover from 'ee/contextual_sidebar/components/trial_status_popover.vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
......@@ -15,13 +19,15 @@ describe('TrialStatusPopover component', () => {
let trackingSpy;
const { trackingEvents } = POPOVER;
const defaultDaysRemaining = 20;
const findGlPopover = () => wrapper.findComponent(GlPopover);
const expectTracking = ({ action, ...options } = {}) => {
return expect(trackingSpy).toHaveBeenCalledWith(undefined, action, {
property: TRACKING_PROPERTY_WHEN_VOLUNTARY,
value: defaultDaysRemaining,
...options,
property: TRACKING_PROPERTY,
});
};
......@@ -30,6 +36,7 @@ describe('TrialStatusPopover component', () => {
mountFn(TrialStatusPopover, {
provide: {
containerId: undefined,
daysRemaining: defaultDaysRemaining,
groupName: 'Some Test Group',
planName: 'Ultimate',
plansHref: 'billing/path-for/group',
......@@ -210,4 +217,26 @@ describe('TrialStatusPopover component', () => {
});
});
});
describe('trackingPropertyAndValue', () => {
it.each`
daysRemaining | startInitiallyShown | property
${14} | ${false} | ${TRACKING_PROPERTY_WHEN_VOLUNTARY}
${14} | ${true} | ${TRACKING_PROPERTY_WHEN_FORCED}
`(
'sets the expected values for `property` & `value`',
({ daysRemaining, startInitiallyShown, property }) => {
wrapper = createComponent({ daysRemaining, startInitiallyShown });
// We'll use the "onShown" method to exercise trackingPropertyAndValue
findGlPopover().vm.$emit('shown');
expectTracking({
...trackingEvents.popoverShown,
property,
value: daysRemaining,
});
},
);
});
});
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { TRACKING_PROPERTY, WIDGET } from 'ee/contextual_sidebar/components/constants';
import { WIDGET } from 'ee/contextual_sidebar/components/constants';
import TrialStatusWidget from 'ee/contextual_sidebar/components/trial_status_widget.vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
......@@ -8,13 +8,14 @@ describe('TrialStatusWidget component', () => {
let wrapper;
const { trackingEvents } = WIDGET;
const defaultDaysRemaining = 20;
const findGlLink = () => wrapper.findComponent(GlLink);
const createComponent = (providers = {}) => {
return shallowMount(TrialStatusWidget, {
provide: {
daysRemaining: 20,
daysRemaining: defaultDaysRemaining,
navIconImagePath: 'illustrations/golden_tanuki.svg',
percentageComplete: 10,
planName: 'Ultimate',
......@@ -58,7 +59,7 @@ describe('TrialStatusWidget component', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, action, {
...options,
property: TRACKING_PROPERTY,
value: defaultDaysRemaining,
});
unmockTracking();
......
......@@ -8,24 +8,15 @@ RSpec.describe TrialStatusWidgetHelper do
let(:today_for_specs) { Date.parse('2021-01-15') }
let(:trial_days_remaining) { 18 }
let(:trial_end_date) { Date.current.advance(days: trial_days_remaining) }
let(:trial_start_date) { Date.current.advance(days: trial_days_remaining - trial_length) }
let(:trial_percentage_complete) { (trial_length - trial_days_remaining) * 100 / trial_length }
let(:subscription) { instance_double(GitlabSubscription, plan_title: 'Ultimate') }
let(:group) do
instance_double(Group,
id: 123,
name: 'Pants Group',
to_param: 'pants-group',
gitlab_subscription: subscription,
trial_days_remaining: trial_days_remaining,
trial_ends_on: trial_end_date,
trial_percentage_complete: trial_percentage_complete
)
end
let_it_be(:group) { create(:group) }
let(:shared_expected_attrs) do
{
container_id: 'trial-status-sidebar-widget',
days_remaining: trial_days_remaining,
plan_name: 'Ultimate',
plans_href: group_billings_path(group)
}
......@@ -33,6 +24,11 @@ RSpec.describe TrialStatusWidgetHelper do
before do
travel_to today_for_specs
build(:gitlab_subscription, :active_trial,
namespace: group,
trial_starts_on: trial_start_date,
trial_ends_on: trial_end_date
)
stub_experiments(forcibly_show_trial_status_popover: :candidate)
end
......@@ -46,6 +42,9 @@ RSpec.describe TrialStatusWidgetHelper do
d14_callout_id = described_class::D14_CALLOUT_ID
d3_callout_id = described_class::D3_CALLOUT_ID
let(:user_callouts_feature_id) { nil }
let(:dismissed_callout) { true }
let_it_be(:user) { create(:user) }
before do
......@@ -126,6 +125,10 @@ RSpec.describe TrialStatusWidgetHelper do
with_them { include_examples 'has correct data attributes' }
end
it 'records the experiment subject' do
expect { data_attrs }.to change { ExperimentSubject.count }
end
end
describe '#trial_status_widget_data_attrs' do
......@@ -138,7 +141,6 @@ RSpec.describe TrialStatusWidgetHelper do
it 'returns the needed data attributes for mounting the widget Vue component' do
expect(data_attrs).to match(
shared_expected_attrs.merge(
days_remaining: trial_days_remaining,
nav_icon_image_path: '/image-path/for-file.svg',
percentage_complete: trial_percentage_complete
)
......
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