Commit e93f722b authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Jacques Erasmus

Add snowplow events for the extended VSA form

parent c1add04d
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlSafeHtmlDirective as SafeHtml, GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui'; } from '@gitlab/ui';
import Tracking from '~/tracking';
import { OVERVIEW_STAGE_ID } from '../constants'; import { OVERVIEW_STAGE_ID } from '../constants';
export default { export default {
...@@ -17,6 +18,7 @@ export default { ...@@ -17,6 +18,7 @@ export default {
directives: { directives: {
SafeHtml, SafeHtml,
}, },
mixins: [Tracking.mixin()],
props: { props: {
loading: { loading: {
type: Boolean, type: Boolean,
...@@ -45,6 +47,14 @@ export default { ...@@ -45,6 +47,14 @@ export default {
hasStageCount({ stageCount = null }) { hasStageCount({ stageCount = null }) {
return stageCount !== null; return stageCount !== null;
}, },
onSelectStage($event) {
this.$emit('selected', $event);
this.track('click_path_navigation', {
extra: {
stage_id: $event.id,
},
});
},
}, },
popoverOptions: { popoverOptions: {
triggers: 'hover', triggers: 'hover',
...@@ -54,7 +64,7 @@ export default { ...@@ -54,7 +64,7 @@ export default {
</script> </script>
<template> <template>
<gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" /> <gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" />
<gl-path v-else :key="selectedStage.id" :items="stages" @selected="$emit('selected', $event)"> <gl-path v-else :key="selectedStage.id" :items="stages" @selected="onSelectStage">
<template #default="{ pathItem, pathId }"> <template #default="{ pathItem, pathId }">
<gl-popover <gl-popover
v-if="showPopover(pathItem)" v-if="showPopover(pathItem)"
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
GlBadge, GlBadge,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Tracking from '~/tracking';
import { import {
NOT_ENOUGH_DATA_ERROR, NOT_ENOUGH_DATA_ERROR,
PAGINATION_SORT_FIELD_END_EVENT, PAGINATION_SORT_FIELD_END_EVENT,
...@@ -42,6 +43,7 @@ export default { ...@@ -42,6 +43,7 @@ export default {
GlBadge, GlBadge,
TotalTime, TotalTime,
}, },
mixins: [Tracking.mixin()],
props: { props: {
selectedStage: { selectedStage: {
type: Object, type: Object,
...@@ -140,15 +142,15 @@ export default { ...@@ -140,15 +142,15 @@ export default {
}, },
onSelectPage(page) { onSelectPage(page) {
const { sort, direction } = this.pagination; const { sort, direction } = this.pagination;
this.track('click_button', { label: 'pagination' });
this.$emit('handleUpdatePagination', { sort, direction, page }); this.$emit('handleUpdatePagination', { sort, direction, page });
}, },
onSort({ sortBy, sortDesc }) { onSort({ sortBy, sortDesc }) {
const direction = sortDesc ? PAGINATION_SORT_DIRECTION_DESC : PAGINATION_SORT_DIRECTION_ASC;
this.sort = sortBy; this.sort = sortBy;
this.sortDesc = sortDesc; this.sortDesc = sortDesc;
this.$emit('handleUpdatePagination', { this.$emit('handleUpdatePagination', { sort: sortBy, direction });
sort: sortBy, this.track('click_button', { label: `sort_${sortBy}_${direction}` });
direction: sortDesc ? PAGINATION_SORT_DIRECTION_DESC : PAGINATION_SORT_DIRECTION_ASC,
});
}, },
}, },
}; };
......
...@@ -5,6 +5,7 @@ import Vue from 'vue'; ...@@ -5,6 +5,7 @@ import Vue from 'vue';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { swapArrayItems } from '~/lib/utils/array_utility'; import { swapArrayItems } from '~/lib/utils/array_utility';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import Tracking from '~/tracking';
import { import {
STAGE_SORT_DIRECTION, STAGE_SORT_DIRECTION,
i18n, i18n,
...@@ -41,6 +42,7 @@ export default { ...@@ -41,6 +42,7 @@ export default {
DefaultStageFields, DefaultStageFields,
CustomStageFields, CustomStageFields,
}, },
mixins: [Tracking.mixin()],
props: { props: {
initialData: { initialData: {
type: Object, type: Object,
...@@ -169,6 +171,9 @@ export default { ...@@ -169,6 +171,9 @@ export default {
this.nameError = []; this.nameError = [];
this.stages = initializeStages(this.defaultStageConfig, this.selectedPreset); this.stages = initializeStages(this.defaultStageConfig, this.selectedPreset);
this.stageErrors = initializeStageErrors(this.defaultStageConfig, this.selectedPreset); this.stageErrors = initializeStageErrors(this.defaultStageConfig, this.selectedPreset);
this.track('submit_form', {
label: this.isEditing ? 'edit_value_stream' : 'create_value_stream',
});
} }
}); });
}, },
......
...@@ -10,7 +10,9 @@ import { ...@@ -10,7 +10,9 @@ import {
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { slugifyWithUnderscore } from '~/lib/utils/text_utility';
import { sprintf, __, s__ } from '~/locale'; import { sprintf, __, s__ } from '~/locale';
import Tracking from '~/tracking';
import { generateInitialStageData } from './create_value_stream_form/utils'; import { generateInitialStageData } from './create_value_stream_form/utils';
import ValueStreamForm from './value_stream_form.vue'; import ValueStreamForm from './value_stream_form.vue';
...@@ -40,6 +42,7 @@ export default { ...@@ -40,6 +42,7 @@ export default {
directives: { directives: {
GlModalDirective, GlModalDirective,
}, },
mixins: [Tracking.mixin()],
data() { data() {
return { return {
showCreateModal: false, showCreateModal: false,
...@@ -94,6 +97,7 @@ export default { ...@@ -94,6 +97,7 @@ export default {
return this.deleteValueStream(this.selectedValueStreamId).then(() => { return this.deleteValueStream(this.selectedValueStreamId).then(() => {
if (!this.deleteValueStreamError) { if (!this.deleteValueStreamError) {
this.onSuccess(sprintf(this.$options.i18n.DELETED, { name })); this.onSuccess(sprintf(this.$options.i18n.DELETED, { name }));
this.track('delete_value_stream', { extra: { name } });
} }
}); });
}, },
...@@ -113,6 +117,9 @@ export default { ...@@ -113,6 +117,9 @@ export default {
stages: generateInitialStageData(this.defaultStageConfig, this.selectedValueStreamStages), stages: generateInitialStageData(this.defaultStageConfig, this.selectedValueStreamStages),
}; };
}, },
slugify(valueStreamTitle) {
return slugifyWithUnderscore(valueStreamTitle);
},
}, },
i18n, i18n,
}; };
...@@ -123,6 +130,8 @@ export default { ...@@ -123,6 +130,8 @@ export default {
v-if="isCustomValueStream" v-if="isCustomValueStream"
v-gl-modal-directive="'value-stream-form-modal'" v-gl-modal-directive="'value-stream-form-modal'"
data-testid="edit-value-stream" data-testid="edit-value-stream"
data-track-action="click_button"
data-track-label="edit_value_stream_form_open"
@click="onEdit" @click="onEdit"
>{{ $options.i18n.EDIT_VALUE_STREAM }}</gl-button >{{ $options.i18n.EDIT_VALUE_STREAM }}</gl-button
> >
...@@ -137,6 +146,8 @@ export default { ...@@ -137,6 +146,8 @@ export default {
:key="id" :key="id"
:is-check-item="true" :is-check-item="true"
:is-checked="isSelected(id)" :is-checked="isSelected(id)"
data-track-action="click_dropdown"
:data-track-label="slugify(streamName)"
@click="onSelect(id)" @click="onSelect(id)"
>{{ streamName }}</gl-dropdown-item >{{ streamName }}</gl-dropdown-item
> >
...@@ -144,6 +155,8 @@ export default { ...@@ -144,6 +155,8 @@ export default {
<gl-dropdown-item <gl-dropdown-item
v-gl-modal-directive="'value-stream-form-modal'" v-gl-modal-directive="'value-stream-form-modal'"
data-testid="create-value-stream" data-testid="create-value-stream"
data-track-action="click_dropdown"
data-track-label="create_value_stream_form_open"
@click="onCreate" @click="onCreate"
>{{ $options.i18n.CREATE_VALUE_STREAM }}</gl-dropdown-item >{{ $options.i18n.CREATE_VALUE_STREAM }}</gl-dropdown-item
> >
...@@ -152,6 +165,8 @@ export default { ...@@ -152,6 +165,8 @@ export default {
v-gl-modal-directive="'delete-value-stream-modal'" v-gl-modal-directive="'delete-value-stream-modal'"
variant="danger" variant="danger"
data-testid="delete-value-stream" data-testid="delete-value-stream"
data-track-action="click_dropdown"
data-track-label="delete_value_stream_form_open"
> >
<gl-sprintf :message="$options.i18n.DELETE_NAME"> <gl-sprintf :message="$options.i18n.DELETE_NAME">
<template #name>{{ selectedValueStreamName }}</template> <template #name>{{ selectedValueStreamName }}</template>
...@@ -162,6 +177,8 @@ export default { ...@@ -162,6 +177,8 @@ export default {
v-else v-else
v-gl-modal-directive="'value-stream-form-modal'" v-gl-modal-directive="'value-stream-form-modal'"
data-testid="create-value-stream-button" data-testid="create-value-stream-button"
data-track-action="click_button"
data-track-label="create_value_stream_form_open"
@click="onCreate" @click="onCreate"
>{{ $options.i18n.CREATE_VALUE_STREAM }}</gl-button >{{ $options.i18n.CREATE_VALUE_STREAM }}</gl-button
> >
......
...@@ -2,6 +2,7 @@ import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui'; ...@@ -2,6 +2,7 @@ import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import StageTable from 'ee/analytics/cycle_analytics/components/stage_table.vue'; import StageTable from 'ee/analytics/cycle_analytics/components/stage_table.vue';
import { PAGINATION_SORT_FIELD_DURATION } from 'ee/analytics/cycle_analytics/constants'; import { PAGINATION_SORT_FIELD_DURATION } from 'ee/analytics/cycle_analytics/constants';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { import {
stagingEvents, stagingEvents,
...@@ -15,6 +16,7 @@ import { ...@@ -15,6 +16,7 @@ import {
} from '../mock_data'; } from '../mock_data';
let wrapper = null; let wrapper = null;
let trackingSpy = null;
const noDataSvgPath = 'path/to/no/data'; const noDataSvgPath = 'path/to/no/data';
const emptyStateMessage = 'Too much data'; const emptyStateMessage = 'Too much data';
...@@ -281,6 +283,12 @@ describe('StageTable', () => { ...@@ -281,6 +283,12 @@ describe('StageTable', () => {
describe('Pagination', () => { describe('Pagination', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
wrapper.destroy();
}); });
it('will display the pagination component', () => { it('will display the pagination component', () => {
...@@ -294,6 +302,12 @@ describe('StageTable', () => { ...@@ -294,6 +302,12 @@ describe('StageTable', () => {
expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([{ page: 2 }]); expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([{ page: 2 }]);
}); });
it('clicking prev or next will send tracking information', () => {
findPagination().vm.$emit('input', 2);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', { label: 'pagination' });
});
describe('with `hasNextPage=false', () => { describe('with `hasNextPage=false', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ pagination: { page: 1, hasNextPage: false } }); wrapper = createComponent({ pagination: { page: 1, hasNextPage: false } });
...@@ -306,16 +320,33 @@ describe('StageTable', () => { ...@@ -306,16 +320,33 @@ describe('StageTable', () => {
}); });
describe('Sorting', () => { describe('Sorting', () => {
const triggerTableSort = (sortDesc = true) =>
findTable().vm.$emit('sort-changed', {
sortBy: PAGINATION_SORT_FIELD_DURATION,
sortDesc,
});
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
it('clicking a table column will update the sort field', () => { afterEach(() => {
findTable().vm.$emit('sort-changed', { unmockTracking();
sortBy: PAGINATION_SORT_FIELD_DURATION, wrapper.destroy();
sortDesc: true,
}); });
it('clicking a table column will send tracking information', () => {
triggerTableSort();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'sort_duration_desc',
});
});
it('clicking a table column will update the sort field', () => {
triggerTableSort();
expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([ expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([
{ {
direction: 'desc', direction: 'desc',
...@@ -325,10 +356,7 @@ describe('StageTable', () => { ...@@ -325,10 +356,7 @@ describe('StageTable', () => {
}); });
it('with sortDesc=false will toggle the direction field', async () => { it('with sortDesc=false will toggle the direction field', async () => {
findTable().vm.$emit('sort-changed', { triggerTableSort(false);
sortBy: PAGINATION_SORT_FIELD_DURATION,
sortDesc: false,
});
expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([ expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([
{ {
......
...@@ -5,6 +5,7 @@ import { PRESET_OPTIONS_BLANK } from 'ee/analytics/cycle_analytics/components/cr ...@@ -5,6 +5,7 @@ import { PRESET_OPTIONS_BLANK } from 'ee/analytics/cycle_analytics/components/cr
import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue'; import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue';
import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue'; import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue';
import ValueStreamForm from 'ee/analytics/cycle_analytics/components/value_stream_form.vue'; import ValueStreamForm from 'ee/analytics/cycle_analytics/components/value_stream_form.vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { import {
convertObjectPropsToCamelCase, convertObjectPropsToCamelCase,
...@@ -20,6 +21,7 @@ localVue.use(Vuex); ...@@ -20,6 +21,7 @@ localVue.use(Vuex);
describe('ValueStreamForm', () => { describe('ValueStreamForm', () => {
let wrapper = null; let wrapper = null;
let trackingSpy = null;
const createValueStreamMock = jest.fn(() => Promise.resolve()); const createValueStreamMock = jest.fn(() => Promise.resolve());
const updateValueStreamMock = jest.fn(() => Promise.resolve()); const updateValueStreamMock = jest.fn(() => Promise.resolve());
...@@ -239,6 +241,7 @@ describe('ValueStreamForm', () => { ...@@ -239,6 +241,7 @@ describe('ValueStreamForm', () => {
describe('with valid fields', () => { describe('with valid fields', () => {
beforeEach(() => { beforeEach(() => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper = createComponent({ wrapper = createComponent({
props: { props: {
initialPreset, initialPreset,
...@@ -248,6 +251,11 @@ describe('ValueStreamForm', () => { ...@@ -248,6 +251,11 @@ describe('ValueStreamForm', () => {
}); });
}); });
afterEach(() => {
unmockTracking();
wrapper.destroy();
});
describe('form submitted successfully', () => { describe('form submitted successfully', () => {
beforeEach(() => { beforeEach(() => {
clickSubmit(); clickSubmit();
...@@ -267,6 +275,12 @@ describe('ValueStreamForm', () => { ...@@ -267,6 +275,12 @@ describe('ValueStreamForm', () => {
position: 'top-center', position: 'top-center',
}); });
}); });
it('sends tracking information', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'submit_form', {
label: 'edit_value_stream',
});
});
}); });
describe('form submission fails', () => { describe('form submission fails', () => {
...@@ -315,6 +329,12 @@ describe('ValueStreamForm', () => { ...@@ -315,6 +329,12 @@ describe('ValueStreamForm', () => {
describe('with valid fields', () => { describe('with valid fields', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ data: { name: streamName } }); wrapper = createComponent({ data: { name: streamName } });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
wrapper.destroy();
}); });
describe('form submitted successfully', () => { describe('form submitted successfully', () => {
...@@ -351,6 +371,12 @@ describe('ValueStreamForm', () => { ...@@ -351,6 +371,12 @@ describe('ValueStreamForm', () => {
position: 'top-center', position: 'top-center',
}); });
}); });
it('sends tracking information', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'submit_form', {
label: 'create_value_stream',
});
});
}); });
describe('form submission fails', () => { describe('form submission fails', () => {
......
...@@ -2,6 +2,7 @@ import { GlDropdown } from '@gitlab/ui'; ...@@ -2,6 +2,7 @@ import { GlDropdown } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue'; import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { findDropdownItemText } from '../helpers'; import { findDropdownItemText } from '../helpers';
import { valueStreams, defaultStageConfig } from '../mock_data'; import { valueStreams, defaultStageConfig } from '../mock_data';
...@@ -11,6 +12,7 @@ localVue.use(Vuex); ...@@ -11,6 +12,7 @@ localVue.use(Vuex);
describe('ValueStreamSelect', () => { describe('ValueStreamSelect', () => {
let wrapper = null; let wrapper = null;
let trackingSpy = null;
const deleteValueStreamMock = jest.fn(() => Promise.resolve()); const deleteValueStreamMock = jest.fn(() => Promise.resolve());
const mockEvent = { preventDefault: jest.fn() }; const mockEvent = { preventDefault: jest.fn() };
...@@ -68,9 +70,11 @@ describe('ValueStreamSelect', () => { ...@@ -68,9 +70,11 @@ describe('ValueStreamSelect', () => {
valueStreams, valueStreams,
}, },
}); });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
afterEach(() => { afterEach(() => {
unmockTracking();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -203,6 +207,12 @@ describe('ValueStreamSelect', () => { ...@@ -203,6 +207,12 @@ describe('ValueStreamSelect', () => {
}, },
); );
}); });
it('sends tracking information', () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'delete_value_stream', {
extra: { name: selectedValueStream.name },
});
});
}); });
describe('fails', () => { describe('fails', () => {
......
import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Component from '~/cycle_analytics/components/path_navigation.vue'; import Component from '~/cycle_analytics/components/path_navigation.vue';
import { transformedProjectStagePathData, selectedStage } from './mock_data'; import { transformedProjectStagePathData, selectedStage } from './mock_data';
describe('Project PathNavigation', () => { describe('Project PathNavigation', () => {
let wrapper = null; let wrapper = null;
let trackingSpy = null;
const createComponent = (props) => { const createComponent = (props) => {
return extendedWrapper( return extendedWrapper(
...@@ -43,9 +45,11 @@ describe('Project PathNavigation', () => { ...@@ -43,9 +45,11 @@ describe('Project PathNavigation', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}); });
afterEach(() => { afterEach(() => {
unmockTracking();
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
}); });
...@@ -132,5 +136,13 @@ describe('Project PathNavigation', () => { ...@@ -132,5 +136,13 @@ describe('Project PathNavigation', () => {
[transformedProjectStagePathData[2]], [transformedProjectStagePathData[2]],
]); ]);
}); });
it('sends tracking information', () => {
clickItemAt(0);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_path_navigation', {
extra: { stage_id: selectedStage.slug },
});
});
}); });
}); });
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