Commit 524ebff5 authored by Phil Hughes's avatar Phil Hughes

Show merge requests in IDE

Closes #45184
parent af9cc234
...@@ -13,6 +13,7 @@ import CommitSection from './repo_commit_section.vue'; ...@@ -13,6 +13,7 @@ import CommitSection from './repo_commit_section.vue';
import CommitForm from './commit_sidebar/form.vue'; import CommitForm from './commit_sidebar/form.vue';
import IdeReview from './ide_review.vue'; import IdeReview from './ide_review.vue';
import SuccessMessage from './commit_sidebar/success_message.vue'; import SuccessMessage from './commit_sidebar/success_message.vue';
import MergeRequestDropdown from './merge_requests/dropdown.vue';
import { activityBarViews } from '../constants'; import { activityBarViews } from '../constants';
export default { export default {
...@@ -32,6 +33,7 @@ export default { ...@@ -32,6 +33,7 @@ export default {
CommitForm, CommitForm,
IdeReview, IdeReview,
SuccessMessage, SuccessMessage,
MergeRequestDropdown,
}, },
data() { data() {
return { return {
...@@ -46,6 +48,7 @@ export default { ...@@ -46,6 +48,7 @@ export default {
'changedFiles', 'changedFiles',
'stagedFiles', 'stagedFiles',
'lastCommitMsg', 'lastCommitMsg',
'currentMergeRequestId',
]), ]),
...mapGetters(['currentProject', 'someUncommitedChanges']), ...mapGetters(['currentProject', 'someUncommitedChanges']),
showSuccessMessage() { showSuccessMessage() {
...@@ -88,9 +91,12 @@ export default { ...@@ -88,9 +91,12 @@ export default {
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="context-header ide-context-header"> <div class="context-header ide-context-header dropdown">
<a <a
:href="currentProject.web_url" href="#"
role="button"
@click.prevent
data-toggle="dropdown"
> >
<div <div
v-if="currentProject.avatar_url" v-if="currentProject.avatar_url"
...@@ -114,6 +120,7 @@ export default { ...@@ -114,6 +120,7 @@ export default {
<div class="sidebar-context-title"> <div class="sidebar-context-title">
{{ currentProject.name }} {{ currentProject.name }}
</div> </div>
<div class="d-flex">
<div <div
class="sidebar-context-title ide-sidebar-branch-title" class="sidebar-context-title ide-sidebar-branch-title"
ref="branchId" ref="branchId"
...@@ -125,8 +132,23 @@ export default { ...@@ -125,8 +132,23 @@ export default {
css-classes="append-right-5" css-classes="append-right-5"
/>{{ currentBranchId }} />{{ currentBranchId }}
</div> </div>
<div
v-if="currentMergeRequestId"
class="sidebar-context-title ide-sidebar-branch-title prepend-left-8"
>
<icon
name="git-merge"
css-classes="append-right-5"
/>!{{ currentMergeRequestId }}
</div> </div>
</div>
</div>
<icon
class="ml-auto"
name="chevron-down"
/>
</a> </a>
<merge-request-dropdown />
</div> </div>
<div class="multi-file-commit-panel-inner-scroll"> <div class="multi-file-commit-panel-inner-scroll">
<component <component
......
<script>
import { mapActions, mapState } from 'vuex';
import Tabs from '../../../vue_shared/components/tabs/tabs';
import Tab from '../../../vue_shared/components/tabs/tab.vue';
import List from './list.vue';
import { scopes } from '../../stores/modules/merge_requests/constants';
export default {
components: {
Tabs,
Tab,
List,
},
data() {
return {
activeTabIndex: 0,
};
},
computed: {
...mapState('mergeRequests', ['isLoading', 'mergeRequests']),
...mapState(['currentMergeRequestId']),
tabScope() {
return this.activeTabIndex === 0 ? scopes.createdByMe : scopes.assignedToMe;
},
},
mounted() {
this.fetchMergeRequests();
},
methods: {
...mapActions('mergeRequests', ['fetchMergeRequests', 'setScope']),
updateActiveTab(index) {
this.activeTabIndex = index;
this.setScope(this.tabScope);
this.fetchMergeRequests();
},
},
};
</script>
<template>
<div class="dropdown-menu">
<tabs
stop-propagation
@changed="updateActiveTab"
>
<tab
:title="__('Created by me')"
active
>
<list
v-if="activeTabIndex === 0"
:is-loading="isLoading"
:items="mergeRequests"
:current-id="currentMergeRequestId"
@search="fetchMergeRequests"
/>
</tab>
<tab :title="__('Assigned to me')">
<list
v-if="activeTabIndex === 1"
:is-loading="isLoading"
:items="mergeRequests"
:current-id="currentMergeRequestId"
@search="fetchMergeRequests"
/>
</tab>
</tabs>
</div>
</template>
<style scoped>
.dropdown-menu {
width: 400px;
padding: 0;
max-height: initial !important;
}
</style>
<script>
import Icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
Icon,
},
props: {
item: {
type: Object,
required: true,
},
currentId: {
type: String,
required: true,
},
},
computed: {
isActive() {
return this.item.iid === parseInt(this.currentId, 10);
},
pathWithID() {
return `${this.item.projectPathWithNamespace}!${this.item.iid}`;
},
},
methods: {
clickItem() {
this.$emit('click', this.item);
},
},
};
</script>
<template>
<button
type="button"
class="d-flex align-items-center"
@click="clickItem"
>
<span
class="d-flex append-right-default"
style="min-width: 18px"
>
<icon
v-if="isActive"
name="mobile-issue-close"
:size="18"
/>
</span>
<span>
<strong>
{{ item.title }}
</strong>
<span class="d-block mt-1">
{{ pathWithID }}
</span>
</span>
</button>
</template>
<script>
import _ from 'underscore';
import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Item from './item.vue';
export default {
components: {
LoadingIcon,
Item,
},
props: {
isLoading: {
type: Boolean,
required: true,
},
items: {
type: Array,
required: true,
},
currentId: {
type: String,
required: true,
},
},
data() {
return {
search: '',
};
},
watch: {
isLoading() {
this.focusSearch();
},
},
methods: {
viewMergeRequest(item) {
this.$router.push(`/project/${item.projectPathWithNamespace}/merge_requests/${item.iid}`);
},
searchMergeRequests: _.debounce(function debounceSearch() {
this.$emit('search', this.search);
}, 250),
focusSearch() {
if (!this.isLoading) {
this.$nextTick(() => {
this.$refs.searchInput.focus();
});
}
},
},
};
</script>
<template>
<div>
<loading-icon
class="mt-3 mb-3"
v-if="isLoading"
size="2"
/>
<template v-else>
<div class="dropdown-input mt-3 pb-3 mb-3 border-bottom">
<input
type="search"
class="dropdown-input-field"
placeholder="Search merge requests"
v-model="search"
@input="searchMergeRequests"
ref="searchInput"
/>
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search"
></i>
</div>
<div class="dropdown-content">
<ul class="mb-3">
<li
v-for="item in items"
:key="item.id"
>
<item
:item="item"
:current-id="currentId"
@click="viewMergeRequest"
/>
</li>
</ul>
</div>
</template>
</div>
</template>
...@@ -22,4 +22,6 @@ export const fetchMergeRequests = ({ dispatch, state: { scope, state } }, search ...@@ -22,4 +22,6 @@ export const fetchMergeRequests = ({ dispatch, state: { scope, state } }, search
export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS); export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS);
export const setScope = ({ commit }, scope) => commit(types.SET_SCOPE, scope);
export default () => {}; export default () => {};
...@@ -3,3 +3,5 @@ export const RECEIVE_MERGE_REQUESTS_ERROR = 'RECEIVE_MERGE_REQUESTS_ERROR'; ...@@ -3,3 +3,5 @@ export const RECEIVE_MERGE_REQUESTS_ERROR = 'RECEIVE_MERGE_REQUESTS_ERROR';
export const RECEIVE_MERGE_REQUESTS_SUCCESS = 'RECEIVE_MERGE_REQUESTS_SUCCESS'; export const RECEIVE_MERGE_REQUESTS_SUCCESS = 'RECEIVE_MERGE_REQUESTS_SUCCESS';
export const RESET_MERGE_REQUESTS = 'RESET_MERGE_REQUESTS'; export const RESET_MERGE_REQUESTS = 'RESET_MERGE_REQUESTS';
export const SET_SCOPE = 'SET_SCOPE';
...@@ -23,4 +23,7 @@ export default { ...@@ -23,4 +23,7 @@ export default {
[types.RESET_MERGE_REQUESTS](state) { [types.RESET_MERGE_REQUESTS](state) {
state.mergeRequests = []; state.mergeRequests = [];
}, },
[types.SET_SCOPE](state, scope) {
state.scope = scope;
},
}; };
...@@ -3,6 +3,6 @@ import { scopes, states } from './constants'; ...@@ -3,6 +3,6 @@ import { scopes, states } from './constants';
export default () => ({ export default () => ({
isLoading: false, isLoading: false,
mergeRequests: [], mergeRequests: [],
scope: scopes.assignedToMe, scope: scopes.createdByMe,
state: states.opened, state: states.opened,
}); });
export default { export default {
props: {
stopPropagation: {
type: Boolean,
required: false,
default: false,
},
},
data() { data() {
return { return {
currentIndex: 0, currentIndex: 0,
...@@ -13,11 +20,17 @@ export default { ...@@ -13,11 +20,17 @@ export default {
this.tabs = this.$children.filter(child => child.isTab); this.tabs = this.$children.filter(child => child.isTab);
this.currentIndex = this.tabs.findIndex(tab => tab.localActive); this.currentIndex = this.tabs.findIndex(tab => tab.localActive);
}, },
setTab(index) { setTab(e, index) {
if (this.stopPropagation) {
e.stopPropagation();
}
this.tabs[this.currentIndex].localActive = false; this.tabs[this.currentIndex].localActive = false;
this.tabs[index].localActive = true; this.tabs[index].localActive = true;
this.currentIndex = index; this.currentIndex = index;
this.$emit('changed', this.currentIndex);
}, },
}, },
render(h) { render(h) {
...@@ -36,7 +49,7 @@ export default { ...@@ -36,7 +49,7 @@ export default {
href: '#', href: '#',
}, },
on: { on: {
click: () => this.setTab(i), click: e => this.setTab(e, i),
}, },
}, },
tab.$slots.title || tab.title, tab.$slots.title || tab.title,
......
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