Commit f42b4c18 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'winh-board-switcher-search-ee' into 'master'

Add search field to issue board switcher

Closes #7715

See merge request gitlab-org/gitlab-ee!8862
parents 238a9674 8fa9adbc
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { throttle } from 'underscore'; import { throttle } from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon, GlSearchBox } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
import BoardForm from './board_form.vue'; import BoardForm from './board_form.vue';
...@@ -14,6 +14,7 @@ export default { ...@@ -14,6 +14,7 @@ export default {
Icon, Icon,
BoardForm, BoardForm,
GlLoadingIcon, GlLoadingIcon,
GlSearchBox,
}, },
props: { props: {
currentBoard: { currentBoard: {
...@@ -79,12 +80,18 @@ export default { ...@@ -79,12 +80,18 @@ export default {
contentClientHeight: 0, contentClientHeight: 0,
maxPosition: 0, maxPosition: 0,
store: boardsStore, store: boardsStore,
filterTerm: '',
}; };
}, },
computed: { computed: {
currentPage() { currentPage() {
return this.state.currentPage; return this.state.currentPage;
}, },
filteredBoards() {
return this.boards.filter(board =>
board.name.toLowerCase().includes(this.filterTerm.toLowerCase()),
);
},
reload: { reload: {
get() { get() {
return this.state.reload; return this.state.reload;
...@@ -106,6 +113,10 @@ export default { ...@@ -106,6 +113,10 @@ export default {
}, },
}, },
watch: { watch: {
filteredBoards() {
this.scrollFadeInitialized = false;
this.$nextTick(this.setScrollFade);
},
reload() { reload() {
if (this.reload) { if (this.reload) {
this.boards = []; this.boards = [];
...@@ -123,6 +134,12 @@ export default { ...@@ -123,6 +134,12 @@ export default {
$('#js-add-list').on('hide.bs.dropdown', this.handleDropdownHide); $('#js-add-list').on('hide.bs.dropdown', this.handleDropdownHide);
$('.js-new-board-list-tabs').on('click', this.handleDropdownTabClick); $('.js-new-board-list-tabs').on('click', this.handleDropdownTabClick);
}, },
mounted() {
// prevent dropdown from closing when search box is clicked
$(this.$el)
.find('.dropdown-header')
.on('click', event => event.stopPropagation());
},
methods: { methods: {
showPage(page) { showPage(page) {
this.state.reload = false; this.state.reload = false;
...@@ -130,6 +147,10 @@ export default { ...@@ -130,6 +147,10 @@ export default {
}, },
toggleDropdown() { toggleDropdown() {
this.open = !this.open; this.open = !this.open;
if (this.open && !this.loading) {
this.$nextTick(this.focusSearchInput);
}
}, },
loadBoards(toggleDropdown = true) { loadBoards(toggleDropdown = true) {
if (toggleDropdown) { if (toggleDropdown) {
...@@ -145,7 +166,10 @@ export default { ...@@ -145,7 +166,10 @@ export default {
this.boards = json; this.boards = json;
}) })
.then(() => this.$nextTick()) // Wait for boards list in DOM .then(() => this.$nextTick()) // Wait for boards list in DOM
.then(this.setScrollFade) .then(() => {
this.setScrollFade();
this.focusSearchInput();
})
.catch(() => { .catch(() => {
this.loading = false; this.loading = false;
}); });
...@@ -190,6 +214,10 @@ export default { ...@@ -190,6 +214,10 @@ export default {
this.hasMilestoneListMounted = true; this.hasMilestoneListMounted = true;
} }
}, },
focusSearchInput() {
const { searchBox } = this.$refs;
searchBox.$el.querySelector('input').focus();
},
}, },
}; };
</script> </script>
...@@ -207,6 +235,10 @@ export default { ...@@ -207,6 +235,10 @@ export default {
{{ board.name }} <icon name="chevron-down" /> {{ board.name }} <icon name="chevron-down" />
</button> </button>
<div class="dropdown-menu" :class="{ 'is-loading': loading }"> <div class="dropdown-menu" :class="{ 'is-loading': loading }">
<div class="dropdown-header position-relative">
<gl-search-box v-if="!loading" ref="searchBox" v-model="filterTerm" />
</div>
<div class="dropdown-content-faded-mask js-scroll-fade" :class="scrollFadeClass"> <div class="dropdown-content-faded-mask js-scroll-fade" :class="scrollFadeClass">
<ul <ul
v-if="!loading" v-if="!loading"
...@@ -214,7 +246,18 @@ export default { ...@@ -214,7 +246,18 @@ export default {
class="dropdown-list js-dropdown-list" class="dropdown-list js-dropdown-list"
@scroll.passive="throttledSetScrollFade" @scroll.passive="throttledSetScrollFade"
> >
<li v-for="otherBoard in boards" :key="otherBoard.id" class="js-dropdown-item"> <li
v-show="filteredBoards.length === 0"
class="dropdown-item no-pointer-events text-secondary"
>
{{ s__('IssueBoards|No matching boards found') }}
</li>
<li
v-for="otherBoard in filteredBoards"
:key="otherBoard.id"
class="js-dropdown-item"
>
<a :href="`${boardBaseUrl}/${otherBoard.id}`"> {{ otherBoard.name }} </a> <a :href="`${boardBaseUrl}/${otherBoard.id}`"> {{ otherBoard.name }} </a>
</li> </li>
<li v-if="hasMissingBoards" class="small unclickable"> <li v-if="hasMissingBoards" class="small unclickable">
......
...@@ -102,3 +102,11 @@ ...@@ -102,3 +102,11 @@
font-weight: normal; font-weight: normal;
} }
} }
.boards-switcher {
.dropdown-content-faded-mask .dropdown-list {
// the following is no longer necessary after https://gitlab.com/gitlab-org/gitlab-ee/issues/8855
$search-box-height: 50px;
max-height: calc(#{$dropdown-max-height} - #{$search-box-height});
}
}
---
title: Add search field to issue board switcher
merge_request: 8862
author:
type: changed
...@@ -92,6 +92,7 @@ describe('BoardsSelector', () => { ...@@ -92,6 +92,7 @@ describe('BoardsSelector', () => {
}); });
it('shows the scroll fade if isScrolledUp', done => { it('shows the scroll fade if isScrolledUp', done => {
vm.scrollFadeInitialized = false;
scrollContainer.scrollTop = 0; scrollContainer.scrollTop = 0;
waitForScroll() waitForScroll()
...@@ -112,4 +113,49 @@ describe('BoardsSelector', () => { ...@@ -112,4 +113,49 @@ describe('BoardsSelector', () => {
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
describe('filtering', () => {
const fillSearchBox = filterTerm => {
const { searchBox } = vm.$refs;
const searchBoxInput = searchBox.$el.querySelector('input');
searchBoxInput.value = filterTerm;
searchBoxInput.dispatchEvent(new Event('input'));
};
it('shows all boards without filtering', () => {
const dropdownItemCount = vm.$el.querySelectorAll('.js-dropdown-item');
expect(dropdownItemCount.length).toBe(boards.length);
});
it('shows only matching boards when filtering', done => {
const filterTerm = 'board1';
const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length;
fillSearchBox(filterTerm);
vm.$nextTick()
.then(() => {
const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
expect(dropdownItems.length).toBe(expectedCount);
})
.then(done)
.catch(done.fail);
});
it('shows message if there are no matching boards', done => {
fillSearchBox('does not exist');
vm.$nextTick()
.then(() => {
const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
expect(dropdownItems.length).toBe(0);
expect(vm.$el).toContainText('No matching boards found');
})
.then(done)
.catch(done.fail);
});
});
}); });
...@@ -4861,6 +4861,9 @@ msgstr "" ...@@ -4861,6 +4861,9 @@ msgstr ""
msgid "IssueBoards|Delete board" msgid "IssueBoards|Delete board"
msgstr "" msgstr ""
msgid "IssueBoards|No matching boards found"
msgstr ""
msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again." msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
msgstr "" msgstr ""
......
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