Commit 4ed86290 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch...

Merge branch '235367-migrate-bootstrap-dropdown-to-gitlab-ui-gldropdown-in-app-assets-javascripts-boards' into 'master'

Refactor assignee select in board scope

See merge request gitlab-org/gitlab!66225
parents 38161a12 b7c83cc3
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { GlModal, GlAlert } from '@gitlab/ui'; import { GlModal, GlAlert } from '@gitlab/ui';
import { mapGetters, mapActions, mapState } from 'vuex'; import { mapGetters, mapActions, mapState } from 'vuex';
import ListLabel from '~/boards/models/label'; import ListLabel from '~/boards/models/label';
import { TYPE_ITERATION, TYPE_MILESTONE, TYPE_USER } from '~/graphql_shared/constants'; import { TYPE_ITERATION, TYPE_MILESTONE } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
import { getParameterByName, visitUrl } from '~/lib/utils/url_utility'; import { getParameterByName, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
...@@ -21,7 +21,6 @@ const boardDefaults = { ...@@ -21,7 +21,6 @@ const boardDefaults = {
milestone_id: undefined, milestone_id: undefined,
iteration_id: undefined, iteration_id: undefined,
assignee: {}, assignee: {},
assignee_id: undefined,
weight: null, weight: null,
hide_backlog_list: false, hide_backlog_list: false,
hide_closed_list: false, hide_closed_list: false,
...@@ -190,9 +189,7 @@ export default { ...@@ -190,9 +189,7 @@ export default {
issueBoardScopeMutationVariables() { issueBoardScopeMutationVariables() {
return { return {
weight: this.board.weight, weight: this.board.weight,
assigneeId: this.board.assignee?.id assigneeId: this.board.assignee?.id || null,
? convertToGraphQLId(TYPE_USER, this.board.assignee.id)
: null,
milestoneId: milestoneId:
this.board.milestone?.id || this.board.milestone?.id === 0 this.board.milestone?.id || this.board.milestone?.id === 0
? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id) ? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id)
...@@ -306,6 +303,11 @@ export default { ...@@ -306,6 +303,11 @@ export default {
} }
}); });
}, },
setAssignee(assigneeId) {
this.board.assignee = {
id: assigneeId,
};
},
}, },
}; };
</script> </script>
...@@ -373,6 +375,7 @@ export default { ...@@ -373,6 +375,7 @@ export default {
:weights="weights" :weights="weights"
@set-iteration="setIteration" @set-iteration="setIteration"
@set-board-labels="setBoardLabels" @set-board-labels="setBoardLabels"
@set-assignee="setAssignee"
/> />
</form> </form>
</gl-modal> </gl-modal>
......
#import "../fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
query usersSearch($search: String!, $fullPath: ID!) {
workspace: group(fullPath: $fullPath) {
users: groupMembers(search: $search, relations: [DIRECT, INHERITED]) {
nodes {
user {
...User
...UserAvailability
}
}
}
}
}
<script> <script>
import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; import {
import { __ } from '~/locale'; GlButton,
import UsersSelect from '~/users_select'; GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByType,
GlLoadingIcon,
} from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapActions, mapGetters } from 'vuex';
import searchGroupUsers from '~/graphql_shared/queries/group_users_search.query.graphql';
import searchProjectUsers from '~/graphql_shared/queries/users_search.query.graphql';
import { s__ } from '~/locale';
import { ASSIGNEES_DEBOUNCE_DELAY } from '~/sidebar/constants';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
export default { export default {
components: { components: {
UserAvatarImage, UserAvatarImage,
GlButton,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByType,
GlLoadingIcon, GlLoadingIcon,
GlIcon,
}, },
inject: ['fullPath'],
props: { props: {
anyUserText: {
type: String,
required: false,
default: __('Any user'),
},
board: { board: {
type: Object, type: Object,
required: true, required: true,
...@@ -25,150 +38,200 @@ export default { ...@@ -25,150 +38,200 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
fieldName: {
type: String,
required: true,
},
groupId: { groupId: {
type: Number, type: Number,
required: false, required: false,
default: 0, default: 0,
}, },
label: {
type: String,
required: true,
},
placeholderText: {
type: String,
required: false,
default: __('Select user'),
},
projectId: { projectId: {
type: Number, type: Number,
required: false, required: false,
default: 0, default: 0,
}, },
selected: {
type: Object,
required: false,
default: () => null,
}, },
wrapperClass: { data() {
type: String, return {
required: false, search: '',
default: '', searchUsers: [],
selected: this.board.assignee,
isEditing: false,
isDropdownShowing: false,
};
},
apollo: {
searchUsers: {
query() {
return this.isProjectBoard ? searchProjectUsers : searchGroupUsers;
},
variables() {
return {
fullPath: this.fullPath,
search: this.search,
first: 20,
};
},
skip() {
return !this.isEditing;
},
update(data) {
// TODO Remove null filter (BE fix required)
// https://gitlab.com/gitlab-org/gitlab/-/issues/329750
return data.workspace?.users?.nodes.filter((x) => x?.user).map(({ user }) => user) || [];
},
debounce: ASSIGNEES_DEBOUNCE_DELAY,
error() {
this.setError({ message: this.$options.i18n.errorSearchingUsers });
},
}, },
}, },
computed: { computed: {
hasValue() { ...mapGetters(['isProjectBoard']),
return this.selected && this.selected.id > 0; isLoading() {
return this.$apollo.queries.searchUsers.loading;
}, },
selectedId() { isSearchEmpty() {
return this.selected ? this.selected.id : null; return this.search === '' && !this.isLoading;
}, },
selectedIsEmpty() {
return isEmpty(this.selected);
}, },
watch: { noUsersFound() {
selected() { return !this.isSearchEmpty && this.users.length === 0;
this.initSelect();
}, },
users() {
const filteredUsers = this.searchUsers.filter(
(user) => user.name.includes(this.search) || user.username.includes(this.search),
);
// TODO this de-duplication is temporary (BE fix required)
// https://gitlab.com/gitlab-org/gitlab/-/issues/327822
return filteredUsers
.concat(this.searchUsers)
.reduce(
(acc, current) => (acc.some((user) => current.id === user.id) ? acc : [...acc, current]),
[],
);
}, },
mounted() {
this.initSelect();
}, },
methods: { methods: {
initSelect() { ...mapActions(['setError']),
this.userDropdown = new UsersSelect(null, this.$refs.dropdown, { selectAssignee(user) {
handleClick: this.selectUser, this.selected = user;
}); this.toggleEdit();
}, this.$emit('set-assignee', user?.id || null);
selectUser(user, isMarking) { },
let assignee = user; toggleEdit() {
if (!isMarking) { if (!this.isEditing && !this.isDropdownShowing) {
// correctly select "unassigned" in Assignee dropdown this.isEditing = true;
assignee = { this.showDropdown();
id: undefined, } else {
}; this.isEditing = false;
this.isDropdownShowing = false;
} }
// eslint-disable-next-line vue/no-mutating-props
this.board.assignee_id = assignee.id;
// eslint-disable-next-line vue/no-mutating-props
this.board.assignee = assignee;
}, },
isSelected(user) {
return this.selected?.username === user.username;
},
showDropdown() {
this.$refs.editDropdown.show();
this.isDropdownShowing = true;
},
setFocus() {
this.$refs.search.focusInput();
},
hideDropdown() {
this.isEditing = false;
},
},
i18n: {
label: s__('BoardScope|Assignee'),
anyAssignee: s__('BoardScope|Any assignee'),
selectAssignee: s__('BoardScope|Select assignee'),
noMatchingResults: s__('BoardScope|No matching results'),
errorSearchingUsers: s__(
'BoardScope|An error occurred while searching for users, please try again.',
),
edit: s__('BoardScope|Edit'),
}, },
}; };
</script> </script>
<template> <template>
<div :class="wrapperClass" class="block"> <div class="block assignee">
<div class="title gl-mb-3"> <div class="title gl-mb-3">
{{ label }} {{ $options.i18n.label }}
<button v-if="canEdit" type="button" class="edit-link btn btn-blank float-right"> <gl-button
{{ __('Edit') }} v-if="canEdit"
</button> variant="link"
</div> class="edit-link float-right gl-text-gray-900!"
<div class="value"> @click="toggleEdit"
<div v-if="hasValue" class="media gl-display-flex gl-align-items-center"> >
<div class="align-center"> {{ $options.i18n.edit }}
<user-avatar-image :img-src="selected.avatar_url" :size="32" /> </gl-button>
</div> </div>
<div class="media-body"> <div v-if="!isEditing" data-testid="selected-assignee">
<div class="bold author">{{ selected.name }}</div> <div v-if="!selectedIsEmpty" class="gl-display-flex gl-align-items-center">
<div class="username">@{{ selected.username }}</div> <user-avatar-image :img-src="selected.avatarUrl || selected.avatar_url" :size="32" />
<div>
<div class="gl-font-weight-bold">{{ selected.name }}</div>
<div>@{{ selected.username }}</div>
</div> </div>
</div> </div>
<div v-else class="text-secondary">{{ anyUserText }}</div> <div v-else class="gl-text-gray-500">{{ $options.i18n.anyAssignee }}</div>
</div> </div>
<div class="selectbox" style="display: none"> <gl-dropdown
<div class="dropdown"> v-show="isEditing"
<!-- eslint-disable @gitlab/vue-no-data-toggle --> ref="editDropdown"
<button :text="$options.i18n.selectAssignee"
ref="dropdown" lazy
:data-field-name="fieldName" menu-class="gl-w-full!"
:data-dropdown-title="placeholderText" class="gl-w-full"
:data-any-user="anyUserText" @shown="setFocus"
:data-group-id="groupId" @hide="hideDropdown"
:data-project-id="projectId"
:data-selected="selectedId"
class="dropdown-menu-toggle wide"
data-toggle="dropdown"
aria-expanded="false"
type="button"
> >
<span class="dropdown-toggle-text">{{ placeholderText }}</span> <template #header>
<gl-icon <gl-search-box-by-type ref="search" v-model.trim="search" class="js-dropdown-input-field" />
name="chevron-down" </template>
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500" <gl-dropdown-form class="gl-relative gl-min-h-7">
:size="16" <gl-loading-icon
v-if="isLoading"
size="md"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/> />
</button> <template v-else>
<!-- eslint-enable @gitlab/vue-no-data-toggle --> <gl-dropdown-item
v-if="isSearchEmpty"
<div :is-checked="selectedIsEmpty"
class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-user dropdown-menu-selectable dropdown-menu-author" :is-check-centered="true"
@click="selectAssignee(null)"
> >
<div class="dropdown-input"> <span :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'" class="gl-font-weight-bold">
<input {{ $options.i18n.anyAssignee }}
autocomplete="off" </span>
class="dropdown-input-field" </gl-dropdown-item>
:placeholder="__('Search')" <gl-dropdown-divider />
type="search" <gl-dropdown-item
/> v-for="user in users"
<gl-icon :key="user.id"
name="search" :is-checked="isSelected(user)"
class="dropdown-input-search gl-absolute gl-top-3 gl-right-5 gl-text-gray-300 gl-pointer-events-none" :is-check-centered="true"
/> :is-check-item="true"
<gl-icon :avatar-url="user.avatar_url || user.avatarUrl"
name="close" :secondary-text="user.username"
class="dropdown-input-clear js-dropdown-input-clear gl-absolute gl-top-3 gl-right-5 gl-text-gray-500" data-testid="unselected-user"
/> @click="selectAssignee(user)"
</div> >
<div class="dropdown-content"></div> {{ user.name }}
<div class="dropdown-loading"> </gl-dropdown-item>
<gl-loading-icon size="sm" /> <gl-dropdown-item v-if="noUsersFound" class="gl-pl-6!">
</div> {{ $options.i18n.noMatchingResults }}
</div> </gl-dropdown-item>
</div> </template>
</div> </gl-dropdown-form>
<template #footer>
<slot name="footer"></slot>
</template>
</gl-dropdown>
</div> </div>
</template> </template>
...@@ -134,15 +134,10 @@ export default { ...@@ -134,15 +134,10 @@ export default {
<assignee-select <assignee-select
v-if="isIssueBoard" v-if="isIssueBoard"
:board="board" :board="board"
:selected="board.assignee"
:can-edit="canAdminBoard" :can-edit="canAdminBoard"
:project-id="projectId" :project-id="projectId"
:group-id="groupId" :group-id="groupId"
any-user-text="Any assignee" @set-assignee="$emit('set-assignee', $event)"
field-name="assignee_id"
label="Assignee"
placeholder-text="Select assignee"
wrapper-class="assignee"
/> />
<!-- eslint-disable vue/no-mutating-props --> <!-- eslint-disable vue/no-mutating-props -->
......
...@@ -229,7 +229,7 @@ RSpec.describe 'Scoped issue boards', :js do ...@@ -229,7 +229,7 @@ RSpec.describe 'Scoped issue boards', :js do
edit_board.click edit_board.click
expect(find('.milestone .value')).to have_content(milestone.title) expect(find('.milestone .value')).to have_content(milestone.title)
expect(find('.assignee .value')).to have_content(user.name) expect(find('[data-testid="selected-assignee"]')).to have_content(user.name)
expect(find('.weight .value')).to have_content(2) expect(find('.weight .value')).to have_content(2)
end end
...@@ -564,7 +564,7 @@ RSpec.describe 'Scoped issue boards', :js do ...@@ -564,7 +564,7 @@ RSpec.describe 'Scoped issue boards', :js do
click_button value click_button value
end end
else else
click_link value click_on value
end end
end end
end end
......
import MockAdapter from 'axios-mock-adapter'; import { GlButton, GlDropdown } from '@gitlab/ui';
import Vue from 'vue'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import AssigneeSelect from 'ee/boards/components/assignee_select.vue'; import AssigneeSelect from 'ee/boards/components/assignee_select.vue';
import { boardObj } from 'jest/boards/mock_data';
import boardsStore from '~/boards/stores/boards_store';
import IssuableContext from '~/issuable_context';
import axios from '~/lib/utils/axios_utils';
let vm;
function selectedText() { import createMockApollo from 'helpers/mock_apollo_helper';
return vm.$el.querySelector('.value').innerText.trim(); import waitForPromises from 'helpers/wait_for_promises';
}
function activeDropdownItem(index) { import { boardObj } from 'jest/boards/mock_data';
const items = document.querySelectorAll('.is-active'); import { projectMembersResponse, groupMembersResponse, mockUser2 } from 'jest/sidebar/mock_data';
if (!items[index]) return '';
return items[index].innerText.trim();
}
const assignee = { import defaultStore from '~/boards/stores';
id: 1, import searchGroupUsersQuery from '~/graphql_shared/queries/group_users_search.query.graphql';
name: 'first assignee', import searchProjectUsersQuery from '~/graphql_shared/queries/users_search.query.graphql';
}; import { ASSIGNEES_DEBOUNCE_DELAY } from '~/sidebar/constants';
const assignee2 = { const localVue = createLocalVue();
id: 2, localVue.use(VueApollo);
name: 'second assignee',
};
describe('Assignee select component', () => { describe('Assignee select component', () => {
beforeEach((done) => { let wrapper;
setFixtures('<div class="test-container"></div>'); let fakeApollo;
boardsStore.create(); let store;
// eslint-disable-next-line no-new const selectedText = () => wrapper.find('[data-testid="selected-assignee"]').text();
new IssuableContext(); const findEditButton = () => wrapper.findComponent(GlButton);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const Component = Vue.extend(AssigneeSelect);
vm = new Component({ const usersQueryHandlerSuccess = jest.fn().mockResolvedValue(projectMembersResponse);
const groupUsersQueryHandlerSuccess = jest.fn().mockResolvedValue(groupMembersResponse);
const createStore = ({ isGroupBoard = false, isProjectBoard = false } = {}) => {
store = new Vuex.Store({
...defaultStore,
getters: {
isGroupBoard: () => isGroupBoard,
isProjectBoard: () => isProjectBoard,
},
});
};
const createComponent = ({ props = {}, usersQueryHandler = usersQueryHandlerSuccess } = {}) => {
fakeApollo = createMockApollo([
[searchProjectUsersQuery, usersQueryHandler],
[searchGroupUsersQuery, groupUsersQueryHandlerSuccess],
]);
wrapper = shallowMount(AssigneeSelect, {
localVue,
store,
apolloProvider: fakeApollo,
propsData: { propsData: {
board: boardObj, board: boardObj,
assigneePath: '/test/issue-boards/assignees.json',
canEdit: true, canEdit: true,
label: 'Assignee', ...props,
selected: {}, },
fieldName: 'assignee_id', provide: {
anyUserText: 'Any assignee', fullPath: 'gitlab-org',
}, },
}).$mount('.test-container');
setImmediate(done);
}); });
describe('canEdit', () => { // We need to mock out `showDropdown` which
it('hides Edit button', (done) => { // invokes `show` method of BDropdown used inside GlDropdown.
vm.canEdit = false; jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation();
Vue.nextTick(() => { };
expect(vm.$el.querySelector('.edit-link')).toBeFalsy();
done();
});
});
it('shows Edit button if true', (done) => { beforeEach(() => {
vm.canEdit = true; createStore({ isProjectBoard: true });
Vue.nextTick(() => { createComponent();
expect(vm.$el.querySelector('.edit-link')).toBeTruthy();
done();
});
}); });
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
store = null;
}); });
describe('selected value', () => { describe('when not editing', () => {
it('defaults to Any Assignee', () => { it('defaults to Any Assignee', () => {
expect(selectedText()).toContain('Any assignee'); expect(selectedText()).toContain('Any assignee');
}); });
it('shows selected assignee', (done) => { it('skips the queries and does not render dropdown', () => {
vm.selected = assignee; expect(usersQueryHandlerSuccess).not.toHaveBeenCalled();
Vue.nextTick(() => { expect(findDropdown().isVisible()).toBe(false);
expect(selectedText()).toContain('first assignee');
done();
}); });
}); });
describe('clicking dropdown items', () => { describe('when editing', () => {
let mock; it('trigger query and renders dropdown with returned users', async () => {
findEditButton().vm.$emit('click');
await waitForPromises();
jest.advanceTimersByTime(ASSIGNEES_DEBOUNCE_DELAY);
await nextTick();
expect(usersQueryHandlerSuccess).toHaveBeenCalled();
beforeEach(() => { expect(findDropdown().isVisible()).toBe(true);
mock = new MockAdapter(axios); expect(wrapper.findAll('[data-testid="unselected-user"]')).toHaveLength(3); // 2 users + Any assignee item
mock.onGet('/-/autocomplete/users.json').reply(200, [assignee, assignee2]);
}); });
afterEach(() => { it('renders selected assignee', async () => {
mock.restore(); findEditButton().vm.$emit('click');
}); await waitForPromises();
jest.advanceTimersByTime(ASSIGNEES_DEBOUNCE_DELAY);
await nextTick();
it('sets assignee', (done) => { wrapper
vm.$el.querySelector('.edit-link').click(); .findAll('[data-testid="unselected-user"]')
.at(1)
.vm.$emit('click', new Event('click'));
jest.runOnlyPendingTimers(); await waitForPromises();
expect(selectedText()).toContain(mockUser2.username);
});
});
setImmediate(() => { describe('canEdit', () => {
vm.$el.querySelectorAll('li a')[2].click(); it('hides Edit button', async () => {
wrapper.setProps({ canEdit: false });
await nextTick();
setImmediate(() => { expect(findEditButton().exists()).toBe(false);
expect(activeDropdownItem(0)).toEqual('second assignee');
expect(vm.board.assignee).toEqual(assignee2);
done();
});
}); });
it('shows Edit button if true', () => {
expect(findEditButton().exists()).toBe(true);
}); });
}); });
it.each`
boardType | mockedResponse | queryHandler | notCalledHandler
${'group'} | ${groupMembersResponse} | ${groupUsersQueryHandlerSuccess} | ${usersQueryHandlerSuccess}
${'project'} | ${projectMembersResponse} | ${usersQueryHandlerSuccess} | ${groupUsersQueryHandlerSuccess}
`(
'fetches $boardType users',
async ({ boardType, mockedResponse, queryHandler, notCalledHandler }) => {
createStore({ isProjectBoard: boardType === 'project', isGroupBoard: boardType === 'group' });
createComponent({
[queryHandler]: jest.fn().mockResolvedValue(mockedResponse),
}); });
findEditButton().vm.$emit('click');
await waitForPromises();
jest.advanceTimersByTime(ASSIGNEES_DEBOUNCE_DELAY);
await nextTick();
expect(queryHandler).toHaveBeenCalled();
expect(notCalledHandler).not.toHaveBeenCalled();
},
);
}); });
import { createLocalVue, mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardScope from 'ee/boards/components/board_scope.vue'; import BoardScope from 'ee/boards/components/board_scope.vue';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer'; import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
const localVue = createLocalVue(); Vue.use(Vuex);
localVue.use(Vuex);
describe('BoardScope', () => { describe('BoardScope', () => {
let wrapper; let wrapper;
...@@ -25,7 +24,6 @@ describe('BoardScope', () => { ...@@ -25,7 +24,6 @@ describe('BoardScope', () => {
function mountComponent() { function mountComponent() {
wrapper = mount(BoardScope, { wrapper = mount(BoardScope, {
localVue,
store, store,
propsData: { propsData: {
collapseScope: false, collapseScope: false,
...@@ -37,6 +35,9 @@ describe('BoardScope', () => { ...@@ -37,6 +35,9 @@ describe('BoardScope', () => {
labelsPath: `${TEST_HOST}/labels`, labelsPath: `${TEST_HOST}/labels`,
labelsWebUrl: `${TEST_HOST}/-/labels`, labelsWebUrl: `${TEST_HOST}/-/labels`,
}, },
stubs: {
AssigneeSelect: true,
},
}); });
} }
......
...@@ -3912,9 +3912,6 @@ msgstr "" ...@@ -3912,9 +3912,6 @@ msgstr ""
msgid "Any namespace" msgid "Any namespace"
msgstr "" msgstr ""
msgid "Any user"
msgstr ""
msgid "App ID" msgid "App ID"
msgstr "" msgstr ""
...@@ -5299,6 +5296,24 @@ msgstr "" ...@@ -5299,6 +5296,24 @@ msgstr ""
msgid "BoardNewIssue|Select a project" msgid "BoardNewIssue|Select a project"
msgstr "" msgstr ""
msgid "BoardScope|An error occurred while searching for users, please try again."
msgstr ""
msgid "BoardScope|Any assignee"
msgstr ""
msgid "BoardScope|Assignee"
msgstr ""
msgid "BoardScope|Edit"
msgstr ""
msgid "BoardScope|No matching results"
msgstr ""
msgid "BoardScope|Select assignee"
msgstr ""
msgid "Boards" msgid "Boards"
msgstr "" msgstr ""
...@@ -29585,9 +29600,6 @@ msgstr "" ...@@ -29585,9 +29600,6 @@ msgstr ""
msgid "Select type" msgid "Select type"
msgstr "" msgstr ""
msgid "Select user"
msgstr ""
msgid "Selected" msgid "Selected"
msgstr "" msgstr ""
......
...@@ -415,7 +415,7 @@ const mockUser1 = { ...@@ -415,7 +415,7 @@ const mockUser1 = {
status: null, status: null,
}; };
const mockUser2 = { export const mockUser2 = {
id: 'gid://gitlab/User/4', id: 'gid://gitlab/User/4',
avatarUrl: '/avatar2', avatarUrl: '/avatar2',
name: 'rookie', name: 'rookie',
...@@ -452,9 +452,40 @@ export const projectMembersResponse = { ...@@ -452,9 +452,40 @@ export const projectMembersResponse = {
null, null,
null, null,
// Remove duplicated entry https://gitlab.com/gitlab-org/gitlab/-/issues/327822 // Remove duplicated entry https://gitlab.com/gitlab-org/gitlab/-/issues/327822
mockUser1, { user: mockUser1 },
mockUser1, { user: mockUser1 },
mockUser2, { user: mockUser2 },
{
user: {
id: 'gid://gitlab/User/2',
avatarUrl:
'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
name: 'Jacki Kub',
username: 'francina.skiles',
webUrl: '/franc',
status: {
availability: 'BUSY',
},
},
},
],
},
},
},
};
export const groupMembersResponse = {
data: {
workspace: {
__typename: 'roup',
users: {
nodes: [
// Remove nulls https://gitlab.com/gitlab-org/gitlab/-/issues/329750
null,
null,
// Remove duplicated entry https://gitlab.com/gitlab-org/gitlab/-/issues/327822
{ user: mockUser1 },
{ user: mockUser1 },
{ {
user: { user: {
id: 'gid://gitlab/User/2', id: 'gid://gitlab/User/2',
......
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