Commit 6b3867a3 authored by anna_vovchenko's avatar anna_vovchenko Committed by Anna Vovchenko

Added tracking events for the validation errors

To collect the metrics on ci variables creation
we are adding tracking events
for the validation errors in the ci_variable modal

Changelog: added
parent 78226340
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Tracking from '~/tracking';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { mapComputed } from '~/vuex_shared/bindings'; import { mapComputed } from '~/vuex_shared/bindings';
import { import {
...@@ -25,10 +26,14 @@ import { ...@@ -25,10 +26,14 @@ import {
AWS_TIP_DISMISSED_COOKIE_NAME, AWS_TIP_DISMISSED_COOKIE_NAME,
AWS_TIP_MESSAGE, AWS_TIP_MESSAGE,
CONTAINS_VARIABLE_REFERENCE_MESSAGE, CONTAINS_VARIABLE_REFERENCE_MESSAGE,
EVENT_LABEL,
EVENT_ACTION,
} from '../constants'; } from '../constants';
import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; import CiEnvironmentsDropdown from './ci_environments_dropdown.vue';
import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens'; import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens';
const trackingMixin = Tracking.mixin({ label: EVENT_LABEL });
export default { export default {
modalId: ADD_CI_VARIABLE_MODAL_ID, modalId: ADD_CI_VARIABLE_MODAL_ID,
tokens: awsTokens, tokens: awsTokens,
...@@ -51,10 +56,14 @@ export default { ...@@ -51,10 +56,14 @@ export default {
GlModal, GlModal,
GlSprintf, GlSprintf,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin(), trackingMixin],
data() { data() {
return { return {
isTipDismissed: Cookies.get(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true', isTipDismissed: Cookies.get(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true',
isValidationErrorEventSent: {
displaysMaskedError: false,
displaysVariableReferenceError: false,
},
}; };
}, },
computed: { computed: {
...@@ -147,6 +156,15 @@ export default { ...@@ -147,6 +156,15 @@ export default {
return this.variable.secret_value === '' || (this.tokenValidationState && this.maskedState); return this.variable.secret_value === '' || (this.tokenValidationState && this.maskedState);
}, },
}, },
watch: {
variable: {
handler() {
this.trackVariableValidationErrors();
},
deep: true,
immediate: true,
},
},
methods: { methods: {
...mapActions([ ...mapActions([
'addVariable', 'addVariable',
...@@ -179,6 +197,7 @@ export default { ...@@ -179,6 +197,7 @@ export default {
this.clearModal(); this.clearModal();
this.resetSelectedEnvironment(); this.resetSelectedEnvironment();
this.resetValidationErrorEvents();
}, },
updateOrAddVariable() { updateOrAddVariable() {
if (this.variableBeingEdited) { if (this.variableBeingEdited) {
...@@ -193,6 +212,25 @@ export default { ...@@ -193,6 +212,25 @@ export default {
this.setVariableProtected(); this.setVariableProtected();
} }
}, },
trackVariableValidationErrors() {
if (this.displayMaskedError && !this.isValidationErrorEventSent.displaysMaskedError) {
this.track(EVENT_ACTION, { property: 'displaysMaskedError' });
this.isValidationErrorEventSent.displaysMaskedError = true;
}
if (
this.containsVariableReference &&
!this.isValidationErrorEventSent.displaysVariableReferenceError
) {
this.track(EVENT_ACTION, { property: 'displaysVariableReferenceError' });
this.isValidationErrorEventSent.displaysVariableReferenceError = true;
}
},
resetValidationErrorEvents() {
this.isValidationErrorEventSent = {
displaysMaskedError: false,
displaysVariableReferenceError: false,
};
},
}, },
}; };
</script> </script>
......
...@@ -19,6 +19,9 @@ export const AWS_TIP_MESSAGE = __( ...@@ -19,6 +19,9 @@ export const AWS_TIP_MESSAGE = __(
'%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}.', '%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}.',
); );
export const EVENT_LABEL = 'ci_variable_modal';
export const EVENT_ACTION = 'validation_error';
// AWS TOKEN CONSTANTS // AWS TOKEN CONSTANTS
export const AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID'; export const AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID';
export const AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION'; export const AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION';
......
import { GlButton, GlFormInput } from '@gitlab/ui'; import { GlButton, GlFormInput } from '@gitlab/ui';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue'; import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue'; import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants'; import { AWS_ACCESS_KEY_ID, EVENT_LABEL, EVENT_ACTION } from '~/ci_variable_list/constants';
import createStore from '~/ci_variable_list/store'; import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data'; import mockData from '../services/mock_data';
import ModalStub from '../stubs'; import ModalStub from '../stubs';
...@@ -14,6 +15,7 @@ localVue.use(Vuex); ...@@ -14,6 +15,7 @@ localVue.use(Vuex);
describe('Ci variable modal', () => { describe('Ci variable modal', () => {
let wrapper; let wrapper;
let store; let store;
let trackingSpy;
const createComponent = (method, options = {}) => { const createComponent = (method, options = {}) => {
store = createStore({ isGroup: options.isGroup }); store = createStore({ isGroup: options.isGroup });
...@@ -124,10 +126,10 @@ describe('Ci variable modal', () => { ...@@ -124,10 +126,10 @@ describe('Ci variable modal', () => {
}); });
describe.each` describe.each`
value | secret | rendered value | secret | rendered | event_sent
${'value'} | ${'secret_value'} | ${false} ${'value'} | ${'secret_value'} | ${false} | ${0}
${'dollar$ign'} | ${'dollar$ign'} | ${true} ${'dollar$ign'} | ${'dollar$ign'} | ${true} | ${1}
`('Adding a new variable', ({ value, secret, rendered }) => { `('Adding a new variable', ({ value, secret, rendered, event_sent }) => {
beforeEach(() => { beforeEach(() => {
const [variable] = mockData.mockVariables; const [variable] = mockData.mockVariables;
const invalidKeyVariable = { const invalidKeyVariable = {
...@@ -138,12 +140,17 @@ describe('Ci variable modal', () => { ...@@ -138,12 +140,17 @@ describe('Ci variable modal', () => {
}; };
createComponent(mount); createComponent(mount);
store.state.variable = invalidKeyVariable; store.state.variable = invalidKeyVariable;
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
it(`${rendered ? 'renders' : 'does not render'} the variable reference warning`, () => { it(`${rendered ? 'renders' : 'does not render'} the variable reference warning`, () => {
const warning = wrapper.find(`[data-testid='contains-variable-reference']`); const warning = wrapper.find(`[data-testid='contains-variable-reference']`);
expect(warning.exists()).toBe(rendered); expect(warning.exists()).toBe(rendered);
}); });
it(`${rendered ? 'sends' : 'does not send'} the variable reference tracking event`, () => {
expect(trackingSpy).toHaveBeenCalledTimes(event_sent);
});
}); });
describe('Editing a variable', () => { describe('Editing a variable', () => {
...@@ -226,6 +233,7 @@ describe('Ci variable modal', () => { ...@@ -226,6 +233,7 @@ describe('Ci variable modal', () => {
}; };
createComponent(mount); createComponent(mount);
store.state.variable = invalidMaskVariable; store.state.variable = invalidMaskVariable;
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
it('disables the submit button', () => { it('disables the submit button', () => {
...@@ -235,6 +243,13 @@ describe('Ci variable modal', () => { ...@@ -235,6 +243,13 @@ describe('Ci variable modal', () => {
it('shows the correct error text', () => { it('shows the correct error text', () => {
expect(findModal().text()).toContain(maskError); expect(findModal().text()).toContain(maskError);
}); });
it('sends the correct tracking event', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
label: EVENT_LABEL,
property: 'displaysMaskedError',
});
});
}); });
describe('when both states are valid', () => { describe('when both states are valid', () => {
......
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