Commit 0927cfc6 authored by Denys Mishunov's avatar Denys Mishunov

Merge branch 'kp-improve-vue-labels-dropdown-performance' into 'master'

Use SmartVirtualList to improve labels list render performance

See merge request gitlab-org/gitlab!32419
parents 427aca0b 19232801
// eslint-disable-next-line import/prefer-default-export
export const DropdownVariant = {
Sidebar: 'sidebar',
Standalone: 'standalone',
};
export const LIST_BUFFER_SIZE = 5;
......@@ -3,15 +3,20 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlButton, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import LabelItem from './label_item.vue';
import { LIST_BUFFER_SIZE } from './constants';
export default {
LIST_BUFFER_SIZE,
components: {
GlLoadingIcon,
GlButton,
GlSearchBoxByType,
GlLink,
SmartVirtualList,
LabelItem,
},
data() {
......@@ -139,10 +144,18 @@ export default {
<gl-search-box-by-type v-model="searchKey" :autofocus="true" />
</div>
<div v-show="!labelsFetchInProgress" ref="labelsListContainer" class="dropdown-content">
<ul class="list-unstyled mb-0">
<smart-virtual-list
:length="visibleLabels.length"
:remain="$options.LIST_BUFFER_SIZE"
:size="$options.LIST_BUFFER_SIZE"
wclass="list-unstyled mb-0"
wtag="ul"
class="h-100"
>
<li v-for="(label, index) in visibleLabels" :key="label.id" class="d-block text-left">
<label-item
:label="label"
:is-label-set="label.set"
:highlight="index === currentHighlightItem"
@clickLabel="handleLabelClick(label)"
/>
......@@ -150,7 +163,7 @@ export default {
<li v-show="!visibleLabels.length" class="p-2 text-center">
{{ __('No matching results') }}
</li>
</ul>
</smart-virtual-list>
</div>
<div v-if="isDropdownVariantSidebar" class="dropdown-footer">
<ul class="list-unstyled">
......@@ -162,9 +175,9 @@ export default {
>
</li>
<li>
<gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">{{
footerManageLabelTitle
}}</gl-link>
<gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">
{{ footerManageLabelTitle }}
</gl-link>
</li>
</ul>
</div>
......
......@@ -11,6 +11,10 @@ export default {
type: Object,
required: true,
},
isLabelSet: {
type: Boolean,
required: true,
},
highlight: {
type: Boolean,
required: false,
......@@ -19,7 +23,7 @@ export default {
},
data() {
return {
isSet: this.label.set,
isSet: this.isLabelSet,
};
},
computed: {
......@@ -29,6 +33,16 @@ export default {
};
},
},
watch: {
/**
* This watcher assures that if user used
* `Enter` key to set/unset label, changes
* are reflected here too.
*/
isLabelSet(value) {
this.isSet = value;
},
},
methods: {
handleClick() {
this.isSet = !this.isSet;
......
......@@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
......@@ -224,6 +225,10 @@ describe('DropdownContentsLabelsView', () => {
expect(searchInputEl.attributes('autofocus')).toBe('true');
});
it('renders smart-virtual-list element', () => {
expect(wrapper.find(SmartVirtualList).exists()).toBe(true);
});
it('renders label elements for all labels', () => {
expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length);
});
......
......@@ -4,10 +4,13 @@ import { GlIcon, GlLink } from '@gitlab/ui';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
import { mockRegularLabel } from './mock_data';
const createComponent = ({ label = mockRegularLabel, highlight = true } = {}) =>
const mockLabel = { ...mockRegularLabel, set: true };
const createComponent = ({ label = mockLabel, highlight = true } = {}) =>
shallowMount(LabelItem, {
propsData: {
label,
isLabelSet: label.set,
highlight,
},
});
......@@ -28,13 +31,29 @@ describe('LabelItem', () => {
it('returns an object containing `backgroundColor` based on `label` prop', () => {
expect(wrapper.vm.labelBoxStyle).toEqual(
expect.objectContaining({
backgroundColor: mockRegularLabel.color,
backgroundColor: mockLabel.color,
}),
);
});
});
});
describe('watchers', () => {
describe('isLabelSet', () => {
it('sets value of `isLabelSet` to `isSet` data prop', () => {
expect(wrapper.vm.isSet).toBe(true);
wrapper.setProps({
isLabelSet: false,
});
return wrapper.vm.$nextTick(() => {
expect(wrapper.vm.isSet).toBe(false);
});
});
});
});
describe('methods', () => {
describe('handleClick', () => {
it('sets value of `isSet` data prop to opposite of its current value', () => {
......@@ -52,7 +71,7 @@ describe('LabelItem', () => {
wrapper.vm.handleClick();
expect(wrapper.emitted('clickLabel')).toBeTruthy();
expect(wrapper.emitted('clickLabel')[0]).toEqual([mockRegularLabel]);
expect(wrapper.emitted('clickLabel')[0]).toEqual([mockLabel]);
});
});
});
......@@ -105,7 +124,7 @@ describe('LabelItem', () => {
});
it('renders label title', () => {
expect(wrapper.text()).toContain(mockRegularLabel.title);
expect(wrapper.text()).toContain(mockLabel.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