Commit 1c44cc94 authored by Nathan Friend's avatar Nathan Friend

Enable Releases issue summary

This commit enables the issue summary on Release blocks and makes a few
small updates based on the new shape of the milestone API data.
parent ce5562a8
...@@ -93,7 +93,10 @@ export default { ...@@ -93,7 +93,10 @@ export default {
<release-block-header :release="release" /> <release-block-header :release="release" />
<div class="card-body"> <div class="card-body">
<div v-if="shouldRenderMilestoneInfo"> <div v-if="shouldRenderMilestoneInfo">
<release-block-milestone-info :milestones="milestones" /> <release-block-milestone-info
:milestones="milestones"
:open-issues-path="release._links.issuesUrl"
/>
<hr class="mb-3 mt-0" /> <hr class="mb-3 mt-0" />
</div> </div>
......
<script> <script>
import { GlProgressBar, GlLink, GlBadge, GlButton, GlTooltipDirective } from '@gitlab/ui'; import {
GlProgressBar,
GlLink,
GlBadge,
GlButton,
GlTooltipDirective,
GlSprintf,
} from '@gitlab/ui';
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 { sum } from 'lodash';
/** Sums the values of an array. For use with Array.reduce. */
const sumReducer = (acc, curr) => acc + curr;
export default { export default {
name: 'ReleaseBlockMilestoneInfo', name: 'ReleaseBlockMilestoneInfo',
...@@ -13,6 +18,7 @@ export default { ...@@ -13,6 +18,7 @@ export default {
GlLink, GlLink,
GlBadge, GlBadge,
GlButton, GlButton,
GlSprintf,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -22,6 +28,16 @@ export default { ...@@ -22,6 +28,16 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
openIssuesPath: {
type: String,
required: false,
default: '',
},
closedIssuesPath: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -42,14 +58,14 @@ export default { ...@@ -42,14 +58,14 @@ export default {
allIssueStats() { allIssueStats() {
return this.milestones.map(m => m.issueStats || {}); return this.milestones.map(m => m.issueStats || {});
}, },
openIssuesCount() { totalIssuesCount() {
return this.allIssueStats.map(stats => stats.opened || 0).reduce(sumReducer); return sum(this.allIssueStats.map(stats => stats.total || 0));
}, },
closedIssuesCount() { closedIssuesCount() {
return this.allIssueStats.map(stats => stats.closed || 0).reduce(sumReducer); return sum(this.allIssueStats.map(stats => stats.closed || 0));
}, },
totalIssuesCount() { openIssuesCount() {
return this.openIssuesCount + this.closedIssuesCount; return this.totalIssuesCount - this.closedIssuesCount;
}, },
milestoneLabelText() { milestoneLabelText() {
return n__('Milestone', 'Milestones', this.milestones.length); return n__('Milestone', 'Milestones', this.milestones.length);
...@@ -130,7 +146,27 @@ export default { ...@@ -130,7 +146,27 @@ export default {
{{ __('Issues') }} {{ __('Issues') }}
<gl-badge pill variant="light" class="font-weight-bold">{{ totalIssuesCount }}</gl-badge> <gl-badge pill variant="light" class="font-weight-bold">{{ totalIssuesCount }}</gl-badge>
</span> </span>
{{ issueCountsText }} <div class="d-flex">
<gl-link v-if="openIssuesPath" ref="openIssuesLink" :href="openIssuesPath">
<gl-sprintf :message="__('Open: %{openIssuesCount}')">
<template #openIssuesCount>{{ openIssuesCount }}</template>
</gl-sprintf>
</gl-link>
<span v-else ref="openIssuesText">
{{ sprintf(__('Open: %{openIssuesCount}'), { openIssuesCount }) }}
</span>
<span class="mx-1">&bull;</span>
<gl-link v-if="closedIssuesPath" ref="closedIssuesLink" :href="closedIssuesPath">
<gl-sprintf :message="__('Closed: %{closedIssuesCount}')">
<template #closedIssuesCount>{{ closedIssuesCount }}</template>
</gl-sprintf>
</gl-link>
<span v-else ref="closedIssuesText">
{{ sprintf(__('Closed: %{closedIssuesCount}'), { closedIssuesCount }) }}
</span>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -6,7 +6,7 @@ class Projects::ReleasesController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::ReleasesController < Projects::ApplicationController
before_action :release, only: %i[edit show update downloads] before_action :release, only: %i[edit show update downloads]
before_action :authorize_read_release! before_action :authorize_read_release!
before_action do before_action do
push_frontend_feature_flag(:release_issue_summary, project) push_frontend_feature_flag(:release_issue_summary, project, default_enabled: true)
push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true) push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true)
push_frontend_feature_flag(:release_show_page, project, default_enabled: true) push_frontend_feature_flag(:release_show_page, project, default_enabled: true)
end end
......
---
title: Add issue summary to Release blocks on the Releases page
merge_request: 27032
author:
type: added
...@@ -3991,6 +3991,9 @@ msgstr "" ...@@ -3991,6 +3991,9 @@ msgstr ""
msgid "Closed this %{quick_action_target}." msgid "Closed this %{quick_action_target}."
msgstr "" msgstr ""
msgid "Closed: %{closedIssuesCount}"
msgstr ""
msgid "Closes this %{quick_action_target}." msgid "Closes this %{quick_action_target}."
msgstr "" msgstr ""
...@@ -13636,6 +13639,9 @@ msgstr "" ...@@ -13636,6 +13639,9 @@ msgstr ""
msgid "Open source software to collaborate on code" msgid "Open source software to collaborate on code"
msgstr "" msgstr ""
msgid "Open: %{openIssuesCount}"
msgstr ""
msgid "Open: %{open} • Closed: %{closed}" msgid "Open: %{open} • Closed: %{closed}"
msgstr "" msgstr ""
......
...@@ -10,11 +10,9 @@ describe('Release block milestone info', () => { ...@@ -10,11 +10,9 @@ describe('Release block milestone info', () => {
let wrapper; let wrapper;
let milestones; let milestones;
const factory = milestonesProp => { const factory = props => {
wrapper = mount(ReleaseBlockMilestoneInfo, { wrapper = mount(ReleaseBlockMilestoneInfo, {
propsData: { propsData: props,
milestones: milestonesProp,
},
}); });
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
...@@ -26,6 +24,7 @@ describe('Release block milestone info', () => { ...@@ -26,6 +24,7 @@ describe('Release block milestone info', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container'); const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container');
...@@ -33,7 +32,7 @@ describe('Release block milestone info', () => { ...@@ -33,7 +32,7 @@ describe('Release block milestone info', () => {
const issuesContainer = () => wrapper.find('.js-issues-container'); const issuesContainer = () => wrapper.find('.js-issues-container');
describe('with default props', () => { describe('with default props', () => {
beforeEach(() => factory(milestones)); beforeEach(() => factory({ milestones }));
it('renders the correct percentage', () => { it('renders the correct percentage', () => {
expect(milestoneProgressBarContainer().text()).toContain('41% complete'); expect(milestoneProgressBarContainer().text()).toContain('41% complete');
...@@ -102,7 +101,7 @@ describe('Release block milestone info', () => { ...@@ -102,7 +101,7 @@ describe('Release block milestone info', () => {
.map(m => m.title) .map(m => m.title)
.join(''); .join('');
return factory(lotsOfMilestones); return factory({ milestones: lotsOfMilestones });
}); });
const clickShowMoreFewerButton = () => { const clickShowMoreFewerButton = () => {
...@@ -153,12 +152,12 @@ describe('Release block milestone info', () => { ...@@ -153,12 +152,12 @@ describe('Release block milestone info', () => {
...m, ...m,
issueStats: { issueStats: {
...m.issueStats, ...m.issueStats,
opened: 0, total: 0,
closed: 0, closed: 0,
}, },
})); }));
return factory(milestones); return factory({ milestones });
}); });
expectAllZeros(); expectAllZeros();
...@@ -171,9 +170,72 @@ describe('Release block milestone info', () => { ...@@ -171,9 +170,72 @@ describe('Release block milestone info', () => {
issueStats: undefined, issueStats: undefined,
})); }));
return factory(milestones); return factory({ milestones });
}); });
expectAllZeros(); expectAllZeros();
}); });
describe('Issue links', () => {
const findOpenIssuesLink = () => wrapper.find({ ref: 'openIssuesLink' });
const findOpenIssuesText = () => wrapper.find({ ref: 'openIssuesText' });
const findClosedIssuesLink = () => wrapper.find({ ref: 'closedIssuesLink' });
const findClosedIssuesText = () => wrapper.find({ ref: 'closedIssuesText' });
describe('when openIssuePath is provided', () => {
const openIssuesPath = '/path/to/open/issues';
beforeEach(() => {
return factory({ milestones, openIssuesPath });
});
it('renders the open issues as a link', () => {
expect(findOpenIssuesLink().exists()).toBe(true);
expect(findOpenIssuesText().exists()).toBe(false);
});
it('renders the open issues link with the correct href', () => {
expect(findOpenIssuesLink().attributes().href).toBe(openIssuesPath);
});
});
describe('when openIssuePath is not provided', () => {
beforeEach(() => {
return factory({ milestones });
});
it('renders the open issues as plain text', () => {
expect(findOpenIssuesLink().exists()).toBe(false);
expect(findOpenIssuesText().exists()).toBe(true);
});
});
describe('when closedIssuePath is provided', () => {
const closedIssuesPath = '/path/to/closed/issues';
beforeEach(() => {
return factory({ milestones, closedIssuesPath });
});
it('renders the closed issues as a link', () => {
expect(findClosedIssuesLink().exists()).toBe(true);
expect(findClosedIssuesText().exists()).toBe(false);
});
it('renders the closed issues link with the correct href', () => {
expect(findClosedIssuesLink().attributes().href).toBe(closedIssuesPath);
});
});
describe('when closedIssuePath is not provided', () => {
beforeEach(() => {
return factory({ milestones });
});
it('renders the closed issues as plain text', () => {
expect(findClosedIssuesLink().exists()).toBe(false);
expect(findClosedIssuesText().exists()).toBe(true);
});
});
});
}); });
...@@ -12,7 +12,7 @@ export const milestones = [ ...@@ -12,7 +12,7 @@ export const milestones = [
start_date: '2019-08-31', start_date: '2019-08-31',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2', web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2',
issue_stats: { issue_stats: {
opened: 14, total: 33,
closed: 19, closed: 19,
}, },
}, },
...@@ -29,7 +29,7 @@ export const milestones = [ ...@@ -29,7 +29,7 @@ export const milestones = [
start_date: '2019-08-19', start_date: '2019-08-19',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1', web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1',
issue_stats: { issue_stats: {
opened: 18, total: 21,
closed: 3, closed: 3,
}, },
}, },
......
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