Commit a3458c7a authored by Nathan Friend's avatar Nathan Friend

Add merge requests stats to release progress view

This commit updates the release progress view on each release block to
show statistics about related merge requests (similar to the existing
issue statistics).
parent 766da6c5
...@@ -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