summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Friend <nathan@gitlab.com>2019-09-11 16:03:53 -0300
committerNathan Friend <nathan@gitlab.com>2019-09-11 16:03:53 -0300
commitf73aa5580bbfec9636155165d44d5c647a7f0961 (patch)
tree74ec753726d8e2665f39139b9db865d6bd414b12
parent6db9cbfe77a556ea2d321f0ea0a0c6b6e14a817c (diff)
downloadgitlab-ce-nfriend-add-release-footer-2.tar.gz
Add related issue and MR links to release blocksnfriend-add-release-footer-2
This commit adds two links to the bottom of release blocks - related issues and merge requests - if the release is associated to a milestone. At the moment, this commit assumes that a release can only be associated to a single milestone, but this will change in upcoming releases.
-rw-r--r--app/assets/javascripts/releases/components/app.vue14
-rw-r--r--app/assets/javascripts/releases/components/issue_merge_request_links.vue47
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue21
-rw-r--r--app/assets/javascripts/releases/index.js4
-rw-r--r--changelogs/unreleased/nfriend-add-release-footer-2.yml5
-rw-r--r--locale/gitlab.pot4
-rw-r--r--spec/frontend/releases/components/issue_merge_request_links_spec.js96
-rw-r--r--spec/frontend/releases/mock_data.js97
8 files changed, 282 insertions, 6 deletions
diff --git a/app/assets/javascripts/releases/components/app.vue b/app/assets/javascripts/releases/components/app.vue
index 5a06c4fec58..adb4cd57531 100644
--- a/app/assets/javascripts/releases/components/app.vue
+++ b/app/assets/javascripts/releases/components/app.vue
@@ -15,7 +15,7 @@ export default {
type: String,
required: true,
},
- documentationLink: {
+ documentationPath: {
type: String,
required: true,
},
@@ -23,6 +23,14 @@ export default {
type: String,
required: true,
},
+ issuesUrl: {
+ type: String,
+ required: true,
+ },
+ mergeRequestsUrl: {
+ type: String,
+ required: true,
+ },
},
computed: {
...mapState(['isLoading', 'releases', 'hasError']),
@@ -55,7 +63,7 @@ export default {
'Releases mark specific points in a project\'s development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API.',
)
"
- :primary-button-link="documentationLink"
+ :primary-button-link="documentationPath"
:primary-button-text="__('Open Documentation')"
/>
@@ -65,6 +73,8 @@ export default {
:key="release.tag_name"
:release="release"
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
+ :issues-url="issuesUrl"
+ :merge-requests-url="mergeRequestsUrl"
/>
</div>
</div>
diff --git a/app/assets/javascripts/releases/components/issue_merge_request_links.vue b/app/assets/javascripts/releases/components/issue_merge_request_links.vue
new file mode 100644
index 00000000000..6eb4f97b3a2
--- /dev/null
+++ b/app/assets/javascripts/releases/components/issue_merge_request_links.vue
@@ -0,0 +1,47 @@
+<script>
+import { s__, sprintf } from '~/locale';
+import _ from 'underscore';
+
+export default {
+ name: 'IssueMergeRequestLinks',
+ props: {
+ milestone: {
+ type: Object,
+ required: true,
+ },
+ issuesUrl: {
+ type: String,
+ required: true,
+ },
+ mergeRequestsUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ queryParam() {
+ return `milestone_title=${encodeURIComponent(_.escape(this.milestone.title))}`;
+ },
+ linkText() {
+ const linkAttrs = 'target="_blank" rel="noopener noreferrer"';
+
+ return sprintf(
+ s__(
+ 'Releases|View %{issuesLinkStart}Issues%{linkEnd} or %{mrsLinkStart}Merge Requests%{linkEnd} in this release',
+ ),
+ {
+ issuesLinkStart: `<a href="${_.escape(this.issuesUrl)}&${this.queryParam}" ${linkAttrs}>`,
+ mrsLinkStart: `<a href="${_.escape(this.mergeRequestsUrl)}&${
+ this.queryParam
+ }" ${linkAttrs}>`,
+ linkEnd: '</a>',
+ },
+ false,
+ );
+ },
+ },
+};
+</script>
+<template>
+ <div v-html="linkText"></div>
+</template>
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 88b6b4732b1..a6b28a080d8 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { __, sprintf } from '../../locale';
+import IssueMergeRequestLinks from './issue_merge_request_links.vue';
export default {
name: 'ReleaseBlock',
@@ -14,6 +15,7 @@ export default {
GlBadge,
Icon,
UserAvatarLink,
+ IssueMergeRequestLinks,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -25,6 +27,14 @@ export default {
required: true,
default: () => ({}),
},
+ issuesUrl: {
+ type: String,
+ required: true,
+ },
+ mergeRequestsUrl: {
+ type: String,
+ required: true,
+ },
},
computed: {
releasedTimeAgo() {
@@ -46,6 +56,9 @@ export default {
author() {
return this.release.author || {};
},
+ shouldRenderIssueMergeRequestLinks() {
+ return Boolean(this.release.milestone);
+ },
hasAuthor() {
return !_.isEmpty(this.author);
},
@@ -136,5 +149,13 @@ export default {
<div v-html="release.description_html"></div>
</div>
</div>
+
+ <div v-if="shouldRenderIssueMergeRequestLinks" class="card-footer">
+ <issue-merge-request-links
+ :milestone="release.milestone"
+ :issues-url="issuesUrl"
+ :merge-requests-url="mergeRequestsUrl"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/releases/index.js b/app/assets/javascripts/releases/index.js
index adbed3cb8e2..9650404937c 100644
--- a/app/assets/javascripts/releases/index.js
+++ b/app/assets/javascripts/releases/index.js
@@ -14,9 +14,7 @@ export default () => {
render(createElement) {
return createElement('app', {
props: {
- projectId: element.dataset.projectId,
- documentationLink: element.dataset.documentationPath,
- illustrationPath: element.dataset.illustrationPath,
+ ...element.dataset,
},
});
},
diff --git a/changelogs/unreleased/nfriend-add-release-footer-2.yml b/changelogs/unreleased/nfriend-add-release-footer-2.yml
new file mode 100644
index 00000000000..7135e79e7c6
--- /dev/null
+++ b/changelogs/unreleased/nfriend-add-release-footer-2.yml
@@ -0,0 +1,5 @@
+---
+title: Add related issues and merge requests links to releases on the Releases page
+merge_request: 32772
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f2d3a39d593..ea13aad9060 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5439,7 +5439,6 @@ msgstr ""
msgid "Go to file (MRs only)"
msgstr ""
-
msgid "Go to file permalink (while viewing a file)"
msgstr ""
@@ -9535,6 +9534,9 @@ msgstr ""
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
+msgid "Releases|View %{issuesLinkStart}Issues%{linkEnd} or %{mrsLinkStart}Merge Requests%{linkEnd} in this release"
+msgstr ""
+
msgid "Remind later"
msgstr ""
diff --git a/spec/frontend/releases/components/issue_merge_request_links_spec.js b/spec/frontend/releases/components/issue_merge_request_links_spec.js
new file mode 100644
index 00000000000..8b80ce7bf09
--- /dev/null
+++ b/spec/frontend/releases/components/issue_merge_request_links_spec.js
@@ -0,0 +1,96 @@
+import { shallowMount } from '@vue/test-utils';
+import IssueMergeRequestLinks from '~/releases/components/issue_merge_request_links.vue';
+import _ from 'underscore';
+import { milestones } from '../mock_data';
+
+describe('IssueMergeRequestLinks', () => {
+ let wrapper;
+ let milestone;
+ const issuesUrl = 'http://example.gitlab.com/issues?scope=all';
+ const mergeRequestsUrl = 'http://example.gitlab.com/merge_requests?scope=all';
+
+ const factory = milestoneProp => {
+ wrapper = shallowMount(IssueMergeRequestLinks, {
+ propsData: {
+ milestone: milestoneProp,
+ issuesUrl,
+ mergeRequestsUrl,
+ },
+ sync: false,
+ });
+ };
+
+ beforeEach(() => {
+ milestone = _.first(milestones);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('with the default props', () => {
+ beforeEach(() => {
+ factory(milestone);
+ });
+
+ it('renders the correct issues URL', () => {
+ expect(wrapper.find('a[href*=issues]').attributes().href).toBe(
+ 'http://example.gitlab.com/issues?scope=all&milestone_title=13.6',
+ );
+ });
+
+ it('renders the correct merge requests URL', () => {
+ expect(wrapper.find('a[href*=merge_requests]').attributes().href).toBe(
+ 'http://example.gitlab.com/merge_requests?scope=all&milestone_title=13.6',
+ );
+ });
+
+ it('renders the issues link with the appropriate attributes', () => {
+ expect(wrapper.find('a[href*=issues]').attributes().target).toBe('_blank');
+ expect(wrapper.find('a[href*=issues]').attributes().rel).toBe('noopener noreferrer');
+ });
+
+ it('renders the merge requests link with the appropriate attributes', () => {
+ expect(wrapper.find('a[href*=merge_requests]').attributes().target).toBe('_blank');
+ expect(wrapper.find('a[href*=merge_requests]').attributes().rel).toBe('noopener noreferrer');
+ });
+ });
+
+ describe('when the milestone title contains URL-unfriendly characters', () => {
+ beforeEach(() => {
+ milestone.title = 'a/weird/title';
+ factory(milestone);
+ });
+
+ it('renders the correct issues URL', () => {
+ expect(wrapper.find('a[href*=issues]').attributes().href).toBe(
+ 'http://example.gitlab.com/issues?scope=all&milestone_title=a%2Fweird%2Ftitle',
+ );
+ });
+
+ it('renders the correct merge requests URL', () => {
+ expect(wrapper.find('a[href*=merge_requests]').attributes().href).toBe(
+ 'http://example.gitlab.com/merge_requests?scope=all&milestone_title=a%2Fweird%2Ftitle',
+ );
+ });
+ });
+
+ describe('when the milestone title contains malicious text', () => {
+ beforeEach(() => {
+ milestone.title = '<script></script>';
+ factory(milestone);
+ });
+
+ it('renders the correct issues URL', () => {
+ expect(wrapper.find('a[href*=issues]').attributes().href).toBe(
+ 'http://example.gitlab.com/issues?scope=all&milestone_title=%26lt%3Bscript%26gt%3B%26lt%3B%2Fscript%26gt%3B',
+ );
+ });
+
+ it('renders the correct merge requests URL', () => {
+ expect(wrapper.find('a[href*=merge_requests]').attributes().href).toBe(
+ 'http://example.gitlab.com/merge_requests?scope=all&milestone_title=%26lt%3Bscript%26gt%3B%26lt%3B%2Fscript%26gt%3B',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js
new file mode 100644
index 00000000000..a0885813c7e
--- /dev/null
+++ b/spec/frontend/releases/mock_data.js
@@ -0,0 +1,97 @@
+export const milestones = [
+ {
+ id: 50,
+ iid: 2,
+ project_id: 18,
+ title: '13.6',
+ description: 'The 13.6 milestone!',
+ state: 'active',
+ created_at: '2019-08-27T17:22:38.280Z',
+ updated_at: '2019-08-27T17:22:38.280Z',
+ due_date: '2019-09-19',
+ start_date: '2019-08-31',
+ web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2',
+ },
+ {
+ id: 49,
+ iid: 1,
+ project_id: 18,
+ title: '13.5',
+ description: 'The 13.5 milestone!',
+ state: 'active',
+ created_at: '2019-08-26T17:55:48.643Z',
+ updated_at: '2019-08-26T17:55:48.643Z',
+ due_date: '2019-10-11',
+ start_date: '2019-08-19',
+ web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1',
+ },
+];
+
+export const release = {
+ name: 'New release',
+ tag_name: 'v0.3',
+ description: 'A super nice release!',
+ description_html: '<p data-sourcepos="1:1-1:21" dir="auto">A super nice release!</p>',
+ created_at: '2019-08-26T17:54:04.952Z',
+ released_at: '2019-08-26T17:54:04.807Z',
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://0.0.0.0:3001/root',
+ },
+ commit: {
+ id: 'c22b0728d1b465f82898c884d32b01aa642f96c1',
+ short_id: 'c22b0728',
+ created_at: '2019-08-26T17:47:07.000Z',
+ parent_ids: [],
+ title: 'Initial commit',
+ message: 'Initial commit',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2019-08-26T17:47:07.000Z',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2019-08-26T17:47:07.000Z',
+ },
+ upcoming_release: false,
+ milestone: milestones[0],
+ assets: {
+ count: 5,
+ sources: [
+ {
+ format: 'zip',
+ url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.zip',
+ },
+ {
+ format: 'tar.gz',
+ url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.gz',
+ },
+ {
+ format: 'tar.bz2',
+ url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.bz2',
+ },
+ {
+ format: 'tar',
+ url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar',
+ },
+ ],
+ links: [
+ {
+ id: 1,
+ name: 'my link',
+ url: 'https://google.com',
+ external: true,
+ },
+ {
+ id: 2,
+ name: 'my second link',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
+ external: false,
+ },
+ ],
+ },
+};