diff options
author | Sean McGivern <sean@mcgivern.me.uk> | 2018-07-06 11:02:20 +0000 |
---|---|---|
committer | Sean McGivern <sean@mcgivern.me.uk> | 2018-07-06 11:02:20 +0000 |
commit | 33116c22fea6f035b63f6e904b619d014922d328 (patch) | |
tree | 1f2989136a1528821cef6b1f3887471752223de2 | |
parent | b0fa01fce3822da94aee6264829841996beb6df3 (diff) | |
parent | ed85787fcd01840bf321a7bddc493996074f984f (diff) | |
download | gitlab-ce-33116c22fea6f035b63f6e904b619d014922d328.tar.gz |
Merge branch 'ide-merge-request-info' into 'master'
Added merge request info to Web IDE sidebar
Closes #45187
See merge request gitlab-org/gitlab-ce!19860
-rw-r--r-- | app/assets/javascripts/api.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/merge_requests/info.vue | 43 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/panes/right.vue | 27 | ||||
-rw-r--r-- | app/assets/javascripts/ide/constants.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/ide/services/index.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/actions/merge_request.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/issue_show/components/title.vue | 112 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/repo.scss | 11 | ||||
-rw-r--r-- | changelogs/unreleased/ide-merge-request-info.yml | 5 | ||||
-rw-r--r-- | doc/api/merge_requests.md | 1 | ||||
-rw-r--r-- | lib/api/entities.rb | 6 | ||||
-rw-r--r-- | lib/api/merge_requests.rb | 3 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | spec/javascripts/ide/components/merge_requests/info_spec.js | 51 | ||||
-rw-r--r-- | spec/javascripts/ide/components/panes/right_spec.js | 72 | ||||
-rw-r--r-- | spec/javascripts/ide/stores/actions/merge_request_spec.js | 4 | ||||
-rw-r--r-- | spec/requests/api/merge_requests_spec.rb | 8 | ||||
-rw-r--r-- | yarn.lock | 6 |
18 files changed, 294 insertions, 68 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 0ca0e8f35dd..422becb7db8 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -100,12 +100,12 @@ const Api = { }, // Return Merge Request for project - mergeRequest(projectPath, mergeRequestId) { + mergeRequest(projectPath, mergeRequestId, params = {}) { const url = Api.buildUrl(Api.mergeRequestPath) .replace(':id', encodeURIComponent(projectPath)) .replace(':mrid', mergeRequestId); - return axios.get(url); + return axios.get(url, { params }); }, mergeRequests(params = {}) { diff --git a/app/assets/javascripts/ide/components/merge_requests/info.vue b/app/assets/javascripts/ide/components/merge_requests/info.vue new file mode 100644 index 00000000000..199d2e74971 --- /dev/null +++ b/app/assets/javascripts/ide/components/merge_requests/info.vue @@ -0,0 +1,43 @@ +<script> +import { mapGetters } from 'vuex'; +import Icon from '../../../vue_shared/components/icon.vue'; +import TitleComponent from '../../../issue_show/components/title.vue'; +import DescriptionComponent from '../../../issue_show/components/description.vue'; + +export default { + components: { + Icon, + TitleComponent, + DescriptionComponent, + }, + computed: { + ...mapGetters(['currentMergeRequest']), + }, +}; +</script> + +<template> + <div class="ide-merge-request-info h-100 d-flex flex-column"> + <div class="detail-page-header"> + <icon + name="git-merge" + class="align-self-center append-right-8" + /> + <strong> + !{{ currentMergeRequest.iid }} + </strong> + </div> + <div class="issuable-details"> + <title-component + :issuable-ref="currentMergeRequest.iid" + :title-html="currentMergeRequest.title_html" + :title-text="currentMergeRequest.title" + /> + <description-component + :description-html="currentMergeRequest.description_html" + :description-text="currentMergeRequest.description" + :can-update="false" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index 5cd2c9ce188..e4a5fcc67c4 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -5,6 +5,7 @@ import Icon from '../../../vue_shared/components/icon.vue'; import { rightSidebarViews } from '../../constants'; import PipelinesList from '../pipelines/list.vue'; import JobsDetail from '../jobs/detail.vue'; +import MergeRequestInfo from '../merge_requests/info.vue'; import ResizablePanel from '../resizable_panel.vue'; export default { @@ -16,9 +17,10 @@ export default { PipelinesList, JobsDetail, ResizablePanel, + MergeRequestInfo, }, computed: { - ...mapState(['rightPane']), + ...mapState(['rightPane', 'currentMergeRequestId']), pipelinesActive() { return ( this.rightPane === rightSidebarViews.pipelines || @@ -54,10 +56,33 @@ export default { </resizable-panel> <nav class="ide-activity-bar"> <ul class="list-unstyled"> + <li + v-if="currentMergeRequestId" + > + <button + v-tooltip + :title="__('Merge Request')" + :aria-label="__('Merge Request')" + :class="{ + active: rightPane === $options.rightSidebarViews.mergeRequestInfo + }" + data-container="body" + data-placement="left" + class="ide-sidebar-link is-right" + type="button" + @click="clickTab($event, $options.rightSidebarViews.mergeRequestInfo)" + > + <icon + :size="16" + name="text-description" + /> + </button> + </li> <li> <button v-tooltip :title="__('Pipelines')" + :aria-label="__('Pipelines')" :class="{ active: pipelinesActive }" diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 12e0c3aeef0..45d36f6f42c 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -31,6 +31,7 @@ export const diffModes = { export const rightSidebarViews = { pipelines: 'pipelines-list', jobsDetail: 'jobs-detail', + mergeRequestInfo: 'merge-request-info', }; export const stageKeys = { diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index 3e939f0c1a3..49a481f25d5 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -40,8 +40,8 @@ export default { getProjectData(namespace, project) { return Api.project(`${namespace}/${project}`); }, - getProjectMergeRequestData(projectId, mergeRequestId) { - return Api.mergeRequest(projectId, mergeRequestId); + getProjectMergeRequestData(projectId, mergeRequestId, params = {}) { + return Api.mergeRequest(projectId, mergeRequestId, params); }, getProjectMergeRequestChanges(projectId, mergeRequestId) { return Api.mergeRequestChanges(projectId, mergeRequestId); diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js index 4aa151abcb7..6bdf9dc3028 100644 --- a/app/assets/javascripts/ide/stores/actions/merge_request.js +++ b/app/assets/javascripts/ide/stores/actions/merge_request.js @@ -9,7 +9,7 @@ export const getMergeRequestData = ( new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) { service - .getProjectMergeRequestData(projectId, mergeRequestId) + .getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true }) .then(({ data }) => { commit(types.SET_MERGE_REQUEST, { projectPath: projectId, diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue index 12101c0daa5..b5e8e0ea44b 100644 --- a/app/assets/javascripts/issue_show/components/title.vue +++ b/app/assets/javascripts/issue_show/components/title.vue @@ -1,67 +1,67 @@ <script> - import animateMixin from '../mixins/animate'; - import eventHub from '../event_hub'; - import tooltip from '../../vue_shared/directives/tooltip'; - import { spriteIcon } from '../../lib/utils/common_utils'; +import animateMixin from '../mixins/animate'; +import eventHub from '../event_hub'; +import tooltip from '../../vue_shared/directives/tooltip'; +import { spriteIcon } from '../../lib/utils/common_utils'; - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + mixins: [animateMixin], + props: { + issuableRef: { + type: [String, Number], + required: true, }, - mixins: [animateMixin], - props: { - issuableRef: { - type: String, - required: true, - }, - canUpdate: { - required: false, - type: Boolean, - default: false, - }, - titleHtml: { - type: String, - required: true, - }, - titleText: { - type: String, - required: true, - }, - showInlineEditButton: { - type: Boolean, - required: false, - default: false, - }, + canUpdate: { + required: false, + type: Boolean, + default: false, }, - data() { - return { - preAnimation: false, - pulseAnimation: false, - titleEl: document.querySelector('title'), - }; + titleHtml: { + type: String, + required: true, }, - computed: { - pencilIcon() { - return spriteIcon('pencil', 'link-highlight'); - }, + titleText: { + type: String, + required: true, }, - watch: { - titleHtml() { - this.setPageTitle(); - this.animateChange(); - }, + showInlineEditButton: { + type: Boolean, + required: false, + default: false, }, - methods: { - setPageTitle() { - const currentPageTitleScope = this.titleEl.innerText.split('·'); - currentPageTitleScope[0] = `${this.titleText} (${this.issuableRef}) `; - this.titleEl.textContent = currentPageTitleScope.join('·'); - }, - edit() { - eventHub.$emit('open.form'); - }, + }, + data() { + return { + preAnimation: false, + pulseAnimation: false, + titleEl: document.querySelector('title'), + }; + }, + computed: { + pencilIcon() { + return spriteIcon('pencil', 'link-highlight'); }, - }; + }, + watch: { + titleHtml() { + this.setPageTitle(); + this.animateChange(); + }, + }, + methods: { + setPageTitle() { + const currentPageTitleScope = this.titleEl.innerText.split('·'); + currentPageTitleScope[0] = `${this.titleText} (${this.issuableRef}) `; + this.titleEl.textContent = currentPageTitleScope.join('·'); + }, + edit() { + eventHub.$emit('open.form'); + }, + }, +}; </script> <template> diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 3c24aaa65e8..6e2b285285a 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -1329,3 +1329,14 @@ line-height: 16px; color: $gl-text-color-secondary; } + +.ide-merge-request-info { + .detail-page-header { + line-height: initial; + min-height: 38px; + } + + .issuable-details { + overflow: auto; + } +} diff --git a/changelogs/unreleased/ide-merge-request-info.yml b/changelogs/unreleased/ide-merge-request-info.yml new file mode 100644 index 00000000000..104f48ae309 --- /dev/null +++ b/changelogs/unreleased/ide-merge-request-info.yml @@ -0,0 +1,5 @@ +--- +title: Display merge request title & description in Web IDE +merge_request: +author: +type: added diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 2057ed3588a..34c2dd7b34d 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -358,6 +358,7 @@ Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `merge_request_iid` (required) - The internal ID of the merge request +- `render_html` (optional) - If `true` response includes rendered HTML for title and description ```json { diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3a6e707fd5b..40df1e79bc7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -532,6 +532,12 @@ module API end class MergeRequestBasic < ProjectEntity + expose :title_html, if: -> (_, options) { options[:render_html] } do |entity| + MarkupHelper.markdown_field(entity, :title) + end + expose :description_html, if: -> (_, options) { options[:render_html] } do |entity| + MarkupHelper.markdown_field(entity, :description) + end expose :target_branch, :source_branch expose :upvotes do |merge_request, options| if options[:issuable_metadata] diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 0f46bc4c98e..2621c9f8fc2 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -232,6 +232,7 @@ module API params do requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' + optional :render_html, type: Boolean, desc: 'Returns the description and title rendered HTML' end desc 'Get a single merge request' do success Entities::MergeRequest @@ -239,7 +240,7 @@ module API get ':id/merge_requests/:merge_request_iid' do merge_request = find_merge_request_with_access(params[:merge_request_iid]) - present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project, render_html: params[:render_html] end desc 'Get the participants of a merge request' do diff --git a/package.json b/package.json index 6980416503e..1fce5d7623d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { - "@gitlab-org/gitlab-svgs": "^1.24.0", + "@gitlab-org/gitlab-svgs": "^1.25.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-core": "^6.26.3", diff --git a/spec/javascripts/ide/components/merge_requests/info_spec.js b/spec/javascripts/ide/components/merge_requests/info_spec.js new file mode 100644 index 00000000000..98a29e5128b --- /dev/null +++ b/spec/javascripts/ide/components/merge_requests/info_spec.js @@ -0,0 +1,51 @@ +import Vue from 'vue'; +import '~/behaviors/markdown/render_gfm'; +import { createStore } from '~/ide/stores'; +import Info from '~/ide/components/merge_requests/info.vue'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; + +describe('IDE merge request details', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(Info); + }); + + beforeEach(() => { + const store = createStore(); + store.state.currentProjectId = 'gitlab-ce'; + store.state.currentMergeRequestId = 1; + store.state.projects['gitlab-ce'] = { + mergeRequests: { + 1: { + iid: 1, + title: 'Testing', + title_html: '<span class="title-html">Testing</span>', + description: 'Description', + description_html: '<p class="description-html">Description HTML</p>', + }, + }, + }; + + vm = createComponentWithStore(Component, store).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders merge request IID', () => { + expect(vm.$el.querySelector('.detail-page-header').textContent).toContain('!1'); + }); + + it('renders title as HTML', () => { + expect(vm.$el.querySelector('.title-html')).not.toBe(null); + expect(vm.$el.querySelector('.title').textContent).toContain('Testing'); + }); + + it('renders description as HTML', () => { + expect(vm.$el.querySelector('.description-html')).not.toBe(null); + expect(vm.$el.querySelector('.description').textContent).toContain('Description HTML'); + }); +}); diff --git a/spec/javascripts/ide/components/panes/right_spec.js b/spec/javascripts/ide/components/panes/right_spec.js new file mode 100644 index 00000000000..99879fb0930 --- /dev/null +++ b/spec/javascripts/ide/components/panes/right_spec.js @@ -0,0 +1,72 @@ +import Vue from 'vue'; +import '~/behaviors/markdown/render_gfm'; +import { createStore } from '~/ide/stores'; +import RightPane from '~/ide/components/panes/right.vue'; +import { rightSidebarViews } from '~/ide/constants'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; + +describe('IDE right pane', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(RightPane); + }); + + beforeEach(() => { + const store = createStore(); + + vm = createComponentWithStore(Component, store).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('active', () => { + it('renders merge request button as active', done => { + vm.$store.state.rightPane = rightSidebarViews.mergeRequestInfo; + vm.$store.state.currentMergeRequestId = '123'; + vm.$store.state.currentProjectId = 'gitlab-ce'; + vm.$store.state.currentMergeRequestId = 1; + vm.$store.state.projects['gitlab-ce'] = { + mergeRequests: { + 1: { + iid: 1, + title: 'Testing', + title_html: '<span class="title-html">Testing</span>', + description: 'Description', + description_html: '<p class="description-html">Description HTML</p>', + }, + }, + }; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-sidebar-link.active')).not.toBe(null); + expect( + vm.$el.querySelector('.ide-sidebar-link.active').getAttribute('data-original-title'), + ).toBe('Merge Request'); + + done(); + }); + }); + }); + + describe('click', () => { + beforeEach(() => { + spyOn(vm, 'setRightPane'); + }); + + it('sets view to merge request', done => { + vm.$store.state.currentMergeRequestId = '123'; + + vm.$nextTick(() => { + vm.$el.querySelector('.ide-sidebar-link').click(); + + expect(vm.setRightPane).toHaveBeenCalledWith(rightSidebarViews.mergeRequestInfo); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index c99ccc70c6a..90c28c769f7 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -39,7 +39,9 @@ describe('IDE store merge request actions', () => { store .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1); + expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1, { + render_html: true, + }); done(); }) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index eba39bb6ccc..1716d182782 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -306,6 +306,14 @@ describe API::MergeRequests do expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size) end + it 'exposes description and title html when render_html is true' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), render_html: true + + expect(response).to have_gitlab_http_status(200) + + expect(json_response).to include('title_html', 'description_html') + end + context 'merge_request_metrics' do before do merge_request.metrics.update!(merged_by: user, diff --git a/yarn.lock b/yarn.lock index 30d49ad276a..5df2a9a2eb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,9 +78,9 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.24.0.tgz#3b2b58c5a1d58ce784f486d648bd87cbbb06cedc" +"@gitlab-org/gitlab-svgs@^1.25.0": + version "1.25.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.25.0.tgz#1a82b1be43e1a46e6b0767ef46f26f5fd6bbd101" "@sindresorhus/is@^0.7.0": version "0.7.0" |