Commit cf093daa authored by Scott Hampton's avatar Scott Hampton Committed by Jose Ivan Vargas

Show multiple builds for coverage

The code coverage number comes from
multiple builds, so we need to clarify to
the user where the number comes from.
parent e25d35f7
<script> <script>
/* eslint-disable vue/require-default-prop, vue/no-v-html */ /* eslint-disable vue/require-default-prop, vue/no-v-html */
import { GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import {
GlIcon,
GlLink,
GlLoadingIcon,
GlSprintf,
GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui';
import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline'; import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
import { s__ } from '~/locale'; import { s__, n__ } from '~/locale';
import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue'; import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
...@@ -15,6 +22,7 @@ export default { ...@@ -15,6 +22,7 @@ export default {
GlLoadingIcon, GlLoadingIcon,
GlIcon, GlIcon,
GlSprintf, GlSprintf,
GlTooltip,
PipelineStage, PipelineStage,
TooltipOnTruncate, TooltipOnTruncate,
LinkedPipelinesMiniList: () => LinkedPipelinesMiniList: () =>
...@@ -33,6 +41,11 @@ export default { ...@@ -33,6 +41,11 @@ export default {
type: String, type: String,
required: false, required: false,
}, },
buildsWithCoverage: {
type: Array,
required: false,
default: () => [],
},
// This prop needs to be camelCase, html attributes are case insensive // This prop needs to be camelCase, html attributes are case insensive
// https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case // https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
hasCi: { hasCi: {
...@@ -100,6 +113,16 @@ export default { ...@@ -100,6 +113,16 @@ export default {
} }
return ''; return '';
}, },
pipelineCoverageJobNumberText() {
return n__('from %d job', 'from %d jobs', this.buildsWithCoverage.length);
},
pipelineCoverageTooltipDescription() {
return n__(
'Coverage value for this pipeline was calculated by the coverage value of %d job.',
'Coverage value for this pipeline was calculated by averaging the resulting coverage values of %d jobs.',
this.buildsWithCoverage.length,
);
},
}, },
errorText: s__( errorText: s__(
'Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}.', 'Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}.',
...@@ -139,7 +162,7 @@ export default { ...@@ -139,7 +162,7 @@ export default {
> >
<gl-icon <gl-icon
name="question" name="question"
:small="12" :size="12"
tabindex="0" tabindex="0"
role="text" role="text"
:aria-label="__('Link to go to GitLab pipeline documentation')" :aria-label="__('Link to go to GitLab pipeline documentation')"
...@@ -189,14 +212,30 @@ export default { ...@@ -189,14 +212,30 @@ export default {
</div> </div>
<div v-if="pipeline.coverage" class="coverage" data-testid="pipeline-coverage"> <div v-if="pipeline.coverage" class="coverage" data-testid="pipeline-coverage">
{{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}% {{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
<span <span
v-if="pipelineCoverageDelta" v-if="pipelineCoverageDelta"
:class="coverageDeltaClass" :class="coverageDeltaClass"
data-testid="pipeline-coverage-delta" data-testid="pipeline-coverage-delta"
>({{ pipelineCoverageDelta }}%)</span
> >
({{ pipelineCoverageDelta }}%)
{{ pipelineCoverageJobNumberText }}
<span ref="pipelineCoverageQuestion">
<gl-icon name="question" :size="12" />
</span> </span>
<gl-tooltip
:target="() => $refs.pipelineCoverageQuestion"
data-testid="pipeline-coverage-tooltip"
>
{{ pipelineCoverageTooltipDescription }}
<div
v-for="(build, index) in buildsWithCoverage"
:key="`${build.name}-${index}`"
class="gl-mt-3 gl-text-left gl-px-4"
>
{{ build.name }} ({{ build.coverage }}%)
</div>
</gl-tooltip>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -77,6 +77,7 @@ export default { ...@@ -77,6 +77,7 @@ export default {
<mr-widget-pipeline <mr-widget-pipeline
:pipeline="pipeline" :pipeline="pipeline"
:pipeline-coverage-delta="mr.pipelineCoverageDelta" :pipeline-coverage-delta="mr.pipelineCoverageDelta"
:builds-with-coverage="mr.buildsWithCoverage"
:ci-status="mr.ciStatus" :ci-status="mr.ciStatus"
:has-ci="mr.hasCI" :has-ci="mr.hasCI"
:pipeline-must-succeed="mr.onlyAllowMergeIfPipelineSucceeds" :pipeline-must-succeed="mr.onlyAllowMergeIfPipelineSucceeds"
......
...@@ -52,6 +52,7 @@ export default class MergeRequestStore { ...@@ -52,6 +52,7 @@ export default class MergeRequestStore {
this.divergedCommitsCount = data.diverged_commits_count; this.divergedCommitsCount = data.diverged_commits_count;
this.pipeline = data.pipeline || {}; this.pipeline = data.pipeline || {};
this.pipelineCoverageDelta = data.pipeline_coverage_delta; this.pipelineCoverageDelta = data.pipeline_coverage_delta;
this.buildsWithCoverage = data.builds_with_coverage;
this.mergePipeline = data.merge_pipeline || {}; this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || []; this.deployments = this.deployments || data.deployments || [];
this.postMergeDeployments = this.postMergeDeployments || []; this.postMergeDeployments = this.postMergeDeployments || [];
......
---
title: Show multiple jobs contributing to code coverage
merge_request: 41217
author:
type: added
...@@ -7102,6 +7102,11 @@ msgstr "" ...@@ -7102,6 +7102,11 @@ msgstr ""
msgid "Coverage Fuzzing" msgid "Coverage Fuzzing"
msgstr "" msgstr ""
msgid "Coverage value for this pipeline was calculated by the coverage value of %d job."
msgid_plural "Coverage value for this pipeline was calculated by averaging the resulting coverage values of %d jobs."
msgstr[0] ""
msgstr[1] ""
msgid "Create" msgid "Create"
msgstr "" msgstr ""
...@@ -29512,6 +29517,11 @@ msgstr "" ...@@ -29512,6 +29517,11 @@ msgstr ""
msgid "from" msgid "from"
msgstr "" msgstr ""
msgid "from %d job"
msgid_plural "from %d jobs"
msgstr[0] ""
msgstr[1] ""
msgid "group" msgid "group"
msgstr "" msgstr ""
......
...@@ -29,6 +29,8 @@ describe('MRWidgetPipeline', () => { ...@@ -29,6 +29,8 @@ describe('MRWidgetPipeline', () => {
const findAllPipelineStages = () => wrapper.findAll(PipelineStage); const findAllPipelineStages = () => wrapper.findAll(PipelineStage);
const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]'); const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]');
const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]'); const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]');
const findPipelineCoverageTooltipText = () =>
wrapper.find('[data-testid="pipeline-coverage-tooltip"]').text();
const findMonitoringPipelineMessage = () => const findMonitoringPipelineMessage = () =>
wrapper.find('[data-testid="monitoring-pipeline-message"]'); wrapper.find('[data-testid="monitoring-pipeline-message"]');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
...@@ -140,6 +142,7 @@ describe('MRWidgetPipeline', () => { ...@@ -140,6 +142,7 @@ describe('MRWidgetPipeline', () => {
createWrapper( createWrapper(
{ {
pipelineCoverageDelta: mockData.pipelineCoverageDelta, pipelineCoverageDelta: mockData.pipelineCoverageDelta,
buildsWithCoverage: mockData.buildsWithCoverage,
}, },
mount, mount,
); );
...@@ -178,6 +181,22 @@ describe('MRWidgetPipeline', () => { ...@@ -178,6 +181,22 @@ describe('MRWidgetPipeline', () => {
expect(findPipelineCoverageDelta().exists()).toBe(true); expect(findPipelineCoverageDelta().exists()).toBe(true);
expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`); expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`);
}); });
it('should render tooltip for jobs contributing to code coverage', () => {
const tooltipText = findPipelineCoverageTooltipText();
const expectedDescription = `Coverage value for this pipeline was calculated by averaging the resulting coverage values of ${mockData.buildsWithCoverage.length} jobs.`;
expect(tooltipText).toContain(expectedDescription);
});
it.each(mockData.buildsWithCoverage)(
'should have name and coverage for build %s listed in tooltip',
build => {
const tooltipText = findPipelineCoverageTooltipText();
expect(tooltipText).toContain(`${build.name} (${build.coverage}%)`);
},
);
}); });
describe('without commit path', () => { describe('without commit path', () => {
......
...@@ -193,6 +193,7 @@ export default { ...@@ -193,6 +193,7 @@ export default {
updated_at: '2017-04-07T15:28:44.800Z', updated_at: '2017-04-07T15:28:44.800Z',
}, },
pipelineCoverageDelta: '15.25', pipelineCoverageDelta: '15.25',
buildsWithCoverage: [{ name: 'karma', coverage: '40.2' }, { name: 'rspec', coverage: '80.4' }],
work_in_progress: false, work_in_progress: false,
source_branch_exists: false, source_branch_exists: false,
mergeable_discussions_state: true, mergeable_discussions_state: true,
......
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