diff options
Diffstat (limited to 'app/assets/javascripts/work_items/components/work_item_assignees.vue')
-rw-r--r-- | app/assets/javascripts/work_items/components/work_item_assignees.vue | 95 |
1 files changed, 71 insertions, 24 deletions
diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue index 9ff424aa20f..7342f215b5e 100644 --- a/app/assets/javascripts/work_items/components/work_item_assignees.vue +++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue @@ -18,11 +18,17 @@ import { n__, s__ } from '~/locale'; import Tracking from '~/tracking'; import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; -import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql'; +import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; import { i18n, TRACKING_CATEGORY_SHOW } from '../constants'; function isTokenSelectorElement(el) { - return el?.classList.contains('gl-token-close') || el?.classList.contains('dropdown-item'); + return ( + el?.classList.contains('gl-token-close') || + el?.classList.contains('dropdown-item') || + // TODO: replace this logic when we have a class added to clear-all button in GitLab UI + (el?.classList.contains('gl-button') && + el?.closest('.form-control')?.classList.contains('gl-token-selector')) + ); } function addClass(el) { @@ -69,6 +75,11 @@ export default { required: false, default: false, }, + canInviteMembers: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -130,7 +141,7 @@ export default { if (this.searchUsers.some((user) => user.username === this.currentUser.username)) { return this.moveCurrentUserToStart(this.searchUsers); } - return [this.currentUser, ...this.searchUsers]; + return [addClass(this.currentUser), ...this.searchUsers]; } return this.searchUsers; }, @@ -138,16 +149,25 @@ export default { return this.searchKey.length === 0; }, addAssigneesText() { + if (!this.canUpdate) { + return s__('WorkItem|None'); + } return this.allowsMultipleAssignees ? s__('WorkItem|Add assignees') : s__('WorkItem|Add assignee'); }, + assigneeIds() { + return this.localAssignees.map(({ id }) => id); + }, }, watch: { - assignees(newVal) { - if (!this.isEditing) { - this.localAssignees = newVal.map(addClass); - } + assignees: { + handler(newVal) { + if (!this.isEditing) { + this.localAssignees = newVal.map(addClass); + } + }, + deep: true, }, }, created() { @@ -169,19 +189,33 @@ export default { handleBlur(e) { if (isTokenSelectorElement(e.relatedTarget) || !this.isEditing) return; this.isEditing = false; - this.setAssignees(this.localAssignees); + this.setAssignees(this.assigneeIds); }, - setAssignees(assignees) { - this.$apollo.mutate({ - mutation: localUpdateWorkItemMutation, - variables: { - input: { - id: this.workItemId, - assignees, + async setAssignees(assigneeIds) { + try { + const { + data: { + workItemUpdate: { errors }, }, - }, - }); - this.track('updated_assignees'); + } = await this.$apollo.mutate({ + mutation: updateWorkItemMutation, + variables: { + input: { + id: this.workItemId, + assigneesWidget: { + assigneeIds, + }, + }, + }, + }); + if (errors.length > 0) { + this.throwUpdateError(); + return; + } + this.track('updated_assignees'); + } catch { + this.throwUpdateError(); + } }, handleFocus() { this.isEditing = true; @@ -205,13 +239,25 @@ export default { }, moveCurrentUserToStart(users = []) { if (this.currentUser) { - return [this.currentUser, ...users.filter((user) => user.id !== this.currentUser.id)]; + return [ + addClass(this.currentUser), + ...users.filter((user) => user.id !== this.currentUser.id), + ]; } return users; }, closeDropdown() { this.$refs.tokenSelector.closeDropdown(); }, + assignToCurrentUser() { + this.setAssignees([this.currentUser.id]); + this.localAssignees = [addClass(this.currentUser)]; + }, + throwUpdateError() { + this.$emit('error', i18n.updateError); + // If mutation is rejected, we're rolling back to initial state + this.localAssignees = this.assignees.map(addClass); + }, }, }; </script> @@ -227,11 +273,12 @@ export default { ref="tokenSelector" :selected-tokens="localAssignees" :container-class="containerClass" - class="assignees-selector gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0!" :class="{ 'gl-hover-border-gray-200': canUpdate }" :dropdown-items="dropdownItems" :loading="isLoadingUsers" :view-only="!canUpdate" + :allow-clear-all="isEditing" + class="assignees-selector gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0! gl-mx-2" @input="handleAssigneesInput" @text-input="debouncedSearchKeyUpdate" @focus="handleFocus" @@ -241,7 +288,7 @@ export default { > <template #empty-placeholder> <div - class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2" + class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-pl-2 gl-top-2" data-testid="empty-state" > <gl-icon name="profile" /> @@ -251,7 +298,7 @@ export default { size="small" class="assign-myself" data-testid="assign-self" - @click.stop="setAssignees([currentUser])" + @click.stop="assignToCurrentUser" >{{ __('Assign myself') }}</gl-button > </div> @@ -262,7 +309,7 @@ export default { :title="token.name" :data-user-id="getUserId(token.id)" data-placement="top" - class="gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link" + class="gl-ml-n2 gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link" > <gl-avatar :size="24" :src="token.avatarUrl" /> <span class="gl-pl-2">{{ token.name }}</span> @@ -279,7 +326,7 @@ export default { <rect width="280" height="20" x="10" y="130" rx="4" /> </gl-skeleton-loader> </template> - <template #dropdown-footer> + <template v-if="canInviteMembers" #dropdown-footer> <gl-dropdown-divider /> <gl-dropdown-item @click="closeDropdown"> <invite-members-trigger |