Commit 15b05b17 authored by Peter Hegman's avatar Peter Hegman Committed by Nicolò Maria Mezzopera

Implement tracking for 2FA recovery code buttons

Track clicks of copy, download, print, and proceed buttons
parent 2ba25d6b
......@@ -2,11 +2,13 @@
import Mousetrap from 'mousetrap';
import { GlSprintf, GlButton, GlAlert } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Tracking from '~/tracking';
import { __ } from '~/locale';
import {
COPY_BUTTON_ACTION,
DOWNLOAD_BUTTON_ACTION,
PRINT_BUTTON_ACTION,
TRACKING_LABEL_PREFIX,
RECOVERY_CODE_DOWNLOAD_FILENAME,
COPY_KEYBOARD_SHORTCUT,
} from '../constants';
......@@ -28,10 +30,12 @@ export default {
copyButtonAction: COPY_BUTTON_ACTION,
downloadButtonAction: DOWNLOAD_BUTTON_ACTION,
printButtonAction: PRINT_BUTTON_ACTION,
trackingLabelPrefix: TRACKING_LABEL_PREFIX,
recoveryCodeDownloadFilename: RECOVERY_CODE_DOWNLOAD_FILENAME,
i18n,
mousetrap: null,
components: { GlSprintf, GlButton, GlAlert, ClipboardButton },
mixins: [Tracking.mixin()],
props: {
codes: {
type: Array,
......@@ -74,6 +78,8 @@ export default {
if (action === this.$options.printButtonAction) {
window.print();
}
this.track('click_button', { label: `${this.$options.trackingLabelPrefix}${action}_button` });
},
handleKeyboardCopy() {
if (!window.getSelection) {
......@@ -84,6 +90,9 @@ export default {
if (copiedText.includes(this.codesAsString)) {
this.proceedButtonDisabled = false;
this.track('copy_keyboard_shortcut', {
label: `${this.$options.trackingLabelPrefix}manual_copy`,
});
}
},
},
......@@ -155,6 +164,8 @@ export default {
:title="$options.i18n.proceedButton"
variant="success"
data-qa-selector="proceed_button"
data-track-event="click_button"
:data-track-label="`${$options.trackingLabelPrefix}proceed_button`"
>{{ $options.i18n.proceedButton }}</gl-button
>
</div>
......
......@@ -2,6 +2,8 @@ export const COPY_BUTTON_ACTION = 'copy';
export const DOWNLOAD_BUTTON_ACTION = 'download';
export const PRINT_BUTTON_ACTION = 'print';
export const TRACKING_LABEL_PREFIX = '2fa_recovery_codes_';
export const RECOVERY_CODE_DOWNLOAD_FILENAME = 'gitlab-recovery-codes.txt';
export const SUCCESS_QUERY_PARAM = 'two_factor_auth_enabled_successfully';
......
......@@ -2,7 +2,8 @@ import { mount } from '@vue/test-utils';
import { GlAlert, GlButton } from '@gitlab/ui';
import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
import { extendedWrapper } from 'jest/helpers/vue_test_utils_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Tracking from '~/tracking';
import RecoveryCodes, {
i18n,
} from '~/authentication/two_factor_auth/components/recovery_codes.vue';
......@@ -42,6 +43,7 @@ describe('RecoveryCodes', () => {
wrapper.vm.$options.mousetrap.trigger(COPY_KEYBOARD_SHORTCUT);
beforeEach(() => {
jest.spyOn(Tracking, 'event');
createComponent();
});
......@@ -73,6 +75,13 @@ describe('RecoveryCodes', () => {
href: profileAccountPath,
});
});
it('fires Snowplow event', () => {
expect(findProceedButton().attributes()).toMatchObject({
'data-track-event': 'click_button',
'data-track-label': '2fa_recovery_codes_proceed_button',
});
});
});
describe('"Copy codes" button', () => {
......@@ -88,13 +97,21 @@ describe('RecoveryCodes', () => {
});
describe('when button is clicked', () => {
it('enables "Proceed" button', async () => {
beforeEach(async () => {
findCopyButton().trigger('click');
await nextTick();
});
it('enables "Proceed" button', () => {
expect(findProceedButton().props('disabled')).toBe(false);
});
it('fires Snowplow event', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: '2fa_recovery_codes_copy_button',
});
});
});
});
......@@ -111,7 +128,7 @@ describe('RecoveryCodes', () => {
});
describe('when button is clicked', () => {
it('enables "Proceed" button', async () => {
beforeEach(async () => {
const downloadButton = findDownloadButton();
// jsdom does not support navigating.
// Since we are clicking an anchor tag there is no way to mock this
......@@ -121,9 +138,17 @@ describe('RecoveryCodes', () => {
downloadButton.trigger('click');
await nextTick();
});
it('enables "Proceed" button', () => {
expect(findProceedButton().props('disabled')).toBe(false);
});
it('fires Snowplow event', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: '2fa_recovery_codes_download_button',
});
});
});
});
......@@ -138,34 +163,48 @@ describe('RecoveryCodes', () => {
});
describe('when button is clicked', () => {
it('enables "Proceed" button and opens print dialog', async () => {
beforeEach(async () => {
window.print = jest.fn();
findPrintButton().trigger('click');
await nextTick();
});
it('enables "Proceed" button and opens print dialog', () => {
expect(findProceedButton().props('disabled')).toBe(false);
expect(window.print).toHaveBeenCalledWith();
expect(window.print).toHaveBeenCalled();
});
it('fires Snowplow event', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: '2fa_recovery_codes_print_button',
});
});
});
});
describe('when codes are manually copied', () => {
describe('when selected text is the recovery codes', () => {
beforeEach(() => {
beforeEach(async () => {
jest.spyOn(window, 'getSelection').mockImplementation(() => ({
toString: jest.fn(() => codesFormattedString),
}));
});
it('enables "Proceed" button', async () => {
manuallyCopyRecoveryCodes();
await nextTick();
});
it('enables "Proceed" button', () => {
expect(findProceedButton().props('disabled')).toBe(false);
});
it('fires Snowplow event', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'copy_keyboard_shortcut', {
label: '2fa_recovery_codes_manual_copy',
});
});
});
describe('when selected text includes the recovery codes', () => {
......
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