Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/work_items/components/work_item_assignees_with_edit.vue')
-rw-r--r--app/assets/javascripts/work_items/components/work_item_assignees_with_edit.vue292
1 files changed, 292 insertions, 0 deletions
diff --git a/app/assets/javascripts/work_items/components/work_item_assignees_with_edit.vue b/app/assets/javascripts/work_items/components/work_item_assignees_with_edit.vue
new file mode 100644
index 00000000000..bb7baed29f6
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_assignees_with_edit.vue
@@ -0,0 +1,292 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { isEmpty } from 'lodash';
+import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
+import groupUsersSearchQuery from '~/graphql_shared/queries/group_users_search.query.graphql';
+import usersSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
+import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
+import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
+import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
+import WorkItemSidebarDropdownWidgetWithEdit from '~/work_items/components/shared/work_item_sidebar_dropdown_widget_with_edit.vue';
+import { s__, sprintf, __ } from '~/locale';
+import Tracking from '~/tracking';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
+import { i18n, TRACKING_CATEGORY_SHOW, DEFAULT_PAGE_SIZE_ASSIGNEES } from '../constants';
+
+export default {
+ components: {
+ WorkItemSidebarDropdownWidgetWithEdit,
+ InviteMembersTrigger,
+ SidebarParticipant,
+ GlButton,
+ UncollapsedAssigneeList,
+ },
+ mixins: [Tracking.mixin()],
+ inject: ['isGroup'],
+ props: {
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ workItemId: {
+ type: String,
+ required: true,
+ },
+ assignees: {
+ type: Array,
+ required: true,
+ },
+ allowsMultipleAssignees: {
+ type: Boolean,
+ required: true,
+ },
+ workItemType: {
+ type: String,
+ required: true,
+ },
+ canUpdate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ canInviteMembers: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ localAssigneeIds: this.assignees.map(({ id }) => id),
+ searchStarted: false,
+ searchKey: '',
+ users: {
+ nodes: [],
+ },
+ currentUser: null,
+ isLoadingMore: false,
+ updateInProgress: false,
+ };
+ },
+ apollo: {
+ users: {
+ query() {
+ return this.isGroup ? groupUsersSearchQuery : usersSearchQuery;
+ },
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ search: this.searchKey,
+ first: DEFAULT_PAGE_SIZE_ASSIGNEES,
+ };
+ },
+ skip() {
+ return !this.searchStarted;
+ },
+ update(data) {
+ return data.workspace?.users;
+ },
+ error() {
+ this.$emit('error', i18n.fetchError);
+ },
+ },
+ currentUser: {
+ query: currentUserQuery,
+ },
+ },
+ computed: {
+ searchUsers() {
+ return this.users.nodes.map(({ user }) => ({
+ ...user,
+ value: user.id,
+ text: user.name,
+ }));
+ },
+ pageInfo() {
+ return this.users.pageInfo;
+ },
+ tracking() {
+ return {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'item_assignees',
+ property: `type_${this.workItemType}`,
+ };
+ },
+ isLoadingUsers() {
+ return this.$apollo.queries.users.loading && !this.isLoadingMore;
+ },
+ hasNextPage() {
+ return this.pageInfo?.hasNextPage;
+ },
+ selectedAssigneeIds() {
+ return this.allowsMultipleAssignees ? this.localAssigneeIds : this.localAssigneeIds[0];
+ },
+ dropdownText() {
+ if (this.localAssigneeIds.length === 0) {
+ return s__('WorkItem|No assignees');
+ }
+
+ return this.localAssigneeIds.length === 1
+ ? this.localAssignees.map(({ name }) => name).join(', ')
+ : sprintf(s__('WorkItem|%{usersLength} assignees'), {
+ usersLength: this.localAssigneeIds.length,
+ });
+ },
+ dropdownLabel() {
+ return this.allowsMultipleAssignees ? __('Assignees') : __('Assignee');
+ },
+ headerText() {
+ return this.allowsMultipleAssignees ? __('Select assignees') : __('Select assignee');
+ },
+ filteredAssignees() {
+ return isEmpty(this.searchUsers)
+ ? this.assignees
+ : this.searchUsers.filter(({ id }) => this.localAssigneeIds.includes(id));
+ },
+ localAssignees() {
+ return this.filteredAssignees || [];
+ },
+ },
+ watch: {
+ assignees: {
+ handler(newVal) {
+ this.localAssigneeIds = newVal.map(({ id }) => id);
+ },
+ deep: true,
+ },
+ },
+ methods: {
+ handleAssigneesInput(assignees) {
+ this.setLocalAssigneeIdsOnEvent(assignees);
+ this.setAssignees();
+ },
+ handleAssigneeClick(assignees) {
+ this.setLocalAssigneeIdsOnEvent(assignees);
+ },
+ async setAssignees() {
+ this.updateInProgress = true;
+ const { localAssigneeIds } = this;
+
+ try {
+ const {
+ data: {
+ workItemUpdate: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: updateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ assigneesWidget: {
+ assigneeIds: localAssigneeIds,
+ },
+ },
+ },
+ });
+ if (errors.length > 0) {
+ this.throwUpdateError();
+ return;
+ }
+ this.track('updated_assignees');
+ } catch {
+ this.throwUpdateError();
+ } finally {
+ this.updateInProgress = false;
+ }
+ },
+ setLocalAssigneeIdsOnEvent(assignees) {
+ const singleSelectAssignee = assignees === null ? [] : [assignees];
+ this.localAssigneeIds = this.allowsMultipleAssignees ? assignees : singleSelectAssignee;
+ },
+ async fetchMoreAssignees() {
+ if (this.isLoadingMore && !this.hasNextPage) return;
+
+ this.isLoadingMore = true;
+ await this.$apollo.queries.users.fetchMore({
+ variables: {
+ after: this.pageInfo.endCursor,
+ first: DEFAULT_PAGE_SIZE_ASSIGNEES,
+ },
+ });
+ this.isLoadingMore = false;
+ },
+ setSearchKey(value) {
+ this.searchKey = value;
+ this.searchStarted = true;
+ },
+ assignToCurrentUser() {
+ const assignees = this.allowsMultipleAssignees ? [this.currentUser.id] : this.currentUser.id;
+ this.setLocalAssigneeIdsOnEvent(assignees);
+ this.setAssignees();
+ },
+ throwUpdateError() {
+ this.$emit('error', i18n.updateError);
+ // If mutation is rejected, we're rolling back to initial state
+ this.localAssigneeIds = this.assignees.map(({ id }) => id);
+ },
+ onDropdownShown() {
+ this.searchStarted = true;
+ },
+ },
+};
+</script>
+
+<template>
+ <work-item-sidebar-dropdown-widget-with-edit
+ :multi-select="allowsMultipleAssignees"
+ class="issuable-assignees gl-mt-2"
+ :dropdown-label="dropdownLabel"
+ :can-update="canUpdate"
+ dropdown-name="assignees"
+ show-footer
+ :infinite-scroll="hasNextPage"
+ :infinite-scroll-loading="isLoadingMore"
+ :loading="isLoadingUsers"
+ :list-items="searchUsers"
+ :item-value="selectedAssigneeIds"
+ :toggle-dropdown-text="dropdownText"
+ :header-text="headerText"
+ :update-in-progress="updateInProgress"
+ :reset-button-label="__('Clear')"
+ data-testid="work-item-assignees-with-edit"
+ @dropdownShown="onDropdownShown"
+ @searchStarted="setSearchKey"
+ @updateValue="handleAssigneesInput"
+ @updateSelected="handleAssigneeClick"
+ @bottomReached="fetchMoreAssignees"
+ >
+ <template #list-item="{ item }">
+ <sidebar-participant :user="item" />
+ </template>
+ <template v-if="canInviteMembers" #footer>
+ <gl-button category="tertiary" block class="gl-justify-content-start!">
+ <invite-members-trigger
+ :display-text="__('Invite members')"
+ trigger-element="side-nav"
+ icon="plus"
+ trigger-source="work-item-assignees-with-edit"
+ classes="gl-hover-text-decoration-none! gl-pb-2"
+ />
+ </gl-button>
+ </template>
+ <template #none>
+ <div class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-gap-2">
+ <span>{{ __('None') }}</span>
+ <template v-if="currentUser && canUpdate">
+ <span>-</span>
+ <gl-button variant="link" data-testid="assign-self" @click.stop="assignToCurrentUser"
+ ><span class="gl-text-gray-500 gl-hover-text-blue-800">{{
+ __('assign yourself')
+ }}</span></gl-button
+ >
+ </template>
+ </div>
+ </template>
+ <template #readonly>
+ <uncollapsed-assignee-list
+ :users="localAssignees"
+ show-less-assignees-class="gl-hover-bg-transparent!"
+ />
+ </template>
+ </work-item-sidebar-dropdown-widget-with-edit>
+</template>