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 {
<release-block-header :release="release" />
<div class="card-body">
<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" />
</div>
......
<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 { MAX_MILESTONES_TO_DISPLAY } from '../constants';
/** Sums the values of an array. For use with Array.reduce. */
const sumReducer = (acc, curr) => acc + curr;
import { sum } from 'lodash';
export default {
name: 'ReleaseBlockMilestoneInfo',
......@@ -13,6 +18,7 @@ export default {
GlLink,
GlBadge,
GlButton,
GlSprintf,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -22,6 +28,16 @@ export default {
type: Array,
required: true,
},
openIssuesPath: {
type: String,
required: false,
default: '',
},
closedIssuesPath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
......@@ -42,14 +58,14 @@ export default {
allIssueStats() {
return this.milestones.map(m => m.issueStats || {});
},
openIssuesCount() {
return this.allIssueStats.map(stats => stats.opened || 0).reduce(sumReducer);
totalIssuesCount() {
return sum(this.allIssueStats.map(stats => stats.total || 0));
},
closedIssuesCount() {
return this.allIssueStats.map(stats => stats.closed || 0).reduce(sumReducer);
return sum(this.allIssueStats.map(stats => stats.closed || 0));
},
totalIssuesCount() {
return this.openIssuesCount + this.closedIssuesCount;
openIssuesCount() {
return this.totalIssuesCount - this.closedIssuesCount;
},
milestoneLabelText() {
return n__('Milestone', 'Milestones', this.milestones.length);
......@@ -130,7 +146,27 @@ export default {
{{ __('Issues') }}
<gl-badge pill variant="light" class="font-weight-bold">{{ totalIssuesCount }}</gl-badge>
</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>
</template>
......@@ -6,7 +6,7 @@ class Projects::ReleasesController < Projects::ApplicationController
before_action :release, only: %i[edit show update downloads]
before_action :authorize_read_release!
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_show_page, project, default_enabled: true)
end
......
---
title: Add issue summary to Release blocks on the Releases page
merge_request: 27032
author:
type: added
......@@ -3991,6 +3991,9 @@ msgstr ""
msgid "Closed this %{quick_action_target}."
msgstr ""
msgid "Closed: %{closedIssuesCount}"
msgstr ""
msgid "Closes this %{quick_action_target}."
msgstr ""
......@@ -13636,6 +13639,9 @@ msgstr ""
msgid "Open source software to collaborate on code"
msgstr ""
msgid "Open: %{openIssuesCount}"
msgstr ""
msgid "Open: %{open} • Closed: %{closed}"
msgstr ""
......
......@@ -10,11 +10,9 @@ describe('Release block milestone info', () => {
let wrapper;
let milestones;
const factory = milestonesProp => {
const factory = props => {
wrapper = mount(ReleaseBlockMilestoneInfo, {
propsData: {
milestones: milestonesProp,
},
propsData: props,
});
return wrapper.vm.$nextTick();
......@@ -26,6 +24,7 @@ describe('Release block milestone info', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container');
......@@ -33,7 +32,7 @@ describe('Release block milestone info', () => {
const issuesContainer = () => wrapper.find('.js-issues-container');
describe('with default props', () => {
beforeEach(() => factory(milestones));
beforeEach(() => factory({ milestones }));
it('renders the correct percentage', () => {
expect(milestoneProgressBarContainer().text()).toContain('41% complete');
......@@ -102,7 +101,7 @@ describe('Release block milestone info', () => {
.map(m => m.title)
.join('');
return factory(lotsOfMilestones);
return factory({ milestones: lotsOfMilestones });
});
const clickShowMoreFewerButton = () => {
......@@ -153,12 +152,12 @@ describe('Release block milestone info', () => {
...m,
issueStats: {
...m.issueStats,
opened: 0,
total: 0,
closed: 0,
},
}));
return factory(milestones);
return factory({ milestones });
});
expectAllZeros();
......@@ -171,9 +170,72 @@ describe('Release block milestone info', () => {
issueStats: undefined,
}));
return factory(milestones);
return factory({ milestones });
});
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 = [
start_date: '2019-08-31',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2',
issue_stats: {
opened: 14,
total: 33,
closed: 19,
},
},
......@@ -29,7 +29,7 @@ export const milestones = [
start_date: '2019-08-19',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1',
issue_stats: {
opened: 18,
total: 21,
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