Commit cadfb1da authored by Miguel Rincon's avatar Miguel Rincon

Merge branch '221066-usage-ping-code-coverage-project-link' into 'master'

Add metrics for group coverage link

See merge request gitlab-org/gitlab!51411
parents 6945e1e5 9bb756e8
---
name: usage_data_i_testing_group_code_coverage_project_click_total
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51411
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299893
milestone: '13.8'
type: development
group: group::testing
default_enabled: true
<script>
import Vue from 'vue';
import { GlCard, GlEmptyState, GlLink, GlSkeletonLoader, GlTable } from '@gitlab/ui';
import api from '~/api';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, s__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
......@@ -19,6 +21,7 @@ export default {
SelectProjectsDropdown,
TimeAgoTooltip,
},
mixins: [glFeatureFlagsMixin()],
apollo: {
projects: {
query: getProjectsTestCoverage,
......@@ -98,6 +101,11 @@ export default {
handleError() {
this.hasError = true;
},
onProjectClick() {
if (this.glFeatures.usageDataITestingGroupCodeCoverageProjectClickTotal) {
api.trackRedisHllUserEvent(this.$options.usagePingProjectEvent);
}
},
selectAllProjects(allProjects) {
this.projectIds = Object.fromEntries(allProjects.map(({ id }) => [id, true]));
this.allProjectsSelected = true;
......@@ -154,6 +162,7 @@ export default {
totalHeight: 15,
},
averageCoverageFormatter: getFormatter(SUPPORTED_FORMATS.percentHundred),
usagePingProjectEvent: 'i_testing_group_code_coverage_project_click_total',
};
</script>
<template>
......@@ -211,7 +220,12 @@ export default {
</template>
<template #cell(project)="{ item }">
<gl-link target="_blank" :href="item.codeCoveragePath" :data-testid="`${item.id}-name`">
<gl-link
target="_blank"
:href="item.codeCoveragePath"
:data-testid="`${item.id}-name`"
@click.once="onProjectClick"
>
{{ item.name }}
</gl-link>
</template>
......
......@@ -7,6 +7,9 @@ class Groups::Analytics::RepositoryAnalyticsController < Groups::Analytics::Appl
before_action :load_group
before_action -> { check_feature_availability!(:group_repository_analytics) }
before_action -> { authorize_view_by_action!(:read_group_repository_analytics) }
before_action only: [:show] do
push_frontend_feature_flag(:usage_data_i_testing_group_code_coverage_project_click_total, @group, default_enabled: :yaml)
end
track_redis_hll_event :show, name: 'i_testing_group_code_coverage_visit_total', feature: :usage_data_i_testing_group_code_coverage_visit_total, feature_default_enabled: true
def show
......
---
title: Capture metrics for group coverage project links
merge_request: 51411
author:
type: added
......@@ -6,6 +6,9 @@ import getProjectsTestCoverage from 'ee/analytics/repository_analytics/graphql/q
import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
jest.mock('~/api.js');
const localVue = createLocalVue();
......@@ -23,6 +26,20 @@ describe('Test coverage table component', () => {
const findProjectCountById = (id) => wrapper.find(`[data-testid="${id}-count"`);
const findProjectDateById = (id) => wrapper.find(`[data-testid="${id}-date"`);
const mockQueryDataNode = {
fullPath: 'test/test',
name: 'test',
id: 1,
repository: {
rootRef: 'master',
},
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedOn: new Date().toISOString(),
},
};
const createComponent = ({ data = {}, mountFn = shallowMount } = {}) => {
wrapper = mountFn(TestCoverageTable, {
localVue,
......@@ -52,6 +69,7 @@ describe('Test coverage table component', () => {
data = {},
mountFn = shallowMount,
queryData = {},
glFeatures = {},
} = {}) => {
localVue.use(VueApollo);
fakeApollo = createMockApollo([
......@@ -72,6 +90,9 @@ describe('Test coverage table component', () => {
};
},
apolloProvider: fakeApollo,
provide: {
glFeatures,
},
});
};
......@@ -144,26 +165,19 @@ describe('Test coverage table component', () => {
yesterday.setDate(yesterday.getDate() - 1);
const allCoverageData = [
{
fullPath: '-',
id: 1,
...mockQueryDataNode,
name: 'should be last',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
...mockQueryDataNode.codeCoverageSummary,
lastUpdatedOn: yesterday.toISOString(),
},
},
{
fullPath: '-',
id: 2,
...mockQueryDataNode,
name: 'should be first',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
id: 2,
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
...mockQueryDataNode.codeCoverageSummary,
lastUpdatedOn: today.toISOString(),
},
},
......@@ -198,17 +212,12 @@ describe('Test coverage table component', () => {
projects: {
nodes: [
{
...mockQueryDataNode,
fullPath,
name: 'test',
id,
repository: {
rootRef,
},
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedOn: new Date().toISOString(),
},
},
],
},
......@@ -222,6 +231,77 @@ describe('Test coverage table component', () => {
expect(findTable().exists()).toBe(true);
expect(findProjectNameById(id).attributes('href')).toBe(expectedPath);
});
describe('with usage metrics', () => {
describe('with :usageDataITestingGroupCodeCoverageProjectClickTotal enabled', () => {
it('tracks i_testing_group_code_coverage_project_click_total metric', async () => {
const id = 1;
createComponentWithApollo({
data: {
projectIds: { [id]: true },
},
queryData: {
data: {
projects: {
nodes: [
{
...mockQueryDataNode,
id,
},
],
},
},
},
mountFn: mount,
glFeatures: { usageDataITestingGroupCodeCoverageProjectClickTotal: true },
});
// We have to wait for apollo to make the mock query and fill the table before
// we can click on the project link inside the table. Neither `runOnlyPendingTimers`
// nor `waitForPromises` work on their own to accomplish this.
jest.runOnlyPendingTimers();
await waitForPromises();
findProjectNameById(id).trigger('click');
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledWith(
wrapper.vm.$options.usagePingProjectEvent,
);
});
});
describe('with :usageDataITestingGroupCodeCoverageProjectClickTotal disabled', () => {
it('does not track i_testing_group_code_coverage_project_click_total metric', async () => {
const id = 1;
createComponentWithApollo({
data: {
projectIds: { [id]: true },
},
queryData: {
data: {
projects: {
nodes: [
{
...mockQueryDataNode,
id,
},
],
},
},
},
mountFn: mount,
glFeatures: { usageDataITestingGroupCodeCoverageProjectClickTotal: false },
});
// We have to wait for apollo to make the mock query and fill the table before
// we can click on the project link inside the table. Neither `runOnlyPendingTimers`
// nor `waitForPromises` work on their own to accomplish this.
jest.runOnlyPendingTimers();
await waitForPromises();
findProjectNameById(id).trigger('click');
expect(Api.trackRedisHllUserEvent).not.toHaveBeenCalled();
});
});
});
});
describe('when selected project has no coverage', () => {
......@@ -236,12 +316,8 @@ describe('Test coverage table component', () => {
projects: {
nodes: [
{
fullPath: 'test/test',
name: 'test',
...mockQueryDataNode,
id,
repository: {
rootRef: 'master',
},
codeCoverageSummary: null,
},
],
......
......@@ -268,6 +268,11 @@
redis_slot: testing
aggregation: weekly
feature_flag: usage_data_i_testing_web_performance_widget_total
- name: i_testing_group_code_coverage_project_click_total
category: testing
redis_slot: testing
aggregation: weekly
feature_flag: usage_data_i_testing_group_code_coverage_project_click_total
# Project Management group
- name: g_project_management_issue_title_changed
category: issues_edit
......
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