diff options
author | Phil Hughes <me@iamphill.com> | 2018-06-01 17:03:34 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2018-06-06 11:18:30 +0100 |
commit | 524ebff5d17fd9111f2277843b384d204ff87a47 (patch) | |
tree | 3a59c38f778542b019fe39ce6d2d1536866aae49 | |
parent | af9cc234f2bf854de38e9730266a411f261918da (diff) | |
download | gitlab-ce-524ebff5d17fd9111f2277843b384d204ff87a47.tar.gz |
Show merge requests in IDE
Closes #45184
9 files changed, 285 insertions, 15 deletions
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 3f980203911..e99305d0bc0 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -13,6 +13,7 @@ import CommitSection from './repo_commit_section.vue'; import CommitForm from './commit_sidebar/form.vue'; import IdeReview from './ide_review.vue'; import SuccessMessage from './commit_sidebar/success_message.vue'; +import MergeRequestDropdown from './merge_requests/dropdown.vue'; import { activityBarViews } from '../constants'; export default { @@ -32,6 +33,7 @@ export default { CommitForm, IdeReview, SuccessMessage, + MergeRequestDropdown, }, data() { return { @@ -46,6 +48,7 @@ export default { 'changedFiles', 'stagedFiles', 'lastCommitMsg', + 'currentMergeRequestId', ]), ...mapGetters(['currentProject', 'someUncommitedChanges']), showSuccessMessage() { @@ -88,9 +91,12 @@ export default { </div> </template> <template v-else> - <div class="context-header ide-context-header"> + <div class="context-header ide-context-header dropdown"> <a - :href="currentProject.web_url" + href="#" + role="button" + @click.prevent + data-toggle="dropdown" > <div v-if="currentProject.avatar_url" @@ -114,19 +120,35 @@ export default { <div class="sidebar-context-title"> {{ currentProject.name }} </div> - <div - class="sidebar-context-title ide-sidebar-branch-title" - ref="branchId" - v-tooltip - :title="branchTooltipTitle" - > - <icon - name="branch" - css-classes="append-right-5" - />{{ currentBranchId }} + <div class="d-flex"> + <div + class="sidebar-context-title ide-sidebar-branch-title" + ref="branchId" + v-tooltip + :title="branchTooltipTitle" + > + <icon + name="branch" + css-classes="append-right-5" + />{{ currentBranchId }} + </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> + <icon + class="ml-auto" + name="chevron-down" + /> </a> + <merge-request-dropdown /> </div> <div class="multi-file-commit-panel-inner-scroll"> <component diff --git a/app/assets/javascripts/ide/components/merge_requests/dropdown.vue b/app/assets/javascripts/ide/components/merge_requests/dropdown.vue new file mode 100644 index 00000000000..106f66b764a --- /dev/null +++ b/app/assets/javascripts/ide/components/merge_requests/dropdown.vue @@ -0,0 +1,78 @@ +<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> diff --git a/app/assets/javascripts/ide/components/merge_requests/item.vue b/app/assets/javascripts/ide/components/merge_requests/item.vue new file mode 100644 index 00000000000..11eb5c8b31b --- /dev/null +++ b/app/assets/javascripts/ide/components/merge_requests/item.vue @@ -0,0 +1,59 @@ +<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> diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue new file mode 100644 index 00000000000..04b3848f3e2 --- /dev/null +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -0,0 +1,91 @@ +<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> diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js index d3050183bd3..2b32c0c0b55 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js @@ -22,4 +22,6 @@ export const fetchMergeRequests = ({ dispatch, state: { scope, state } }, search export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS); +export const setScope = ({ commit }, scope) => commit(types.SET_SCOPE, scope); + export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js b/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js index 0badddcbae7..6d163491209 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js @@ -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 RESET_MERGE_REQUESTS = 'RESET_MERGE_REQUESTS'; + +export const SET_SCOPE = 'SET_SCOPE'; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js index 98102a68e08..5c0c502b0d1 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js @@ -23,4 +23,7 @@ export default { [types.RESET_MERGE_REQUESTS](state) { state.mergeRequests = []; }, + [types.SET_SCOPE](state, scope) { + state.scope = scope; + }, }; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/state.js b/app/assets/javascripts/ide/stores/modules/merge_requests/state.js index 2947b686c1c..64ca24b1af6 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/state.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/state.js @@ -3,6 +3,6 @@ import { scopes, states } from './constants'; export default () => ({ isLoading: false, mergeRequests: [], - scope: scopes.assignedToMe, + scope: scopes.createdByMe, state: states.opened, }); diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js index 4362264caa5..8497c3e851f 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tabs.js +++ b/app/assets/javascripts/vue_shared/components/tabs/tabs.js @@ -1,4 +1,11 @@ export default { + props: { + stopPropagation: { + type: Boolean, + required: false, + default: false, + }, + }, data() { return { currentIndex: 0, @@ -13,11 +20,17 @@ export default { this.tabs = this.$children.filter(child => child.isTab); 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[index].localActive = true; this.currentIndex = index; + + this.$emit('changed', this.currentIndex); }, }, render(h) { @@ -36,7 +49,7 @@ export default { href: '#', }, on: { - click: () => this.setTab(i), + click: e => this.setTab(e, i), }, }, tab.$slots.title || tab.title, |