Commit 33116c22 authored by Sean McGivern's avatar Sean McGivern

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
parents b0fa01fc ed85787f
......@@ -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 = {}) {
......
<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>
......@@ -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
}"
......
......@@ -31,6 +31,7 @@ export const diffModes = {
export const rightSidebarViews = {
pipelines: 'pipelines-list',
jobsDetail: 'jobs-detail',
mergeRequestInfo: 'merge-request-info',
};
export const stageKeys = {
......
......@@ -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);
......
......@@ -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,
......
<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>
......
......@@ -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;
}
}
---
title: Display merge request title & description in Web IDE
merge_request:
author:
type: added
......@@ -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
{
......
......@@ -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]
......
......@@ -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
......
......@@ -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",
......
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');
});
});
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();
});
});
});
});
......@@ -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();
})
......
......@@ -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,
......
......@@ -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"
......
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