298 lines
8.4 KiB
Vue
298 lines
8.4 KiB
Vue
<script>
|
|
import {
|
|
GlIcon,
|
|
GlDropdown,
|
|
GlDropdownDivider,
|
|
GlDropdownSectionHeader,
|
|
GlDropdownItem,
|
|
GlSearchBoxByType,
|
|
GlLoadingIcon,
|
|
GlTooltip,
|
|
GlButton,
|
|
GlSprintf,
|
|
} from '@gitlab/ui';
|
|
import { debounce } from 'lodash';
|
|
import axios from '~/lib/utils/axios_utils';
|
|
import { s__ } from '~/locale';
|
|
import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.mutation.graphql';
|
|
import SidebarAssignee from './sidebar_assignee.vue';
|
|
|
|
const DATA_REFETCH_DELAY = 250;
|
|
|
|
export default {
|
|
i18n: {
|
|
FETCH_USERS_ERROR: s__(
|
|
'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
|
|
),
|
|
UPDATE_ALERT_ASSIGNEES_ERROR: s__(
|
|
'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
|
|
),
|
|
UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR: s__(
|
|
'AlertManagement|This assignee cannot be assigned to this alert.',
|
|
),
|
|
ASSIGNEES_BLOCK: s__('AlertManagement|Alert assignee(s): %{assignees}'),
|
|
},
|
|
components: {
|
|
GlIcon,
|
|
GlDropdown,
|
|
GlDropdownItem,
|
|
GlDropdownDivider,
|
|
GlDropdownSectionHeader,
|
|
GlSearchBoxByType,
|
|
GlLoadingIcon,
|
|
GlTooltip,
|
|
GlButton,
|
|
GlSprintf,
|
|
SidebarAssignee,
|
|
},
|
|
props: {
|
|
projectId: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
projectPath: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
alert: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
isEditable: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
sidebarCollapsed: {
|
|
type: Boolean,
|
|
required: false,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
isDropdownShowing: false,
|
|
isDropdownSearching: false,
|
|
isUpdating: false,
|
|
search: '',
|
|
users: [],
|
|
};
|
|
},
|
|
computed: {
|
|
currentUser() {
|
|
return gon?.current_username;
|
|
},
|
|
userName() {
|
|
return this.alert?.assignees?.nodes[0]?.username;
|
|
},
|
|
userFullName() {
|
|
return this.alert?.assignees?.nodes[0]?.name;
|
|
},
|
|
userImg() {
|
|
return this.alert?.assignees?.nodes[0]?.avatarUrl;
|
|
},
|
|
sortedUsers() {
|
|
return this.users
|
|
.map(user => ({ ...user, active: this.isActive(user.username) }))
|
|
.sort((a, b) => (a.active === b.active ? 0 : a.active ? -1 : 1)); // eslint-disable-line no-nested-ternary
|
|
},
|
|
dropdownClass() {
|
|
return this.isDropdownShowing ? 'show' : 'gl-display-none';
|
|
},
|
|
userListValid() {
|
|
return !this.isDropdownSearching && this.users.length > 0;
|
|
},
|
|
userListEmpty() {
|
|
return !this.isDropdownSearching && this.users.length === 0;
|
|
},
|
|
},
|
|
watch: {
|
|
search: debounce(function debouncedUserSearch() {
|
|
this.updateAssigneesDropdown();
|
|
}, DATA_REFETCH_DELAY),
|
|
},
|
|
mounted() {
|
|
this.updateAssigneesDropdown();
|
|
},
|
|
methods: {
|
|
hideDropdown() {
|
|
this.isDropdownShowing = false;
|
|
},
|
|
toggleFormDropdown() {
|
|
this.isDropdownShowing = !this.isDropdownShowing;
|
|
const { dropdown } = this.$refs.dropdown.$refs;
|
|
if (dropdown && this.isDropdownShowing) {
|
|
dropdown.show();
|
|
}
|
|
},
|
|
isActive(name) {
|
|
return this.alert.assignees.nodes.some(({ username }) => username === name);
|
|
},
|
|
buildUrl(urlRoot, url) {
|
|
let newUrl;
|
|
if (urlRoot != null) {
|
|
newUrl = urlRoot.replace(/\/$/, '') + url;
|
|
}
|
|
return newUrl;
|
|
},
|
|
updateAssigneesDropdown() {
|
|
this.isDropdownSearching = true;
|
|
return axios
|
|
.get(this.buildUrl(gon.relative_url_root, '/-/autocomplete/users.json'), {
|
|
params: {
|
|
search: this.search,
|
|
per_page: 20,
|
|
active: true,
|
|
current_user: true,
|
|
project_id: this.projectId,
|
|
},
|
|
})
|
|
.then(({ data }) => {
|
|
this.users = data;
|
|
})
|
|
.catch(() => {
|
|
this.$emit('alert-error', this.$options.i18n.FETCH_USERS_ERROR);
|
|
})
|
|
.finally(() => {
|
|
this.isDropdownSearching = false;
|
|
});
|
|
},
|
|
updateAlertAssignees(assignees) {
|
|
this.isUpdating = true;
|
|
this.$apollo
|
|
.mutate({
|
|
mutation: alertSetAssignees,
|
|
variables: {
|
|
iid: this.alert.iid,
|
|
assigneeUsernames: [this.isActive(assignees) ? '' : assignees],
|
|
projectPath: this.projectPath,
|
|
},
|
|
})
|
|
.then(({ data: { alertSetAssignees: { errors } = [] } = {} } = {}) => {
|
|
this.hideDropdown();
|
|
|
|
if (errors[0]) {
|
|
this.$emit(
|
|
'alert-error',
|
|
`${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
|
|
);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_ASSIGNEES_ERROR);
|
|
})
|
|
.finally(() => {
|
|
this.isUpdating = false;
|
|
});
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="block alert-assignees ">
|
|
<div ref="assignees" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')">
|
|
<gl-icon name="user" :size="14" />
|
|
<gl-loading-icon v-if="isUpdating" />
|
|
</div>
|
|
<gl-tooltip :target="() => $refs.assignees" boundary="viewport" placement="left">
|
|
<gl-sprintf :message="$options.i18n.ASSIGNEES_BLOCK">
|
|
<template #assignees>
|
|
{{ userName }}
|
|
</template>
|
|
</gl-sprintf>
|
|
</gl-tooltip>
|
|
|
|
<div class="hide-collapsed">
|
|
<p class="title gl-display-flex gl-justify-content-space-between">
|
|
{{ __('Assignee') }}
|
|
<a
|
|
v-if="isEditable"
|
|
ref="editButton"
|
|
class="btn-link"
|
|
href="#"
|
|
@click="toggleFormDropdown"
|
|
@keydown.esc="hideDropdown"
|
|
>
|
|
{{ __('Edit') }}
|
|
</a>
|
|
</p>
|
|
|
|
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
|
|
<gl-dropdown
|
|
ref="dropdown"
|
|
:text="userName"
|
|
class="w-100"
|
|
toggle-class="dropdown-menu-toggle"
|
|
@keydown.esc.native="hideDropdown"
|
|
@hide="hideDropdown"
|
|
>
|
|
<p class="gl-new-dropdown-header-top">
|
|
{{ __('Assign To') }}
|
|
</p>
|
|
<gl-search-box-by-type v-model.trim="search" :placeholder="__('Search users')" />
|
|
<div class="dropdown-content dropdown-body">
|
|
<template v-if="userListValid">
|
|
<gl-dropdown-item
|
|
:active="!userName"
|
|
active-class="is-active"
|
|
@click="updateAlertAssignees('')"
|
|
>
|
|
{{ __('Unassigned') }}
|
|
</gl-dropdown-item>
|
|
<gl-dropdown-divider />
|
|
|
|
<gl-dropdown-section-header>
|
|
{{ __('Assignee') }}
|
|
</gl-dropdown-section-header>
|
|
<sidebar-assignee
|
|
v-for="user in sortedUsers"
|
|
:key="user.username"
|
|
:user="user"
|
|
:active="user.active"
|
|
@update-alert-assignees="updateAlertAssignees"
|
|
/>
|
|
</template>
|
|
<p v-else-if="userListEmpty" class="mx-3 my-2">
|
|
{{ __('No Matching Results') }}
|
|
</p>
|
|
<gl-loading-icon v-else />
|
|
</div>
|
|
</gl-dropdown>
|
|
</div>
|
|
|
|
<gl-loading-icon v-if="isUpdating" :inline="true" />
|
|
<div v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }">
|
|
<div v-if="userName" class="gl-display-inline-flex gl-mt-2" data-testid="assigned-users">
|
|
<span class="gl-relative mr-2">
|
|
<img
|
|
:alt="userName"
|
|
:src="userImg"
|
|
:width="32"
|
|
class="avatar avatar-inline gl-m-0 s32"
|
|
data-qa-selector="avatar_image"
|
|
/>
|
|
</span>
|
|
<span class="gl-display-flex gl-flex-direction-column gl-overflow-hidden">
|
|
<strong class="dropdown-menu-user-full-name">
|
|
{{ userFullName }}
|
|
</strong>
|
|
<span class="dropdown-menu-user-username">{{ userName }}</span>
|
|
</span>
|
|
</div>
|
|
<span v-else class="gl-display-flex gl-align-items-center gl-line-height-normal">
|
|
{{ __('None') }} -
|
|
<gl-button
|
|
class="gl-ml-2"
|
|
href="#"
|
|
variant="link"
|
|
data-testid="unassigned-users"
|
|
@click="updateAlertAssignees(currentUser)"
|
|
>
|
|
{{ __('assign yourself') }}
|
|
</gl-button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|