Commit c0f74cfd authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '267730-2fa-ux-save-recovery-codes-add-tracking' into 'master'

Implement tracking for 2FA recovery code buttons

See merge request gitlab-org/gitlab!49510
parents f75df3c0 15b05b17
......@@ -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