From f73aa5580bbfec9636155165d44d5c647a7f0961 Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Wed, 11 Sep 2019 16:03:53 -0300 Subject: Add related issue and MR links to release blocks 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. --- app/assets/javascripts/releases/components/app.vue | 14 +++- .../components/issue_merge_request_links.vue | 47 +++++++++++ .../releases/components/release_block.vue | 21 +++++ app/assets/javascripts/releases/index.js | 4 +- .../unreleased/nfriend-add-release-footer-2.yml | 5 ++ locale/gitlab.pot | 4 +- .../components/issue_merge_request_links_spec.js | 96 +++++++++++++++++++++ spec/frontend/releases/mock_data.js | 97 ++++++++++++++++++++++ 8 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/releases/components/issue_merge_request_links.vue create mode 100644 changelogs/unreleased/nfriend-add-release-footer-2.yml create mode 100644 spec/frontend/releases/components/issue_merge_request_links_spec.js create mode 100644 spec/frontend/releases/mock_data.js 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" /> 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 @@ + + 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 {
+ + 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 = ''; + 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: '

A super nice release!

', + 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, + }, + ], + }, +}; -- cgit v1.2.1