diff options
author | Nathan Friend <nathan@gitlab.com> | 2019-09-11 16:03:53 -0300 |
---|---|---|
committer | Nathan Friend <nathan@gitlab.com> | 2019-09-11 16:03:53 -0300 |
commit | f73aa5580bbfec9636155165d44d5c647a7f0961 (patch) | |
tree | 74ec753726d8e2665f39139b9db865d6bd414b12 | |
parent | 6db9cbfe77a556ea2d321f0ea0a0c6b6e14a817c (diff) | |
download | gitlab-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.vue | 14 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/issue_merge_request_links.vue | 47 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/release_block.vue | 21 | ||||
-rw-r--r-- | app/assets/javascripts/releases/index.js | 4 | ||||
-rw-r--r-- | changelogs/unreleased/nfriend-add-release-footer-2.yml | 5 | ||||
-rw-r--r-- | locale/gitlab.pot | 4 | ||||
-rw-r--r-- | spec/frontend/releases/components/issue_merge_request_links_spec.js | 96 | ||||
-rw-r--r-- | spec/frontend/releases/mock_data.js | 97 |
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, + }, + ], + }, +}; |