Commit ca494e5d authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Track Snowplow events for Error tracking & Alert Management

parent bb6a848d
...@@ -18,10 +18,15 @@ import query from '../graphql/queries/details.query.graphql'; ...@@ -18,10 +18,15 @@ import query from '../graphql/queries/details.query.graphql';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ALERTS_SEVERITY_LABELS } from '../constants'; import {
ALERTS_SEVERITY_LABELS,
trackAlertsDetailsViewsOptions,
trackAlertStatusUpdateOptions,
} from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql'; import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
export default { export default {
statuses: { statuses: {
...@@ -108,6 +113,9 @@ export default { ...@@ -108,6 +113,9 @@ export default {
return this.errored && !this.isErrorDismissed; return this.errored && !this.isErrorDismissed;
}, },
}, },
mounted() {
this.trackPageViews();
},
methods: { methods: {
dismissError() { dismissError() {
this.isErrorDismissed = true; this.isErrorDismissed = true;
...@@ -122,6 +130,9 @@ export default { ...@@ -122,6 +130,9 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
}, },
}) })
.then(() => {
this.trackStatusUpdate(status);
})
.catch(() => { .catch(() => {
createFlash( createFlash(
s__( s__(
...@@ -157,6 +168,14 @@ export default { ...@@ -157,6 +168,14 @@ export default {
issuePath(issueId) { issuePath(issueId) {
return joinPaths(this.projectIssuesPath, issueId); return joinPaths(this.projectIssuesPath, issueId);
}, },
trackPageViews() {
const { category, action } = trackAlertsDetailsViewsOptions;
Tracking.event(category, action);
},
trackStatusUpdate(status) {
const { category, action, label } = trackAlertStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
}, },
}; };
</script> </script>
......
...@@ -19,9 +19,16 @@ import { fetchPolicies } from '~/lib/graphql'; ...@@ -19,9 +19,16 @@ import { fetchPolicies } from '~/lib/graphql';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import getAlerts from '../graphql/queries/get_alerts.query.graphql'; import getAlerts from '../graphql/queries/get_alerts.query.graphql';
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql'; import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants'; import {
ALERTS_STATUS,
ALERTS_STATUS_TABS,
ALERTS_SEVERITY_LABELS,
trackAlertListViewsOptions,
trackAlertStatusUpdateOptions,
} from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
import { capitalizeFirstCharacter, convertToSnakeCase } from '~/lib/utils/text_utility'; import { capitalizeFirstCharacter, convertToSnakeCase } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
const tdClass = 'table-col d-flex d-md-table-cell align-items-center'; const tdClass = 'table-col d-flex d-md-table-cell align-items-center';
const bodyTrClass = const bodyTrClass =
...@@ -182,6 +189,7 @@ export default { ...@@ -182,6 +189,7 @@ export default {
}, },
mounted() { mounted() {
findDefaultSortColumn().ariaSort = 'ascending'; findDefaultSortColumn().ariaSort = 'ascending';
this.trackPageViews();
}, },
methods: { methods: {
filterAlertsByStatus(tabIndex) { filterAlertsByStatus(tabIndex) {
...@@ -208,6 +216,7 @@ export default { ...@@ -208,6 +216,7 @@ export default {
}, },
}) })
.then(() => { .then(() => {
this.trackStatusUpdate(status);
this.$apollo.queries.alerts.refetch(); this.$apollo.queries.alerts.refetch();
this.$apollo.queries.alertsCount.refetch(); this.$apollo.queries.alertsCount.refetch();
}) })
...@@ -222,6 +231,14 @@ export default { ...@@ -222,6 +231,14 @@ export default {
navigateToAlertDetails({ iid }) { navigateToAlertDetails({ iid }) {
return visitUrl(joinPaths(window.location.pathname, iid, 'details')); return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
}, },
trackPageViews() {
const { category, action } = trackAlertListViewsOptions;
Tracking.event(category, action);
},
trackStatusUpdate(status) {
const { category, action, label } = trackAlertStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
}, },
}; };
</script> </script>
......
...@@ -44,3 +44,30 @@ export const ALERTS_STATUS_TABS = [ ...@@ -44,3 +44,30 @@ export const ALERTS_STATUS_TABS = [
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED], filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED],
}, },
]; ];
/* eslint-disable @gitlab/require-i18n-strings */
/**
* Tracks snowplow event when user views alerts list
*/
export const trackAlertListViewsOptions = {
category: 'Alert Management',
action: 'view_alerts_list',
};
/**
* Tracks snowplow event when user views alert details
*/
export const trackAlertsDetailsViewsOptions = {
category: 'Alert Management',
action: 'view_alert_details',
};
/**
* Tracks snowplow event when alert status is updated
*/
export const trackAlertStatusUpdateOptions = {
category: 'Alert Management',
action: 'update_alert_status',
label: 'Status',
};
...@@ -20,8 +20,13 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; ...@@ -20,8 +20,13 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import Stacktrace from './stacktrace.vue'; import Stacktrace from './stacktrace.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event'; import TrackEventDirective from '~/vue_shared/directives/track_event';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { trackClickErrorLinkToSentryOptions } from '../utils';
import { severityLevel, severityLevelVariant, errorStatus } from './constants'; import { severityLevel, severityLevelVariant, errorStatus } from './constants';
import Tracking from '~/tracking';
import {
trackClickErrorLinkToSentryOptions,
trackErrorDetailsViewsOptions,
trackErrorStatusUpdateOptions,
} from '../utils';
import query from '../queries/details.query.graphql'; import query from '../queries/details.query.graphql';
...@@ -172,6 +177,7 @@ export default { ...@@ -172,6 +177,7 @@ export default {
}, },
}, },
mounted() { mounted() {
this.trackPageViews();
this.startPollingStacktrace(this.issueStackTracePath); this.startPollingStacktrace(this.issueStackTracePath);
this.errorPollTimeout = Date.now() + SENTRY_TIMEOUT; this.errorPollTimeout = Date.now() + SENTRY_TIMEOUT;
this.$apollo.queries.error.setOptions({ this.$apollo.queries.error.setOptions({
...@@ -194,7 +200,10 @@ export default { ...@@ -194,7 +200,10 @@ export default {
onIgnoreStatusUpdate() { onIgnoreStatusUpdate() {
const status = const status =
this.errorStatus === errorStatus.IGNORED ? errorStatus.UNRESOLVED : errorStatus.IGNORED; this.errorStatus === errorStatus.IGNORED ? errorStatus.UNRESOLVED : errorStatus.IGNORED;
this.updateIgnoreStatus({ endpoint: this.issueUpdatePath, status }); // eslint-disable-next-line promise/catch-or-return
this.updateIgnoreStatus({ endpoint: this.issueUpdatePath, status }).then(() => {
this.trackStatusUpdate(status);
});
}, },
onResolveStatusUpdate() { onResolveStatusUpdate() {
const status = const status =
...@@ -206,6 +215,7 @@ export default { ...@@ -206,6 +215,7 @@ export default {
if (this.closedIssueId) { if (this.closedIssueId) {
this.isAlertVisible = true; this.isAlertVisible = true;
} }
this.trackStatusUpdate(status);
}); });
}, },
onNoApolloResult() { onNoApolloResult() {
...@@ -218,6 +228,14 @@ export default { ...@@ -218,6 +228,14 @@ export default {
formatDate(date) { formatDate(date) {
return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`; return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
}, },
trackPageViews() {
const { category, action } = trackErrorDetailsViewsOptions;
Tracking.event(category, action);
},
trackStatusUpdate(status) {
const { category, action, label } = trackErrorStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
}, },
}; };
</script> </script>
...@@ -259,7 +277,7 @@ export default { ...@@ -259,7 +277,7 @@ export default {
<div class="d-inline-flex bv-d-sm-down-none"> <div class="d-inline-flex bv-d-sm-down-none">
<gl-deprecated-button <gl-deprecated-button
:loading="updatingIgnoreStatus" :loading="updatingIgnoreStatus"
data-qa-selector="update_ignore_status_button" data-testid="update-ignore-status-btn"
@click="onIgnoreStatusUpdate" @click="onIgnoreStatusUpdate"
> >
{{ ignoreBtnLabel }} {{ ignoreBtnLabel }}
...@@ -267,7 +285,7 @@ export default { ...@@ -267,7 +285,7 @@ export default {
<gl-deprecated-button <gl-deprecated-button
class="btn-outline-info ml-2" class="btn-outline-info ml-2"
:loading="updatingResolveStatus" :loading="updatingResolveStatus"
data-qa-selector="update_resolve_status_button" data-testid="update-resolve-status-btn"
@click="onResolveStatusUpdate" @click="onResolveStatusUpdate"
> >
{{ resolveBtnLabel }} {{ resolveBtnLabel }}
...@@ -275,7 +293,7 @@ export default { ...@@ -275,7 +293,7 @@ export default {
<gl-deprecated-button <gl-deprecated-button
v-if="error.gitlabIssuePath" v-if="error.gitlabIssuePath"
class="ml-2" class="ml-2"
data-qa-selector="view_issue_button" data-testid="view_issue_button"
:href="error.gitlabIssuePath" :href="error.gitlabIssuePath"
variant="success" variant="success"
> >
...@@ -375,6 +393,7 @@ export default { ...@@ -375,6 +393,7 @@ export default {
v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)" v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)"
:href="error.externalUrl" :href="error.externalUrl"
target="_blank" target="_blank"
data-testid="external-url-link"
> >
<span class="text-truncate">{{ error.externalUrl }}</span> <span class="text-truncate">{{ error.externalUrl }}</span>
<icon name="external-link" class="ml-1 flex-shrink-0" /> <icon name="external-link" class="ml-1 flex-shrink-0" />
......
...@@ -19,6 +19,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -19,6 +19,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import ErrorTrackingActions from './error_tracking_actions.vue'; import ErrorTrackingActions from './error_tracking_actions.vue';
import Tracking from '~/tracking';
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils';
export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center'; export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center';
...@@ -150,6 +152,9 @@ export default { ...@@ -150,6 +152,9 @@ export default {
this.startPolling(); this.startPolling();
} }
}, },
mounted() {
this.trackPageViews();
},
methods: { methods: {
...mapActions('list', [ ...mapActions('list', [
'startPolling', 'startPolling',
...@@ -197,13 +202,25 @@ export default { ...@@ -197,13 +202,25 @@ export default {
this.filterValue = label; this.filterValue = label;
return this.filterByStatus(status); return this.filterByStatus(status);
}, },
updateIssueStatus({ errorId, status }) { updateErrosStatus({ errorId, status }) {
// eslint-disable-next-line promise/catch-or-return
this.updateStatus({ this.updateStatus({
endpoint: this.getIssueUpdatePath(errorId), endpoint: this.getIssueUpdatePath(errorId),
status, status,
}).then(() => {
this.trackStatusUpdate(status);
}); });
this.removeIgnoredResolvedErrors(errorId); this.removeIgnoredResolvedErrors(errorId);
}, },
trackPageViews() {
const { category, action } = trackErrorListViewsOptions;
Tracking.event(category, action);
},
trackStatusUpdate(status) {
const { category, action, label } = trackErrorStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
}, },
}; };
</script> </script>
...@@ -359,7 +376,7 @@ export default { ...@@ -359,7 +376,7 @@ export default {
</div> </div>
</template> </template>
<template #cell(status)="errors"> <template #cell(status)="errors">
<error-tracking-actions :error="errors.item" @update-issue-status="updateIssueStatus" /> <error-tracking-actions :error="errors.item" @update-issue-status="updateErrosStatus" />
</template> </template>
<template #empty> <template #empty>
{{ __('No errors to display.') }} {{ __('No errors to display.') }}
......
/* eslint-disable @gitlab/require-i18n-strings, import/prefer-default-export */ /* eslint-disable @gitlab/require-i18n-strings */
/** /**
* Tracks snowplow event when User clicks on error link to Sentry * Tracks snowplow event when User clicks on error link to Sentry
...@@ -10,3 +10,28 @@ export const trackClickErrorLinkToSentryOptions = url => ({ ...@@ -10,3 +10,28 @@ export const trackClickErrorLinkToSentryOptions = url => ({
label: 'Error Link', label: 'Error Link',
property: url, property: url,
}); });
/**
* Tracks snowplow event when user views error list
*/
export const trackErrorListViewsOptions = {
category: 'Error Tracking',
action: 'view_errors_list',
};
/**
* Tracks snowplow event when user views error details
*/
export const trackErrorDetailsViewsOptions = {
category: 'Error Tracking',
action: 'view_error_details',
};
/**
* Tracks snowplow event when error status is updated
*/
export const trackErrorStatusUpdateOptions = {
category: 'Error Tracking',
action: 'update_error_status',
label: 'Status',
};
---
title: Monitor:Health metrics instrumenation
merge_request: 32846
author:
type: added
...@@ -5,6 +5,11 @@ import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert ...@@ -5,6 +5,11 @@ import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert
import createIssueQuery from '~/alert_management/graphql/mutations/create_issue_from_alert.graphql'; import createIssueQuery from '~/alert_management/graphql/mutations/create_issue_from_alert.graphql';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
import {
trackAlertsDetailsViewsOptions,
trackAlertStatusUpdateOptions,
} from '~/alert_management/constants';
import Tracking from '~/tracking';
import mockAlerts from '../mocks/alerts.json'; import mockAlerts from '../mocks/alerts.json';
...@@ -253,7 +258,7 @@ describe('AlertDetails', () => { ...@@ -253,7 +258,7 @@ describe('AlertDetails', () => {
}); });
}); });
describe('updating the alert status', () => { describe('Updating the alert status', () => {
const mockUpdatedMutationResult = { const mockUpdatedMutationResult = {
data: { data: {
updateAlertStatus: { updateAlertStatus: {
...@@ -298,4 +303,31 @@ describe('AlertDetails', () => { ...@@ -298,4 +303,31 @@ describe('AlertDetails', () => {
}); });
}); });
}); });
describe('Snowplow tracking', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alert: mockAlert },
loading: false,
});
});
it('should track alert details page views', () => {
const { category, action } = trackAlertsDetailsViewsOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
it('should track alert status updates', () => {
Tracking.event.mockClear();
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
findStatusDropdownItem().vm.$emit('click');
const status = findStatusDropdownItem().text();
setImmediate(() => {
const { category, action, label } = trackAlertStatusUpdateOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
});
});
});
}); });
...@@ -14,9 +14,14 @@ import { visitUrl } from '~/lib/utils/url_utility'; ...@@ -14,9 +14,14 @@ import { visitUrl } from '~/lib/utils/url_utility';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import createFlash from '~/flash'; import createFlash from '~/flash';
import AlertManagementList from '~/alert_management/components/alert_management_list.vue'; import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
import { ALERTS_STATUS_TABS } from '../../../../app/assets/javascripts/alert_management/constants'; import {
ALERTS_STATUS_TABS,
trackAlertListViewsOptions,
trackAlertStatusUpdateOptions,
} from '~/alert_management/constants';
import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql'; import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql';
import mockAlerts from '../mocks/alerts.json'; import mockAlerts from '../mocks/alerts.json';
import Tracking from '~/tracking';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -94,7 +99,7 @@ describe('AlertManagementList', () => { ...@@ -94,7 +99,7 @@ describe('AlertManagementList', () => {
} }
}); });
describe('alert management feature renders empty state', () => { describe('Empty state', () => {
it('shows empty state', () => { it('shows empty state', () => {
expect(wrapper.find(GlEmptyState).exists()).toBe(true); expect(wrapper.find(GlEmptyState).exists()).toBe(true);
}); });
...@@ -363,4 +368,31 @@ describe('AlertManagementList', () => { ...@@ -363,4 +368,31 @@ describe('AlertManagementList', () => {
}); });
}); });
}); });
describe('Snowplow tracking', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, alertsCount },
loading: false,
});
});
it('should track alert list page views', () => {
const { category, action } = trackAlertListViewsOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
it('should track alert status updates', () => {
Tracking.event.mockClear();
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
findFirstStatusOption().vm.$emit('click');
const status = findFirstStatusOption().text();
setImmediate(() => {
const { category, action, label } = trackAlertStatusUpdateOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
});
});
});
}); });
...@@ -18,6 +18,12 @@ import { ...@@ -18,6 +18,12 @@ import {
severityLevelVariant, severityLevelVariant,
errorStatus, errorStatus,
} from '~/error_tracking/components/constants'; } from '~/error_tracking/components/constants';
import Tracking from '~/tracking';
import {
trackClickErrorLinkToSentryOptions,
trackErrorDetailsViewsOptions,
trackErrorStatusUpdateOptions,
} from '~/error_tracking/utils';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -30,12 +36,19 @@ describe('ErrorDetails', () => { ...@@ -30,12 +36,19 @@ describe('ErrorDetails', () => {
let actions; let actions;
let getters; let getters;
let mocks; let mocks;
const externalUrl = 'https://sentry.io/organizations/test-sentry-nk/issues/1/?project=1';
const findInput = name => { const findInput = name => {
const inputs = wrapper.findAll(GlFormInput).filter(c => c.attributes('name') === name); const inputs = wrapper.findAll(GlFormInput).filter(c => c.attributes('name') === name);
return inputs.length ? inputs.at(0) : inputs; return inputs.length ? inputs.at(0) : inputs;
}; };
const findUpdateIgnoreStatusButton = () =>
wrapper.find('[data-testid="update-ignore-status-btn"]');
const findUpdateResolveStatusButton = () =>
wrapper.find('[data-testid="update-resolve-status-btn"]');
const findExternalUrl = () => wrapper.find('[data-testid="external-url-link"]');
function mountComponent() { function mountComponent() {
wrapper = shallowMount(ErrorDetails, { wrapper = shallowMount(ErrorDetails, {
stubs: { GlDeprecatedButton, GlSprintf }, stubs: { GlDeprecatedButton, GlSprintf },
...@@ -57,7 +70,7 @@ describe('ErrorDetails', () => { ...@@ -57,7 +70,7 @@ describe('ErrorDetails', () => {
beforeEach(() => { beforeEach(() => {
actions = { actions = {
startPollingStacktrace: () => {}, startPollingStacktrace: () => {},
updateIgnoreStatus: jest.fn(), updateIgnoreStatus: jest.fn().mockResolvedValue({}),
updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }), updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }),
}; };
...@@ -302,11 +315,6 @@ describe('ErrorDetails', () => { ...@@ -302,11 +315,6 @@ describe('ErrorDetails', () => {
}); });
describe('Status update', () => { describe('Status update', () => {
const findUpdateIgnoreStatusButton = () =>
wrapper.find('[data-qa-selector="update_ignore_status_button"]');
const findUpdateResolveStatusButton = () =>
wrapper.find('[data-qa-selector="update_resolve_status_button"]');
afterEach(() => { afterEach(() => {
actions.updateIgnoreStatus.mockClear(); actions.updateIgnoreStatus.mockClear();
actions.updateResolveStatus.mockClear(); actions.updateResolveStatus.mockClear();
...@@ -491,4 +499,55 @@ describe('ErrorDetails', () => { ...@@ -491,4 +499,55 @@ describe('ErrorDetails', () => {
}); });
}); });
}); });
describe('Snowplow tracking', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
mocks.$apollo.queries.error.loading = false;
mountComponent();
wrapper.setData({
error: { externalUrl },
});
});
it('should track detail page views', () => {
const { category, action } = trackErrorDetailsViewsOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
it('should track IGNORE status update', () => {
Tracking.event.mockClear();
findUpdateIgnoreStatusButton().vm.$emit('click');
setImmediate(() => {
const { category, action, label } = trackErrorStatusUpdateOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action, {
label,
property: 'ignored',
});
});
});
it('should track RESOLVE status update', () => {
Tracking.event.mockClear();
findUpdateResolveStatusButton().vm.$emit('click');
setImmediate(() => {
const { category, action, label } = trackErrorStatusUpdateOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action, {
label,
property: 'resolved',
});
});
});
it('should track external Sentry link views', () => {
Tracking.event.mockClear();
findExternalUrl().trigger('click');
setImmediate(() => {
const { category, action, label, property } = trackClickErrorLinkToSentryOptions(
externalUrl,
);
expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property });
});
});
});
}); });
...@@ -4,7 +4,9 @@ import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } fr ...@@ -4,7 +4,9 @@ import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } fr
import stubChildren from 'helpers/stub_children'; import stubChildren from 'helpers/stub_children';
import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue'; import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils';
import errorsList from './list_mock.json'; import errorsList from './list_mock.json';
import Tracking from '~/tracking';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -460,4 +462,41 @@ describe('ErrorTrackingList', () => { ...@@ -460,4 +462,41 @@ describe('ErrorTrackingList', () => {
}); });
}); });
}); });
describe('Snowplow tracking', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
store.state.list.loading = false;
store.state.list.errors = errorsList;
mountComponent({
stubs: {
GlTable: false,
GlLink: false,
GlDeprecatedButton: false,
},
});
});
it('should track list views', () => {
const { category, action } = trackErrorListViewsOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
it('should track status updates', () => {
Tracking.event.mockClear();
const status = 'ignored';
findErrorActions().vm.$emit('update-issue-status', {
errorId: 1,
status,
});
setImmediate(() => {
const { category, action, label } = trackErrorStatusUpdateOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action, {
label,
property: status,
});
});
});
});
}); });
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