Commit 5beac427 authored by Nathan Friend's avatar Nathan Friend

Merge branch 'nfriend-add-mr-stats-to-release-progress-view' into 'master'

Add merge request stats to release progress view

See merge request gitlab-org/gitlab!46080
parents 2cfd792a a3458c7a
...@@ -54,9 +54,7 @@ export default { ...@@ -54,9 +54,7 @@ export default {
</script> </script>
<template> <template>
<div <div class="gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5">
class="gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5 js-issues-container"
>
<span class="gl-mb-2"> <span class="gl-mb-2">
{{ label }} {{ label }}
<gl-badge variant="muted" size="sm">{{ total }}</gl-badge> <gl-badge variant="muted" size="sm">{{ total }}</gl-badge>
......
<script> <script>
import { GlProgressBar, GlLink, GlButton, GlTooltipDirective } from '@gitlab/ui'; import { GlProgressBar, GlLink, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { sum } from 'lodash';
import { __, n__, sprintf } from '~/locale'; import { __, n__, sprintf } from '~/locale';
import { MAX_MILESTONES_TO_DISPLAY } from '../constants'; import { MAX_MILESTONES_TO_DISPLAY } from '../constants';
import IssuableStats from './issuable_stats.vue'; import IssuableStats from './issuable_stats.vue';
...@@ -31,6 +30,21 @@ export default { ...@@ -31,6 +30,21 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
openMergeRequestsPath: {
type: String,
required: false,
default: '',
},
mergedMergeRequestsPath: {
type: String,
required: false,
default: '',
},
closedMergeRequestsPath: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -45,17 +59,45 @@ export default { ...@@ -45,17 +59,45 @@ export default {
}); });
}, },
percentComplete() { percentComplete() {
const percent = Math.round((this.closedIssuesCount / this.totalIssuesCount) * 100); const percent = Math.round((this.issueCounts.closed / this.issueCounts.total) * 100);
return Number.isNaN(percent) ? 0 : percent; return Number.isNaN(percent) ? 0 : percent;
}, },
allIssueStats() { issueCounts() {
return this.milestones.map(m => m.issueStats || {}); return this.milestones
.map(m => m.issueStats || {})
.reduce(
(acc, current) => {
acc.total += current.total || 0;
acc.closed += current.closed || 0;
return acc;
},
{
total: 0,
closed: 0,
},
);
}, },
totalIssuesCount() { showMergeRequestStats() {
return sum(this.allIssueStats.map(stats => stats.total || 0)); return this.milestones.some(m => m.mrStats);
}, },
closedIssuesCount() { mergeRequestCounts() {
return sum(this.allIssueStats.map(stats => stats.closed || 0)); return this.milestones
.map(m => m.mrStats || {})
.reduce(
(acc, current) => {
acc.total += current.total || 0;
acc.merged += current.merged || 0;
acc.closed += current.closed || 0;
return acc;
},
{
total: 0,
merged: 0,
closed: 0,
},
);
}, },
milestoneLabelText() { milestoneLabelText() {
return n__('Milestone', 'Milestones', this.milestones.length); return n__('Milestone', 'Milestones', this.milestones.length);
...@@ -98,7 +140,7 @@ export default { ...@@ -98,7 +140,7 @@ export default {
> >
<span class="gl-mb-3">{{ percentCompleteText }}</span> <span class="gl-mb-3">{{ percentCompleteText }}</span>
<span class="gl-w-full"> <span class="gl-w-full">
<gl-progress-bar :value="closedIssuesCount" :max="totalIssuesCount" variant="success" /> <gl-progress-bar :value="issueCounts.closed" :max="issueCounts.total" variant="success" />
</span> </span>
</div> </div>
<div <div
...@@ -129,10 +171,22 @@ export default { ...@@ -129,10 +171,22 @@ export default {
</div> </div>
<issuable-stats <issuable-stats
:label="__('Issues')" :label="__('Issues')"
:total="totalIssuesCount" :total="issueCounts.total"
:closed="closedIssuesCount" :closed="issueCounts.closed"
:open-path="openIssuesPath" :open-path="openIssuesPath"
:closed-path="closedIssuesPath" :closed-path="closedIssuesPath"
data-testid="issue-stats"
/>
<issuable-stats
v-if="showMergeRequestStats"
:label="__('Merge Requests')"
:total="mergeRequestCounts.total"
:merged="mergeRequestCounts.merged"
:closed="mergeRequestCounts.closed"
:open-path="openMergeRequestsPath"
:merged-path="mergedMergeRequestsPath"
:closed-path="closedMergeRequestsPath"
data-testid="merge-request-stats"
/> />
</div> </div>
</template> </template>
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`~/releases/components/issuable_stats.vue matches snapshot 1`] = ` exports[`~/releases/components/issuable_stats.vue matches snapshot 1`] = `
"<div class=\\"gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5 js-issues-container\\"><span class=\\"gl-mb-2\\"> "<div class=\\"gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5\\"><span class=\\"gl-mb-2\\">
Items Items
<span class=\\"badge badge-muted badge-pill gl-badge sm\\"><!----> 10</span></span> <span class=\\"badge badge-muted badge-pill gl-badge sm\\"><!----> 10</span></span>
<div class=\\"gl-display-flex\\"><span data-testid=\\"open-stat\\" class=\\"gl-white-space-pre-wrap\\">Open: <a href=\\"path/to/open/items\\" class=\\"gl-link\\">1</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"merged-stat\\" class=\\"gl-white-space-pre-wrap\\">Merged: <a href=\\"path/to/merged/items\\" class=\\"gl-link\\">7</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"closed-stat\\" class=\\"gl-white-space-pre-wrap\\">Closed: <a href=\\"path/to/closed/items\\" class=\\"gl-link\\">2</a></span></div> <div class=\\"gl-display-flex\\"><span data-testid=\\"open-stat\\" class=\\"gl-white-space-pre-wrap\\">Open: <a href=\\"path/to/open/items\\" class=\\"gl-link\\">1</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"merged-stat\\" class=\\"gl-white-space-pre-wrap\\">Merged: <a href=\\"path/to/merged/items\\" class=\\"gl-link\\">7</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"closed-stat\\" class=\\"gl-white-space-pre-wrap\\">Closed: <a href=\\"path/to/closed/items\\" class=\\"gl-link\\">2</a></span></div>
......
...@@ -31,7 +31,8 @@ describe('Release block milestone info', () => { ...@@ -31,7 +31,8 @@ describe('Release block milestone info', () => {
const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container'); const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container');
const milestoneListContainer = () => wrapper.find('.js-milestone-list-container'); const milestoneListContainer = () => wrapper.find('.js-milestone-list-container');
const issuesContainer = () => wrapper.find('.js-issues-container'); const issuesContainer = () => wrapper.find('[data-testid="issue-stats"]');
const mergeRequestsContainer = () => wrapper.find('[data-testid="merge-request-stats"]');
describe('with default props', () => { describe('with default props', () => {
beforeEach(() => factory({ milestones })); beforeEach(() => factory({ milestones }));
...@@ -187,4 +188,33 @@ describe('Release block milestone info', () => { ...@@ -187,4 +188,33 @@ describe('Release block milestone info', () => {
expectAllZeros(); expectAllZeros();
}); });
describe('if the API response is missing the "mr_stats" property', () => {
beforeEach(() => factory({ milestones }));
it('does not render merge request stats', () => {
expect(mergeRequestsContainer().exists()).toBe(false);
});
});
describe('if the API response includes the "mr_stats" property', () => {
beforeEach(() => {
milestones = milestones.map(m => ({
...m,
mrStats: {
total: 15,
merged: 12,
closed: 1,
},
}));
return factory({ milestones });
});
it('renders merge request stats', () => {
expect(trimText(mergeRequestsContainer().text())).toBe(
'Merge Requests 30 Open: 4 • Merged: 24 • Closed: 2',
);
});
});
}); });
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