diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 20:02:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 20:02:30 +0000 |
commit | 41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch) | |
tree | 9c8d89a8624828992f06d892cd2f43818ff5dcc8 /app/assets/javascripts/sidebar | |
parent | 0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff) | |
download | gitlab-ce-41fe97390ceddf945f3d967b8fdb3de4c66b7dea.tar.gz |
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/sidebar')
22 files changed, 398 insertions, 47 deletions
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue index da9ff407faf..240e12ee597 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue @@ -1,5 +1,6 @@ <script> import { GlIcon } from '@gitlab/ui'; +import { IssuableType } from '~/issues/constants'; import { __, sprintf } from '~/locale'; export default { @@ -31,10 +32,11 @@ export default { ); }, isMergeRequest() { - return this.issuableType === 'merge_request'; + return this.issuableType === IssuableType.MergeRequest; }, hasMergeIcon() { - return this.isMergeRequest && !this.user.can_merge; + const canMerge = this.user.mergeRequestInteraction?.canMerge || this.user.can_merge; + return this.isMergeRequest && !canMerge; }, }, }; diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue index 2a237e7ace0..578c344da02 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue @@ -1,5 +1,6 @@ <script> import { GlTooltipDirective, GlLink } from '@gitlab/ui'; +import { IssuableType } from '~/issues/constants'; import { __ } from '~/locale'; import { isUserBusy } from '~/set_status_modal/utils'; import AssigneeAvatar from './assignee_avatar.vue'; @@ -71,7 +72,8 @@ export default { }, computed: { cannotMerge() { - return this.issuableType === 'merge_request' && !this.user.can_merge; + const canMerge = this.user.mergeRequestInteraction?.canMerge || this.user.can_merge; + return this.issuableType === IssuableType.MergeRequest && !canMerge; }, tooltipTitle() { const { name = '', availability = '' } = this.user; diff --git a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue index 6a74ab83c22..856687c00ae 100644 --- a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue +++ b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue @@ -58,7 +58,7 @@ export default { return this.users.length > 2; }, allAssigneesCanMerge() { - return this.users.every((user) => user.can_merge); + return this.users.every((user) => user.can_merge || user.mergeRequestInteraction?.canMerge); }, sidebarAvatarCounter() { if (this.users.length > DEFAULT_MAX_COUNTER) { @@ -77,7 +77,9 @@ export default { return ''; } - const mergeLength = this.users.filter((u) => u.can_merge).length; + const mergeLength = this.users.filter( + (u) => u.can_merge || u.mergeRequestInteraction?.canMerge, + ).length; if (mergeLength === this.users.length) { return ''; diff --git a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue index a3379784bc1..59a4eb54bbe 100644 --- a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue @@ -44,7 +44,7 @@ export default { <div class="gl-display-flex gl-flex-direction-column issuable-assignees"> <div v-if="emptyUsers" - class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-2 hide-collapsed" + class="gl-display-flex gl-align-items-center gl-text-gray-500 hide-collapsed" data-testid="none" > <span> {{ __('None') }}</span> @@ -65,7 +65,7 @@ export default { v-else :users="users" :issuable-type="issuableType" - class="gl-text-gray-800 gl-mt-2 hide-collapsed" + class="gl-text-gray-800 hide-collapsed" @toggle-attention-requested="toggleAttentionRequested" /> </div> diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue index 453dd1b0580..e596d6292bf 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue @@ -63,7 +63,7 @@ export default { computed: { shouldEnableRealtime() { // Note: Realtime is only available on issues right now, future support for MR wil be built later. - return this.glFeatures.realTimeIssueSidebar && this.issuableType === 'issue'; + return this.issuableType === 'issue'; }, queryVariables() { return { diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue index 18654b73ab3..7743004a293 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue @@ -1,6 +1,5 @@ <script> import { GlDropdownItem } from '@gitlab/ui'; -import { cloneDeep } from 'lodash'; import Vue from 'vue'; import createFlash from '~/flash'; import { IssuableType } from '~/issues/constants'; @@ -101,7 +100,10 @@ export default { } const issuable = data.workspace?.issuable; if (issuable) { - this.selected = cloneDeep(issuable.assignees.nodes); + this.selected = issuable.assignees.nodes.map((node) => ({ + ...node, + canMerge: node.mergeRequestInteraction?.canMerge || false, + })); } }, error() { @@ -112,7 +114,7 @@ export default { computed: { shouldEnableRealtime() { // Note: Realtime is only available on issues right now, future support for MR wil be built later. - return this.glFeatures.realTimeIssueSidebar && this.issuableType === IssuableType.Issue; + return this.issuableType === IssuableType.Issue; }, queryVariables() { return { @@ -141,6 +143,7 @@ export default { username: gon?.current_username, name: gon?.current_user_fullname, avatarUrl: gon?.current_user_avatar_url, + canMerge: this.issuable?.userPermissions?.canMerge || false, }; }, signedIn() { @@ -206,8 +209,8 @@ export default { expandWidget() { this.$refs.toggle.expand(); }, - focusSearch() { - this.$refs.userSelect.focusSearch(); + showDropdown() { + this.$refs.userSelect.showDropdown(); }, showError() { createFlash({ message: __('An error occurred while fetching participants.') }); @@ -236,11 +239,11 @@ export default { :initial-loading="isAssigneesLoading" :title="assigneeText" :is-dirty="isDirty" - @open="focusSearch" + @open="showDropdown" @close="saveAssignees" > <template #collapsed> - <slot name="collapsed" :users="assignees" :on-click="expandWidget"></slot> + <slot name="collapsed" :users="assignees"></slot> <issuable-assignees :users="assignees" :issuable-type="issuableType" @@ -256,12 +259,13 @@ export default { :text="$options.i18n.assignees" :header-text="$options.i18n.assignTo" :iid="iid" + :issuable-id="issuableId" :full-path="fullPath" :allow-multiple-assignees="allowMultipleAssignees" :current-user="currentUser" :issuable-type="issuableType" :is-editing="edit" - class="gl-w-full dropdown-menu-user" + class="gl-w-full dropdown-menu-user gl-mt-n3" @toggle="collapseWidget" @error="showError" @input="setDirtyState" diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue index 8ef65ef7308..28bc5afc1a4 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue @@ -30,6 +30,6 @@ export default { :event="$options.dataTrackEvent" :label="$options.dataTrackLabel" :trigger-source="triggerSource" - classes="gl-display-block gl-pl-6 gl-hover-text-decoration-none gl-hover-text-blue-800!" + classes="gl-display-block gl-pl-0 gl-hover-text-decoration-none gl-hover-text-blue-800!" /> </template> diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue index e2a38a100b9..19f588b28be 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue @@ -1,17 +1,24 @@ <script> -import { GlAvatarLabeled, GlAvatarLink } from '@gitlab/ui'; +import { GlAvatarLabeled, GlAvatarLink, GlIcon } from '@gitlab/ui'; +import { IssuableType } from '~/issues/constants'; import { s__, sprintf } from '~/locale'; export default { components: { GlAvatarLabeled, GlAvatarLink, + GlIcon, }, props: { user: { type: Object, required: true, }, + issuableType: { + type: String, + required: false, + default: IssuableType.Issue, + }, }, computed: { userLabel() { @@ -22,6 +29,9 @@ export default { author: this.user.name, }); }, + hasCannotMergeIcon() { + return this.issuableType === IssuableType.MergeRequest && !this.user.canMerge; + }, }, }; </script> @@ -31,9 +41,19 @@ export default { <gl-avatar-labeled :size="32" :label="userLabel" - :sub-label="user.username" + :sub-label="`@${user.username}`" :src="user.avatarUrl || user.avatar || user.avatar_url" - class="gl-align-items-center" - /> + class="gl-align-items-center gl-relative" + > + <template #meta> + <gl-icon + v-if="hasCannotMergeIcon" + name="warning-solid" + aria-hidden="true" + class="merge-icon" + :size="12" + /> + </template> + </gl-avatar-labeled> </gl-avatar-link> </template> diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue index a27dbee31ec..558fe8ca2aa 100644 --- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue +++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue @@ -114,7 +114,7 @@ export default { class="gl-display-inline-block" > <attention-requested-toggle - v-if="showVerticalList && user.can_update_merge_request" + v-if="showVerticalList" :user="user" type="assignee" @toggle-attention-requested="toggleAttentionRequested" diff --git a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue index 42e56906e2c..6ba88939373 100644 --- a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue +++ b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue @@ -8,6 +8,8 @@ export default { attentionRequestedReviewer: __('Request attention to review'), attentionRequestedAssignee: __('Request attention'), removeAttentionRequested: __('Remove attention request'), + attentionRequestedNoPermission: __('Attention requested'), + noAttentionRequestedNoPermission: __('No attention request'), }, components: { GlButton, @@ -33,17 +35,25 @@ export default { computed: { tooltipTitle() { if (this.user.attention_requested) { - return this.$options.i18n.removeAttentionRequested; + if (this.user.can_update_merge_request) { + return this.$options.i18n.removeAttentionRequested; + } + + return this.$options.i18n.attentionRequestedNoPermission; + } + + if (this.user.can_update_merge_request) { + return this.type === 'reviewer' + ? this.$options.i18n.attentionRequestedReviewer + : this.$options.i18n.attentionRequestedAssignee; } - return this.type === 'reviewer' - ? this.$options.i18n.attentionRequestedReviewer - : this.$options.i18n.attentionRequestedAssignee; + return this.$options.i18n.noAttentionRequestedNoPermission; }, }, methods: { toggleAttentionRequired() { - if (this.loading) return; + if (this.loading || !this.user.can_update_merge_request) return; this.$root.$emit(BV_HIDE_TOOLTIP); this.loading = true; @@ -60,12 +70,16 @@ export default { </script> <template> - <span v-gl-tooltip.left.viewport="tooltipTitle"> + <span + v-gl-tooltip.left.viewport="tooltipTitle" + class="gl-display-inline-block js-attention-request-toggle" + > <gl-button :loading="loading" :variant="user.attention_requested ? 'warning' : 'default'" :icon="user.attention_requested ? 'attention-solid' : 'attention'" :aria-label="tooltipTitle" + :class="{ 'gl-pointer-events-none': !user.can_update_merge_request }" size="small" category="tertiary" @click="toggleAttentionRequired" diff --git a/app/assets/javascripts/sidebar/components/incidents/constants.js b/app/assets/javascripts/sidebar/components/incidents/constants.js new file mode 100644 index 00000000000..cd05a6099fd --- /dev/null +++ b/app/assets/javascripts/sidebar/components/incidents/constants.js @@ -0,0 +1,25 @@ +import { s__ } from '~/locale'; + +export const STATUS_TRIGGERED = 'TRIGGERED'; +export const STATUS_ACKNOWLEDGED = 'ACKNOWLEDGED'; +export const STATUS_RESOLVED = 'RESOLVED'; + +export const STATUS_TRIGGERED_LABEL = s__('IncidentManagement|Triggered'); +export const STATUS_ACKNOWLEDGED_LABEL = s__('IncidentManagement|Acknowledged'); +export const STATUS_RESOLVED_LABEL = s__('IncidentManagement|Resolved'); + +export const STATUS_LABELS = { + [STATUS_TRIGGERED]: STATUS_TRIGGERED_LABEL, + [STATUS_ACKNOWLEDGED]: STATUS_ACKNOWLEDGED_LABEL, + [STATUS_RESOLVED]: STATUS_RESOLVED_LABEL, +}; + +export const i18n = { + fetchError: s__( + 'IncidentManagement|An error occurred while fetching the incident status. Please reload the page.', + ), + title: s__('IncidentManagement|Status'), + updateError: s__( + 'IncidentManagement|An error occurred while updating the incident status. Please reload the page and try again.', + ), +}; diff --git a/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue b/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue new file mode 100644 index 00000000000..2c32cf89387 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue @@ -0,0 +1,61 @@ +<script> +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { i18n, STATUS_ACKNOWLEDGED, STATUS_TRIGGERED, STATUS_RESOLVED } from './constants'; +import { getStatusLabel } from './utils'; + +const STATUS_LIST = [STATUS_TRIGGERED, STATUS_ACKNOWLEDGED, STATUS_RESOLVED]; + +export default { + i18n, + STATUS_LIST, + components: { + GlDropdown, + GlDropdownItem, + }, + props: { + value: { + type: String, + required: false, + default: null, + validator(value) { + return [...STATUS_LIST, null].includes(value); + }, + }, + }, + computed: { + currentStatusLabel() { + return this.getStatusLabel(this.value); + }, + }, + methods: { + show() { + this.$refs.dropdown.show(); + }, + hide() { + this.$refs.dropdown.hide(); + }, + getStatusLabel, + }, +}; +</script> + +<template> + <gl-dropdown + ref="dropdown" + block + :text="currentStatusLabel" + toggle-class="dropdown-menu-toggle gl-mb-2" + > + <slot name="header"> </slot> + <gl-dropdown-item + v-for="status in $options.STATUS_LIST" + :key="status" + data-testid="status-dropdown-item" + :is-check-item="true" + :is-checked="status === value" + @click="$emit('input', status)" + > + {{ getStatusLabel(status) }} + </gl-dropdown-item> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/sidebar/components/incidents/sidebar_escalation_status.vue b/app/assets/javascripts/sidebar/components/incidents/sidebar_escalation_status.vue new file mode 100644 index 00000000000..67ae1e6fcab --- /dev/null +++ b/app/assets/javascripts/sidebar/components/incidents/sidebar_escalation_status.vue @@ -0,0 +1,135 @@ +<script> +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { escalationStatusQuery, escalationStatusMutation } from '~/sidebar/constants'; +import { createAlert } from '~/flash'; +import { logError } from '~/lib/logger'; +import EscalationStatus from 'ee_else_ce/sidebar/components/incidents/escalation_status.vue'; +import SidebarEditableItem from '../sidebar_editable_item.vue'; +import { i18n } from './constants'; +import { getStatusLabel } from './utils'; + +export default { + i18n, + components: { + EscalationStatus, + SidebarEditableItem, + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + iid: { + type: String, + required: true, + }, + projectPath: { + type: String, + required: true, + }, + issuableType: { + required: true, + type: String, + }, + }, + data() { + return { + status: null, + isUpdating: false, + }; + }, + apollo: { + status: { + query() { + return escalationStatusQuery; + }, + variables() { + return { + fullPath: this.projectPath, + iid: this.iid, + }; + }, + update(data) { + return data.workspace?.issuable?.escalationStatus; + }, + error(error) { + const message = this.$options.i18n.fetchError; + createAlert({ message }); + logError(message, error); + }, + }, + }, + computed: { + isLoading() { + return this.$apollo.queries.status.loading; + }, + currentStatusLabel() { + return getStatusLabel(this.status); + }, + tooltipText() { + return `${this.$options.i18n.title}: ${this.currentStatusLabel}`; + }, + }, + methods: { + updateStatus(status) { + this.isUpdating = true; + this.closeSidebar(); + return this.$apollo + .mutate({ + mutation: escalationStatusMutation, + variables: { + status, + iid: this.iid, + projectPath: this.projectPath, + }, + }) + .then(({ data: { issueSetEscalationStatus } }) => { + this.status = issueSetEscalationStatus.issue.escalationStatus; + }) + .catch((error) => { + const message = this.$options.i18n.updateError; + createAlert({ message }); + logError(message, error); + }) + .finally(() => { + this.isUpdating = false; + }); + }, + closeSidebar() { + this.close(); + this.$refs.editable.collapse(); + }, + open() { + this.$refs.escalationStatus.show(); + }, + close() { + this.$refs.escalationStatus.hide(); + }, + }, +}; +</script> + +<template> + <sidebar-editable-item + ref="editable" + :title="$options.i18n.title" + :initial-loading="isLoading" + :loading="isUpdating" + @open="open" + @close="close" + > + <template #default> + <escalation-status ref="escalationStatus" :value="status" @input="updateStatus" /> + </template> + <template #collapsed> + <div + v-gl-tooltip.viewport.left="tooltipText" + class="sidebar-collapsed-icon" + data-testid="status-icon" + > + <gl-icon name="status" :size="16" /> + </div> + <span class="hide-collapsed text-secondary">{{ currentStatusLabel }}</span> + </template> + </sidebar-editable-item> +</template> diff --git a/app/assets/javascripts/sidebar/components/incidents/utils.js b/app/assets/javascripts/sidebar/components/incidents/utils.js new file mode 100644 index 00000000000..59bf1ea466c --- /dev/null +++ b/app/assets/javascripts/sidebar/components/incidents/utils.js @@ -0,0 +1,5 @@ +import { s__ } from '~/locale'; + +import { STATUS_LABELS } from './constants'; + +export const getStatusLabel = (status) => STATUS_LABELS[status] ?? s__('IncidentManagement|None'); diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue index adaf1b65f3f..9485802d3da 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue @@ -98,7 +98,7 @@ export default { data-testid="reviewer" > <attention-requested-toggle - v-if="glFeatures.mrAttentionRequests && user.can_update_merge_request" + v-if="glFeatures.mrAttentionRequests" :user="user" type="reviewer" @toggle-attention-requested="toggleAttentionRequested" diff --git a/app/assets/javascripts/sidebar/components/severity/severity.vue b/app/assets/javascripts/sidebar/components/severity/severity.vue index 7e7d62256c9..0db856543d0 100644 --- a/app/assets/javascripts/sidebar/components/severity/severity.vue +++ b/app/assets/javascripts/sidebar/components/severity/severity.vue @@ -1,9 +1,11 @@ <script> import { GlIcon } from '@gitlab/ui'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; export default { components: { GlIcon, + TooltipOnTruncate, }, props: { severity: { @@ -30,13 +32,15 @@ export default { <template> <div - class="incident-severity gl-display-inline-flex gl-align-items-center gl-justify-content-between" + class="incident-severity gl-display-inline-flex gl-align-items-center gl-justify-content-between gl-max-w-full" > <gl-icon :size="iconSize" :name="`severity-${severity.icon}`" :class="[`icon-${severity.icon}`, { 'gl-mr-3': !iconOnly }]" /> - <span v-if="!iconOnly">{{ severity.label }}</span> + <tooltip-on-truncate v-if="!iconOnly" :title="severity.label" class="gl-text-truncate">{{ + severity.label + }}</tooltip-on-truncate> </div> </template> diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js index 0238fb8e8d5..989dc574bc3 100644 --- a/app/assets/javascripts/sidebar/constants.js +++ b/app/assets/javascripts/sidebar/constants.js @@ -1,7 +1,8 @@ import { s__, sprintf } from '~/locale'; import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql'; +import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql'; +import userSearchWithMRPermissionsQuery from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql'; import { IssuableType, WorkspaceType } from '~/issues/constants'; -import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql'; import epicDueDateQuery from '~/sidebar/queries/epic_due_date.query.graphql'; import epicParticipantsQuery from '~/sidebar/queries/epic_participants.query.graphql'; @@ -49,12 +50,12 @@ import getMergeRequestParticipants from '~/vue_shared/components/sidebar/queries import getMrTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql'; import updateIssueAssigneesMutation from '~/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql'; import updateMergeRequestAssigneesMutation from '~/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql'; +import getEscalationStatusQuery from '~/sidebar/queries/escalation_status.query.graphql'; +import updateEscalationStatusMutation from '~/sidebar/queries/update_escalation_status.mutation.graphql'; import projectIssueMilestoneMutation from './queries/project_issue_milestone.mutation.graphql'; import projectIssueMilestoneQuery from './queries/project_issue_milestone.query.graphql'; import projectMilestonesQuery from './queries/project_milestones.query.graphql'; -export const ASSIGNEES_DEBOUNCE_DELAY = DEFAULT_DEBOUNCE_AND_THROTTLE_MS; - export const defaultEpicSort = 'TITLE_ASC'; export const epicIidPattern = /^&(?<iid>\d+)$/; @@ -91,6 +92,15 @@ export const participantsQueries = { }, }; +export const userSearchQueries = { + [IssuableType.Issue]: { + query: userSearchQuery, + }, + [IssuableType.MergeRequest]: { + query: userSearchWithMRPermissionsQuery, + }, +}; + export const confidentialityQueries = { [IssuableType.Issue]: { query: issueConfidentialQuery, @@ -305,3 +315,6 @@ export function dropdowni18nText(issuableAttribute, issuableType) { ), }; } + +export const escalationStatusQuery = getEscalationStatusQuery; +export const escalationStatusMutation = updateEscalationStatusMutation; diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index c29784aa328..2a7d967cb61 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -10,6 +10,7 @@ import { isInIssuePage, isInDesignPage, isInIncidentPage, + isInMRPage, parseBoolean, } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; @@ -27,9 +28,11 @@ import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_v import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue'; import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; import eventHub from '~/sidebar/event_hub'; +import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import Translate from '../vue_shared/translate'; import SidebarAssignees from './components/assignees/sidebar_assignees.vue'; import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue'; +import SidebarEscalationStatus from './components/incidents/sidebar_escalation_status.vue'; import IssuableLockForm from './components/lock/issuable_lock_form.vue'; import SidebarReviewers from './components/reviewers/sidebar_reviewers.vue'; import SidebarSeverity from './components/severity/sidebar_severity.vue'; @@ -134,6 +137,8 @@ function mountAssigneesComponent() { if (!el) return; const { id, iid, fullPath, editable } = getSidebarOptions(); + const isIssuablePage = isInIssuePage() || isInIncidentPage() || isInDesignPage(); + const issuableType = isIssuablePage ? IssuableType.Issue : IssuableType.MergeRequest; // eslint-disable-next-line no-new new Vue({ el, @@ -151,21 +156,16 @@ function mountAssigneesComponent() { props: { iid: String(iid), fullPath, - issuableType: - isInIssuePage() || isInIncidentPage() || isInDesignPage() - ? IssuableType.Issue - : IssuableType.MergeRequest, + issuableType, issuableId: id, allowMultipleAssignees: !el.dataset.maxAssignees, }, scopedSlots: { - collapsed: ({ users, onClick }) => + collapsed: ({ users }) => createElement(CollapsedAssigneeList, { props: { users, - }, - nativeOn: { - click: onClick, + issuableType, }, }), }, @@ -567,6 +567,36 @@ function mountSeverityComponent() { }); } +function mountEscalationStatusComponent() { + const statusContainerEl = document.querySelector('#js-escalation-status'); + + if (!statusContainerEl) { + return false; + } + + const { issuableType } = getSidebarOptions(); + const { canUpdate, issueIid, projectPath } = statusContainerEl.dataset; + + return new Vue({ + el: statusContainerEl, + apolloProvider, + components: { + SidebarEscalationStatus, + }, + provide: { + canUpdate: parseBoolean(canUpdate), + }, + render: (createElement) => + createElement('sidebar-escalation-status', { + props: { + iid: issueIid, + issuableType, + projectPath, + }, + }), + }); +} + function mountCopyEmailComponent() { const el = document.getElementById('issuable-copy-email'); @@ -584,7 +614,7 @@ function mountCopyEmailComponent() { } const isAssigneesWidgetShown = - (isInIssuePage() || isInDesignPage()) && gon.features.issueAssigneesWidget; + (isInIssuePage() || isInDesignPage() || isInMRPage()) && gon.features.issueAssigneesWidget; export function mountSidebar(mediator, store) { initInviteMembersModal(); @@ -618,10 +648,13 @@ export function mountSidebar(mediator, store) { mountSeverityComponent(); + mountEscalationStatusComponent(); + if (window.gon?.features?.mrAttentionRequests) { - eventHub.$on('removeCurrentUserAttentionRequested', () => - mediator.removeCurrentUserAttentionRequested(), - ); + eventHub.$on('removeCurrentUserAttentionRequested', () => { + mediator.removeCurrentUserAttentionRequested(); + refreshUserMergeRequestCounts(); + }); } } diff --git a/app/assets/javascripts/sidebar/queries/escalation_status.query.graphql b/app/assets/javascripts/sidebar/queries/escalation_status.query.graphql new file mode 100644 index 00000000000..cb7c5a0fbe7 --- /dev/null +++ b/app/assets/javascripts/sidebar/queries/escalation_status.query.graphql @@ -0,0 +1,9 @@ +query escalationStatusQuery($fullPath: ID!, $iid: String) { + workspace: project(fullPath: $fullPath) { + id + issuable: issue(iid: $iid) { + id + escalationStatus + } + } +} diff --git a/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql new file mode 100644 index 00000000000..a4aff7968df --- /dev/null +++ b/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql @@ -0,0 +1,10 @@ +mutation updateEscalationStatus($projectPath: ID!, $status: IssueEscalationStatus!, $iid: String!) { + issueSetEscalationStatus(input: { projectPath: $projectPath, status: $status, iid: $iid }) { + errors + clientMutationId + issue { + id + escalationStatus + } + } +} diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js index 1be670f7590..74ab65e4e04 100644 --- a/app/assets/javascripts/sidebar/sidebar_bundle.js +++ b/app/assets/javascripts/sidebar/sidebar_bundle.js @@ -3,7 +3,17 @@ import Mediator from './sidebar_mediator'; export default (store) => { const mediator = new Mediator(getSidebarOptions()); - mediator.fetch(); + mediator + .fetch() + .then(() => { + if (window.gon?.features?.mrAttentionRequests) { + return import('~/attention_requests'); + } + + return null; + }) + .then((module) => module?.initSideNavPopover()) + .catch(() => {}); mountSidebar(mediator, store); }; diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 4664bb56958..83fb8f31dfb 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -2,6 +2,7 @@ import Store from '~/sidebar/stores/sidebar_store'; import createFlash from '~/flash'; import { __, sprintf } from '~/locale'; import toast from '~/vue_shared/plugins/global_toast'; +import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import { visitUrl } from '../lib/utils/url_utility'; import Service from './services/sidebar_service'; @@ -125,6 +126,7 @@ export default class SidebarMediator { this.store.updateReviewer(user.id, 'attention_requested'); this.store.updateAssignee(user.id, 'attention_requested'); + refreshUserMergeRequestCounts(); callback(); } catch (error) { callback(); |