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
This commit is contained in:
commit
33116c22fe
|
@ -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 = {}) {
|
||||
|
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
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",
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue