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> <script>
import Vue from 'vue'; import Vue from 'vue';
import { GlCard, GlEmptyState, GlLink, GlSkeletonLoader, GlTable } from '@gitlab/ui'; 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 { __, s__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
...@@ -19,6 +21,7 @@ export default { ...@@ -19,6 +21,7 @@ export default {
SelectProjectsDropdown, SelectProjectsDropdown,
TimeAgoTooltip, TimeAgoTooltip,
}, },
mixins: [glFeatureFlagsMixin()],
apollo: { apollo: {
projects: { projects: {
query: getProjectsTestCoverage, query: getProjectsTestCoverage,
...@@ -98,6 +101,11 @@ export default { ...@@ -98,6 +101,11 @@ export default {
handleError() { handleError() {
this.hasError = true; this.hasError = true;
}, },
onProjectClick() {
if (this.glFeatures.usageDataITestingGroupCodeCoverageProjectClickTotal) {
api.trackRedisHllUserEvent(this.$options.usagePingProjectEvent);
}
},
selectAllProjects(allProjects) { selectAllProjects(allProjects) {
this.projectIds = Object.fromEntries(allProjects.map(({ id }) => [id, true])); this.projectIds = Object.fromEntries(allProjects.map(({ id }) => [id, true]));
this.allProjectsSelected = true; this.allProjectsSelected = true;
...@@ -154,6 +162,7 @@ export default { ...@@ -154,6 +162,7 @@ export default {
totalHeight: 15, totalHeight: 15,
}, },
averageCoverageFormatter: getFormatter(SUPPORTED_FORMATS.percentHundred), averageCoverageFormatter: getFormatter(SUPPORTED_FORMATS.percentHundred),
usagePingProjectEvent: 'i_testing_group_code_coverage_project_click_total',
}; };
</script> </script>
<template> <template>
...@@ -211,7 +220,12 @@ export default { ...@@ -211,7 +220,12 @@ export default {
</template> </template>
<template #cell(project)="{ item }"> <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 }} {{ item.name }}
</gl-link> </gl-link>
</template> </template>
......
...@@ -7,6 +7,9 @@ class Groups::Analytics::RepositoryAnalyticsController < Groups::Analytics::Appl ...@@ -7,6 +7,9 @@ class Groups::Analytics::RepositoryAnalyticsController < Groups::Analytics::Appl
before_action :load_group before_action :load_group
before_action -> { check_feature_availability!(:group_repository_analytics) } before_action -> { check_feature_availability!(:group_repository_analytics) }
before_action -> { authorize_view_by_action!(:read_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 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 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 ...@@ -6,6 +6,9 @@ import getProjectsTestCoverage from 'ee/analytics/repository_analytics/graphql/q
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
jest.mock('~/api.js');
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -23,6 +26,20 @@ describe('Test coverage table component', () => { ...@@ -23,6 +26,20 @@ describe('Test coverage table component', () => {
const findProjectCountById = (id) => wrapper.find(`[data-testid="${id}-count"`); const findProjectCountById = (id) => wrapper.find(`[data-testid="${id}-count"`);
const findProjectDateById = (id) => wrapper.find(`[data-testid="${id}-date"`); 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 } = {}) => { const createComponent = ({ data = {}, mountFn = shallowMount } = {}) => {
wrapper = mountFn(TestCoverageTable, { wrapper = mountFn(TestCoverageTable, {
localVue, localVue,
...@@ -52,6 +69,7 @@ describe('Test coverage table component', () => { ...@@ -52,6 +69,7 @@ describe('Test coverage table component', () => {
data = {}, data = {},
mountFn = shallowMount, mountFn = shallowMount,
queryData = {}, queryData = {},
glFeatures = {},
} = {}) => { } = {}) => {
localVue.use(VueApollo); localVue.use(VueApollo);
fakeApollo = createMockApollo([ fakeApollo = createMockApollo([
...@@ -72,6 +90,9 @@ describe('Test coverage table component', () => { ...@@ -72,6 +90,9 @@ describe('Test coverage table component', () => {
}; };
}, },
apolloProvider: fakeApollo, apolloProvider: fakeApollo,
provide: {
glFeatures,
},
}); });
}; };
...@@ -144,26 +165,19 @@ describe('Test coverage table component', () => { ...@@ -144,26 +165,19 @@ describe('Test coverage table component', () => {
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
const allCoverageData = [ const allCoverageData = [
{ {
fullPath: '-', ...mockQueryDataNode,
id: 1,
name: 'should be last', name: 'should be last',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
codeCoverageSummary: { codeCoverageSummary: {
averageCoverage: '1.45', ...mockQueryDataNode.codeCoverageSummary,
coverageCount: '1',
lastUpdatedOn: yesterday.toISOString(), lastUpdatedOn: yesterday.toISOString(),
}, },
}, },
{ {
fullPath: '-', ...mockQueryDataNode,
id: 2,
name: 'should be first', name: 'should be first',
repository: { rootRef: 'master' }, id: 2,
codeCoveragePath: '#',
codeCoverageSummary: { codeCoverageSummary: {
averageCoverage: '1.45', ...mockQueryDataNode.codeCoverageSummary,
coverageCount: '1',
lastUpdatedOn: today.toISOString(), lastUpdatedOn: today.toISOString(),
}, },
}, },
...@@ -198,17 +212,12 @@ describe('Test coverage table component', () => { ...@@ -198,17 +212,12 @@ describe('Test coverage table component', () => {
projects: { projects: {
nodes: [ nodes: [
{ {
...mockQueryDataNode,
fullPath, fullPath,
name: 'test',
id, id,
repository: { repository: {
rootRef, rootRef,
}, },
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedOn: new Date().toISOString(),
},
}, },
], ],
}, },
...@@ -222,6 +231,77 @@ describe('Test coverage table component', () => { ...@@ -222,6 +231,77 @@ describe('Test coverage table component', () => {
expect(findTable().exists()).toBe(true); expect(findTable().exists()).toBe(true);
expect(findProjectNameById(id).attributes('href')).toBe(expectedPath); 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', () => { describe('when selected project has no coverage', () => {
...@@ -236,12 +316,8 @@ describe('Test coverage table component', () => { ...@@ -236,12 +316,8 @@ describe('Test coverage table component', () => {
projects: { projects: {
nodes: [ nodes: [
{ {
fullPath: 'test/test', ...mockQueryDataNode,
name: 'test',
id, id,
repository: {
rootRef: 'master',
},
codeCoverageSummary: null, codeCoverageSummary: null,
}, },
], ],
......
...@@ -268,6 +268,11 @@ ...@@ -268,6 +268,11 @@
redis_slot: testing redis_slot: testing
aggregation: weekly aggregation: weekly
feature_flag: usage_data_i_testing_web_performance_widget_total 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 # Project Management group
- name: g_project_management_issue_title_changed - name: g_project_management_issue_title_changed
category: issues_edit 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