Commit 66a34007 authored by Scott Hampton's avatar Scott Hampton

Refactor chart and tests

Refactored the props for the area chart to be
more in line with what echarts expects.

Updated the specs to test a little more of the data in the chart.

Refactored the specs to use shallowMount.

Updated the documentation to point to the repo
analytics documentation.
parent ab9c3439
......@@ -854,19 +854,7 @@ With [GitLab Issue Analytics](issues_analytics/index.md), you can see a bar char
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in GitLab 13.6.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/276003) in GitLab 13.7.
With [GitLab Repositories Analytics](repositories_analytics/index.md), you can download a CSV of the latest coverage data for all the projects in your group.
### Check code coverage for all projects
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.7.
See the overall activity of all projects with code coverage with [GitLab Repositories Analytics](repositories_analytics/index.md).
It displays the current code coverage data available for your projects. In
[GitLab 13.9 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/215140), it
also displays a graph of the average code coverage over the last 30 days:
![Group repositories analytics](img/group_code_coverage_analytics_v13_9.png)
With [GitLab Repositories Analytics](repositories_analytics/index.md), you can view overall activity of all projects with code coverage.
## Dependency Proxy
......
......@@ -12,6 +12,25 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
![Group repositories analytics](../img/group_code_coverage_analytics_v13_9.png)
## Current group code coverage
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.7.
The **Analytics > Repositories** group page displays the overall test coverage of all your projects in your group.
In the **Overall activity** section, you can see:
- The number of projects with coverage reports.
- The average percentage of coverage across all your projects.
- The total number of pipeline jobs that produce coverage reports.
## Average group test coverage from the last 30 days
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215140) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.9.
The **Analytics > Repositories** group page displays the average test coverage of all your projects in your group in a graph for the last 30 days.
## Latest project test coverage list
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267624) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
......
......@@ -5,8 +5,11 @@ import { __, s__ } from '~/locale';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import getGroupTestCoverage from '../graphql/queries/get_group_test_coverage.query.graphql';
const formatPercent = getFormatter(SUPPORTED_FORMATS.percentHundred);
export default {
name: 'TestCoverageSummary',
components: {
......@@ -29,7 +32,7 @@ export default {
return {
groupFullPath: this.groupFullPath,
startDate: new Date(Date.now() - THIRTY_DAYS),
startDate: formatDate(new Date(Date.now() - THIRTY_DAYS), 'yyyy-mm-dd'),
};
},
result({ data }) {
......@@ -42,11 +45,8 @@ export default {
this.coverageCount = coverageCount;
this.groupCoverageChartData = [
{
name: this.$options.text.graphName,
data: groupCoverage.map((coverage) => [
formatDate(coverage.date, 'mmm dd'),
coverage.averageCoverage,
]),
name: this.$options.i18n.graphName,
data: groupCoverage.map((coverage) => [coverage.date, coverage.averageCoverage]),
},
];
},
......@@ -80,34 +80,44 @@ export default {
{
key: 'projectCount',
value: this.projectCount,
label: this.$options.text.metrics.projectCountLabel,
label: this.$options.i18n.metrics.projectCountLabel,
},
{
key: 'averageCoverage',
value: this.averageCoverage,
unit: '%',
label: this.$options.text.metrics.averageCoverageLabel,
label: this.$options.i18n.metrics.averageCoverageLabel,
},
{
key: 'coverageCount',
value: this.coverageCount,
label: this.$options.text.metrics.coverageCountLabel,
label: this.$options.i18n.metrics.coverageCountLabel,
},
];
},
chartOptions() {
return {
xAxis: {
name: this.$options.text.xAxisName,
type: 'category',
name: this.$options.i18n.xAxisName,
type: 'time',
axisLabel: {
formatter: (value) => formatDate(value, 'mmm dd'),
},
},
yAxis: {
name: this.$options.text.yAxisName,
name: this.$options.i18n.yAxisName,
type: 'value',
min: 0,
max: 100,
axisLabel: {
formatter: (value) => `${value}%`,
/**
* We can't do `formatter: formatPercent` because
* formatter passes in a second argument of index, which
* formatPercent takes in as the number of decimal points
* we should include after. This formats 100 as 100.00000%
* instead of 100%.
*/
formatter: (value) => formatPercent(value),
},
},
};
......@@ -115,18 +125,18 @@ export default {
},
methods: {
formatTooltipText(params) {
this.tooltipTitle = params.value;
this.coveragePercentage = params.seriesData?.[0]?.data?.[1];
this.tooltipTitle = formatDate(params.value, 'mmm dd');
this.coveragePercentage = formatPercent(params.seriesData?.[0]?.data?.[1], 2);
},
},
text: {
i18n: {
graphCardHeader: s__('RepositoriesAnalytics|Average test coverage last 30 days'),
yAxisName: __('Coverage'),
xAxisName: __('Date'),
graphName: s__('RepositoriesAnalytics|Average coverage'),
graphTooltipMessage: __('Code Coverage: %{coveragePercentage}%{percentSymbol}'),
graphTooltipMessage: __('Code Coverage: %{coveragePercentage}'),
metrics: {
cardTitle: __('Overall Activity'),
cardTitle: __('Overall activity'),
projectCountLabel: s__('RepositoriesAnalytics|Projects with Coverage'),
averageCoverageLabel: s__('RepositoriesAnalytics|Average Coverage by Job'),
coverageCountLabel: s__('RepositoriesAnalytics|Jobs with Coverage'),
......@@ -137,14 +147,14 @@ export default {
<template>
<div>
<metric-card
:title="$options.text.metrics.cardTitle"
:title="$options.i18n.metrics.cardTitle"
:metrics="metrics"
:is-loading="isLoading"
/>
<gl-card>
<template #header>
<h5>{{ $options.text.graphCardHeader }}</h5>
<h5>{{ $options.i18n.graphCardHeader }}</h5>
</template>
<chart-skeleton-loader v-if="isLoading" data-testid="group-coverage-chart-loading" />
......@@ -161,11 +171,10 @@ export default {
{{ tooltipTitle }}
</template>
<template #tooltip-content>
<gl-sprintf :message="$options.text.graphTooltipMessage">
<gl-sprintf :message="$options.i18n.graphTooltipMessage">
<template #coveragePercentage>
{{ coveragePercentage }}
</template>
<template #percentSymbol>%</template>
</gl-sprintf>
</template>
</gl-area-chart>
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Test coverage table component when group code coverage is available renders area chart with correct data 1`] = `
Array [
Object {
"data": Array [
Array [
"2020-01-10",
77.9,
],
Array [
"2020-01-11",
78.7,
],
Array [
"2020-01-12",
79.6,
],
],
"name": "test",
},
]
`;
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import TestCoverageSummary from 'ee/analytics/repository_analytics/components/test_coverage_summary.vue';
import getGroupTestCoverage from 'ee/analytics/repository_analytics/graphql/queries/get_group_test_coverage.query.graphql';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
const localVue = createLocalVue();
describe('Test coverage table component', () => {
let wrapper;
let fakeApollo;
const findProjectsWithTests = () => wrapper.find('.js-metric-card-item:nth-child(1) h3');
const findAverageCoverage = () => wrapper.find('.js-metric-card-item:nth-child(2) h3');
......@@ -20,38 +16,34 @@ describe('Test coverage table component', () => {
const findChartLoadingState = () => wrapper.findByTestId('group-coverage-chart-loading');
const findLoadingState = () => wrapper.find(GlSkeletonLoading);
const createComponent = ({ data = {} } = {}, withApollo = false) => {
fakeApollo = createMockApollo([[getGroupTestCoverage, jest.fn().mockResolvedValue()]]);
const props = {
localVue,
data() {
return {
projectCount: null,
averageCoverage: null,
coverageCount: null,
hasError: false,
isLoading: false,
...data,
};
},
};
if (withApollo) {
localVue.use(VueApollo);
props.apolloProvider = fakeApollo;
} else {
props.mocks = {
$apollo: {
queries: {
group: {
query: jest.fn().mockResolvedValue(),
const createComponent = ({ data = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(TestCoverageSummary, {
localVue,
data() {
return {
projectCount: null,
averageCoverage: null,
coverageCount: null,
hasError: false,
isLoading: false,
...data,
};
},
mocks: {
$apollo: {
queries: {
group: {
query: jest.fn().mockResolvedValue(),
},
},
},
},
};
}
wrapper = extendedWrapper(mount(TestCoverageSummary, props));
stubs: {
MetricCard,
},
}),
);
};
afterEach(() => {
......@@ -97,16 +89,16 @@ describe('Test coverage table component', () => {
expect(findTotalCoverages().text()).toBe(coverageCount);
});
it('renders area chart', () => {
it('renders area chart with correct data', () => {
createComponent({
data: {
groupCoverageChartData: [
{
name: 'test',
data: [
['Jan 10', 77.9],
['Jan 11', 78.7],
['Jan 12', 79.6],
['2020-01-10', 77.9],
['2020-01-11', 78.7],
['2020-01-12', 79.6],
],
},
],
......@@ -114,30 +106,29 @@ describe('Test coverage table component', () => {
});
expect(findGroupCoverageChart().exists()).toBe(true);
expect(findGroupCoverageChart().props('data')).toMatchSnapshot();
});
});
describe('when group has no coverage', () => {
it('renders empty metrics', async () => {
it('formats the area chart labels correctly', () => {
createComponent({
withApollo: true,
data: {},
queryData: {
data: {
group: {
codeCoverageActivities: {
nodes: [],
},
data: {
groupCoverageChartData: [
{
name: 'test',
data: [
['2020-01-10', 77.9],
['2020-01-11', 78.7],
['2020-01-12', 79.6],
],
},
},
],
},
});
jest.runOnlyPendingTimers();
await waitForPromises();
expect(findProjectsWithTests().text()).toBe('-');
expect(findAverageCoverage().text()).toBe('-');
expect(findTotalCoverages().text()).toBe('-');
expect(findGroupCoverageChart().props('option').xAxis.axisLabel.formatter('2020-01-10')).toBe(
'Jan 10',
);
expect(findGroupCoverageChart().props('option').yAxis.axisLabel.formatter(80)).toBe('80%');
});
});
});
......@@ -7196,6 +7196,9 @@ msgstr ""
msgid "Code"
msgstr ""
msgid "Code Coverage: %{coveragePercentage}"
msgstr ""
msgid "Code Coverage: %{coveragePercentage}%{percentSymbol}"
msgstr ""
......@@ -20916,7 +20919,7 @@ msgstr ""
msgid "Outdent"
msgstr ""
msgid "Overall Activity"
msgid "Overall activity"
msgstr ""
msgid "Overridden"
......
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