Skip to content
代码片段 群组 项目
未验证 提交 e24ff607 编辑于 作者: Jose Ivan Vargas's avatar Jose Ivan Vargas 提交者: GitLab
浏览文件

Merge branch 'psi-top-selected' into 'master'

Show selected assignees at top of the list

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148858



Merged-by: default avatarJose Ivan Vargas <jvargas@gitlab.com>
Approved-by: default avatarBriley Sandlin <bsandlin@gitlab.com>
Approved-by: default avatarNick Leonard <nleonard@gitlab.com>
Reviewed-by: default avatarSimon Knox <simon@gitlab.com>
Co-authored-by: default avatarSimon Knox <simon@gitlab.com>
No related branches found
No related tags found
无相关合并请求
...@@ -207,6 +207,7 @@ export default { ...@@ -207,6 +207,7 @@ export default {
:multiple="multiSelect" :multiple="multiSelect"
:searchable="searchable" :searchable="searchable"
start-opened start-opened
block
is-check-centered is-check-centered
:infinite-scroll="infiniteScroll" :infinite-scroll="infiniteScroll"
:searching="loading" :searching="loading"
...@@ -217,7 +218,7 @@ export default { ...@@ -217,7 +218,7 @@ export default {
:selected="localSelectedItem" :selected="localSelectedItem"
:reset-button-label="resetButton" :reset-button-label="resetButton"
:infinite-scroll-loading="infiniteScrollLoading" :infinite-scroll-loading="infiniteScrollLoading"
toggle-class="gl-w-full! work-item-sidebar-dropdown-toggle" toggle-class="work-item-sidebar-dropdown-toggle"
@reset="unassignValue" @reset="unassignValue"
@search="debouncedSearchKeyUpdate" @search="debouncedSearchKeyUpdate"
@select="handleItemClick" @select="handleItemClick"
......
...@@ -67,7 +67,8 @@ export default { ...@@ -67,7 +67,8 @@ export default {
}, },
data() { data() {
return { return {
localAssigneeIds: this.assignees.map(({ id }) => id), localAssigneeIds: [],
assigneeIdsToShowAtTopOfTheListbox: [],
searchStarted: false, searchStarted: false,
searchKey: '', searchKey: '',
users: [], users: [],
...@@ -111,14 +112,51 @@ export default { ...@@ -111,14 +112,51 @@ export default {
}, },
}, },
computed: { computed: {
shouldShowParticipants() {
return this.searchKey === '';
},
searchUsers() { searchUsers() {
const allUsers = this.shouldShowParticipants // when there is no search text, then we show selected users first
? unionBy(this.users, this.participants, 'id') // followed by participants, then all other users
: this.users; if (this.searchKey === '') {
return allUsers.map((user) => ({ const alphabetizedUsers = unionBy(this.users, this.participants, 'id').sort(
sortNameAlphabetically,
);
if (alphabetizedUsers.length === 0) {
return [];
}
const currentUser = alphabetizedUsers.find(({ id }) => id === this.currentUser?.id);
const allUsers = unionBy([currentUser], alphabetizedUsers, 'id').map((user) => ({
...user,
value: user?.id,
text: user?.name,
}));
const selectedUsers =
allUsers
.filter(({ id }) => this.assigneeIdsToShowAtTopOfTheListbox.includes(id))
.sort(sortNameAlphabetically) || [];
const unselectedUsers = allUsers.filter(
({ id }) => !this.assigneeIdsToShowAtTopOfTheListbox.includes(id),
);
// don't show the selected section if it's empty
if (selectedUsers.length === 0) {
return allUsers.map((user) => ({
...user,
value: user?.id,
text: user?.name,
}));
}
return [
{ options: selectedUsers, text: __('Selected') },
{ options: unselectedUsers, text: __('All users'), textSrOnly: true },
];
}
return this.users.map((user) => ({
...user, ...user,
value: user?.id, value: user?.id,
text: user?.name, text: user?.name,
...@@ -174,8 +212,15 @@ export default { ...@@ -174,8 +212,15 @@ export default {
assignees: { assignees: {
handler(newVal) { handler(newVal) {
this.localAssigneeIds = newVal.map(({ id }) => id); this.localAssigneeIds = newVal.map(({ id }) => id);
this.assigneeIdsToShowAtTopOfTheListbox = this.localAssigneeIds;
}, },
deep: true, deep: true,
immediate: true,
},
searchKey(newVal, oldVal) {
if (newVal === '' && oldVal !== '') {
this.assigneeIdsToShowAtTopOfTheListbox = this.localAssigneeIds;
}
}, },
}, },
methods: { methods: {
...@@ -241,7 +286,8 @@ export default { ...@@ -241,7 +286,8 @@ export default {
this.searchStarted = true; this.searchStarted = true;
}, },
onDropdownHide() { onDropdownHide() {
this.setSearchKey('', false); this.setSearchKey('');
this.assigneeIdsToShowAtTopOfTheListbox = this.localAssigneeIds;
}, },
}, },
}; };
...@@ -271,7 +317,7 @@ export default { ...@@ -271,7 +317,7 @@ export default {
@dropdownHidden="onDropdownHide" @dropdownHidden="onDropdownHide"
> >
<template #list-item="{ item }"> <template #list-item="{ item }">
<sidebar-participant :user="item" /> <sidebar-participant v-if="item" :user="item" />
</template> </template>
<template v-if="canInviteMembers" #footer> <template v-if="canInviteMembers" #footer>
<gl-button category="tertiary" block class="gl-justify-content-start!"> <gl-button category="tertiary" block class="gl-justify-content-start!">
......
...@@ -5104,6 +5104,9 @@ msgstr "" ...@@ -5104,6 +5104,9 @@ msgstr ""
msgid "All threads resolved!" msgid "All threads resolved!"
msgstr "" msgstr ""
   
msgid "All users"
msgstr ""
msgid "All users in this group must set up two-factor authentication" msgid "All users in this group must set up two-factor authentication"
msgstr "" msgstr ""
   
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { cloneDeep } from 'lodash';
import { sortNameAlphabetically } from '~/work_items/utils'; import { sortNameAlphabetically } from '~/work_items/utils';
import WorkItemAssignees from '~/work_items/components/work_item_assignees_with_edit.vue'; import WorkItemAssignees from '~/work_items/components/work_item_assignees_with_edit.vue';
import WorkItemSidebarDropdownWidgetWithEdit from '~/work_items/components/shared/work_item_sidebar_dropdown_widget_with_edit.vue'; import WorkItemSidebarDropdownWidgetWithEdit from '~/work_items/components/shared/work_item_sidebar_dropdown_widget_with_edit.vue';
...@@ -57,6 +58,10 @@ describe('WorkItemAssigneesWithEdit component', () => { ...@@ -57,6 +58,10 @@ describe('WorkItemAssigneesWithEdit component', () => {
findSidebarDropdownWidget().vm.$emit('dropdownShown'); findSidebarDropdownWidget().vm.$emit('dropdownShown');
}; };
const hideDropdown = () => {
findSidebarDropdownWidget().vm.$emit('dropdownHidden');
};
const createComponent = ({ const createComponent = ({
mountFn = shallowMountExtended, mountFn = shallowMountExtended,
assignees = mockAssignees, assignees = mockAssignees,
...@@ -102,7 +107,7 @@ describe('WorkItemAssigneesWithEdit component', () => { ...@@ -102,7 +107,7 @@ describe('WorkItemAssigneesWithEdit component', () => {
showDropdown(); showDropdown();
await waitForPromises(); await waitForPromises();
expect(findSidebarDropdownWidget().props('listItems')).toHaveLength(0); expect(findSidebarDropdownWidget().props('listItems')).toEqual([]);
}); });
it('emits error event if search users query fails', async () => { it('emits error event if search users query fails', async () => {
...@@ -113,7 +118,7 @@ describe('WorkItemAssigneesWithEdit component', () => { ...@@ -113,7 +118,7 @@ describe('WorkItemAssigneesWithEdit component', () => {
expect(wrapper.emitted('error')).toEqual([[i18n.fetchError]]); expect(wrapper.emitted('error')).toEqual([[i18n.fetchError]]);
}); });
it('passes the correct props to clear search text on item select', () => { it('clears search text on item select', () => {
createComponent(); createComponent();
expect(findSidebarDropdownWidget().props('clearSearchOnItemSelect')).toBe(true); expect(findSidebarDropdownWidget().props('clearSearchOnItemSelect')).toBe(true);
...@@ -253,7 +258,7 @@ describe('WorkItemAssigneesWithEdit component', () => { ...@@ -253,7 +258,7 @@ describe('WorkItemAssigneesWithEdit component', () => {
}); });
describe('sorting', () => { describe('sorting', () => {
it('always sorts assignees based on alphabetical order on the frontend', async () => { it('sorts assignees based on alphabetical order on the frontend', async () => {
createComponent({ mountFn: mountExtended }); createComponent({ mountFn: mountExtended });
await waitForPromises(); await waitForPromises();
...@@ -263,6 +268,74 @@ describe('WorkItemAssigneesWithEdit component', () => { ...@@ -263,6 +268,74 @@ describe('WorkItemAssigneesWithEdit component', () => {
mockAssignees.sort(sortNameAlphabetically), mockAssignees.sort(sortNameAlphabetically),
); );
}); });
it('sorts selected assignees first', async () => {
const [
unselected,
selected,
] = projectMembersAutocompleteResponseWithCurrentUser.data.workspace.users;
createComponent({
assignees: [selected],
});
showDropdown();
await waitForPromises();
expect(findSidebarDropdownWidget().props('listItems')).toMatchObject(
cloneDeep([
{ options: [selected], text: 'Selected' },
{ options: [unselected], text: 'All users', textSrOnly: true },
]),
);
});
it('shows current user above other users', async () => {
const [unselected, currentUser] = cloneDeep(
projectMembersAutocompleteResponseWithCurrentUser.data.workspace.users,
);
createComponent({
assignees: [],
});
showDropdown();
await waitForPromises();
findSidebarDropdownWidget().vm.$emit('updateValue', currentUser.id);
expect(findSidebarDropdownWidget().props('listItems')).toMatchObject([
{ text: currentUser.name },
{ text: unselected.name },
]);
});
it('does not move newly selected assignees to the top until dropdown is closed', async () => {
const [unselected, currentUser] = cloneDeep(
projectMembersAutocompleteResponseWithCurrentUser.data.workspace.users,
);
createComponent({
assignees: [],
});
showDropdown();
await waitForPromises();
findSidebarDropdownWidget().vm.$emit('updateValue', currentUser.id);
expect(findSidebarDropdownWidget().props('listItems')).toMatchObject([
{ text: currentUser.name },
{ text: unselected.name },
]);
hideDropdown();
await waitForPromises();
showDropdown();
await waitForPromises();
expect(findSidebarDropdownWidget().props('listItems')).toMatchObject([
{ options: [currentUser], text: 'Selected' },
{ options: [unselected], text: 'All users', textSrOnly: true },
]);
});
}); });
describe('invite members', () => { describe('invite members', () => {
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册