diff options
Diffstat (limited to 'app/assets/javascripts/sidebar/components')
14 files changed, 436 insertions, 295 deletions
diff --git a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue index e2dc37a0ac2..b53b7039018 100644 --- a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue @@ -31,13 +31,18 @@ export default { </script> <template> - <div class="gl-display-flex gl-flex-direction-column"> - <div v-if="emptyUsers" data-testid="none"> + <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" + data-testid="none" + > <span> {{ __('None') }} -</span> <gl-button data-testid="assign-yourself" category="tertiary" variant="link" + class="gl-ml-2" @click="$emit('assign-self')" > <span class="gl-text-gray-500 gl-hover-text-blue-800">{{ __('assign yourself') }}</span> 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 8f3f77cb5f0..cc2201ad359 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue @@ -15,13 +15,12 @@ import { IssuableType } from '~/issue_show/constants'; import { __, n__ } from '~/locale'; import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; -import { assigneesQueries } from '~/sidebar/constants'; +import { assigneesQueries, ASSIGNEES_DEBOUNCE_DELAY } from '~/sidebar/constants'; import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue'; export const assigneesWidget = Vue.observable({ updateAssignees: null, }); - export default { i18n: { unassigned: __('Unassigned'), @@ -88,10 +87,10 @@ export default { return this.queryVariables; }, update(data) { - return data.issuable || data.project?.issuable; + return data.workspace?.issuable; }, result({ data }) { - const issuable = data.issuable || data.project?.issuable; + const issuable = data.workspace?.issuable; if (issuable) { this.selected = this.moveCurrentUserToStart(cloneDeep(issuable.assignees.nodes)); } @@ -104,13 +103,24 @@ export default { query: searchUsers, variables() { return { + fullPath: this.fullPath, search: this.search, }; }, update(data) { - return data.users?.nodes || []; + const searchResults = data.workspace?.users?.nodes.map(({ user }) => user) || []; + const mergedSearchResults = this.participants.reduce((acc, current) => { + if ( + !acc.some((user) => current.username === user.username) && + (current.name.includes(this.search) || current.username.includes(this.search)) + ) { + acc.push(current); + } + return acc; + }, searchResults); + return mergedSearchResults; }, - debounce: 250, + debounce: ASSIGNEES_DEBOUNCE_DELAY, skip() { return this.isSearchEmpty; }, @@ -185,7 +195,7 @@ export default { return this.selected.some(isCurrentUser) || this.participants.some(isCurrentUser); }, noUsersFound() { - return !this.isSearchEmpty && this.unselectedFiltered.length === 0; + return !this.isSearchEmpty && this.searchUsers.length === 0; }, showCurrentUser() { return !this.isCurrentUserInParticipants && (this.isSearchEmpty || this.isSearching); @@ -218,7 +228,7 @@ export default { }, }) .then(({ data }) => { - this.$emit('assignees-updated', data); + this.$emit('assignees-updated', data.issuableSetAssignees.issuable.assignees.nodes); return data; }) .catch(() => { @@ -281,6 +291,9 @@ export default { collapseWidget() { this.$refs.toggle.collapse(); }, + showDivider(list) { + return list.length > 0 && this.isSearchEmpty; + }, }, }; </script> @@ -306,6 +319,7 @@ export default { <issuable-assignees :users="assignees" :issuable-type="issuableType" + class="gl-mt-2" @assign-self="assignSelf" /> </template> @@ -334,12 +348,14 @@ export default { data-testid="unassign" @click="selectAssignee()" > - <span :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'">{{ - $options.i18n.unassigned - }}</span></gl-dropdown-item + <span + :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'" + class="gl-font-weight-bold" + >{{ $options.i18n.unassigned }}</span + ></gl-dropdown-item > - <gl-dropdown-divider data-testid="unassign-divider" /> </template> + <gl-dropdown-divider v-if="showDivider(selectedFiltered)" /> <gl-dropdown-item v-for="item in selectedFiltered" :key="item.id" @@ -358,10 +374,10 @@ export default { /> </gl-avatar-link> </gl-dropdown-item> - <gl-dropdown-divider v-if="!selectedIsEmpty" data-testid="selected-user-divider" /> <template v-if="showCurrentUser"> + <gl-dropdown-divider /> <gl-dropdown-item - data-testid="unselected-participant" + data-testid="current-user" @click.stop="selectAssignee(currentUser)" > <gl-avatar-link> @@ -370,12 +386,12 @@ export default { :label="currentUser.name" :sub-label="currentUser.username" :src="currentUser.avatarUrl" - class="gl-align-items-center" + class="gl-align-items-center gl-pl-6!" /> </gl-avatar-link> </gl-dropdown-item> - <gl-dropdown-divider /> </template> + <gl-dropdown-divider v-if="showDivider(unselectedFiltered)" /> <gl-dropdown-item v-for="unselectedUser in unselectedFiltered" :key="unselectedUser.id" @@ -392,7 +408,7 @@ export default { /> </gl-avatar-link> </gl-dropdown-item> - <gl-dropdown-item v-if="noUsersFound && !isSearching"> + <gl-dropdown-item v-if="noUsersFound && !isSearching" data-testid="empty-results"> {{ __('No matching results') }} </gl-dropdown-item> </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 36775648809..d0da4a9c75a 100644 --- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue +++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue @@ -83,7 +83,7 @@ export default { <assignee-avatar-link :user="user" :issuable-type="issuableType" /> </div> </div> - <div v-if="renderShowMoreSection" class="user-list-more"> + <div v-if="renderShowMoreSection" class="user-list-more gl-hover-text-blue-800"> <button type="button" class="btn-link" diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue deleted file mode 100644 index 57b3705e803..00000000000 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ /dev/null @@ -1,113 +0,0 @@ -<script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { mapState } from 'vuex'; -import { __, sprintf } from '~/locale'; -import eventHub from '~/sidebar/event_hub'; -import EditForm from './edit_form.vue'; - -export default { - components: { - EditForm, - GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - fullPath: { - required: true, - type: String, - }, - isEditable: { - required: true, - type: Boolean, - }, - issuableType: { - required: false, - type: String, - default: 'issue', - }, - }, - data() { - return { - edit: false, - }; - }, - computed: { - ...mapState({ - confidential: ({ noteableData, confidential }) => { - if (noteableData) { - return noteableData.confidential; - } - return Boolean(confidential); - }, - }), - confidentialityIcon() { - return this.confidential ? 'eye-slash' : 'eye'; - }, - tooltipLabel() { - return this.confidential ? __('Confidential') : __('Not confidential'); - }, - confidentialText() { - return sprintf(__('This %{issuableType} is confidential'), { - issuableType: this.issuableType, - }); - }, - }, - created() { - eventHub.$on('closeConfidentialityForm', this.toggleForm); - }, - beforeDestroy() { - eventHub.$off('closeConfidentialityForm', this.toggleForm); - }, - methods: { - toggleForm() { - this.edit = !this.edit; - }, - }, -}; -</script> - -<template> - <div class="block issuable-sidebar-item confidentiality"> - <div - ref="collapseIcon" - v-gl-tooltip.viewport.left - :title="tooltipLabel" - class="sidebar-collapsed-icon" - @click="toggleForm" - > - <gl-icon :name="confidentialityIcon" /> - </div> - <div class="title hide-collapsed"> - {{ __('Confidentiality') }} - <a - v-if="isEditable" - ref="editLink" - class="float-right confidential-edit" - href="#" - data-track-event="click_edit_button" - data-track-label="right_sidebar" - data-track-property="confidentiality" - @click.prevent="toggleForm" - >{{ __('Edit') }}</a - > - </div> - <div class="value sidebar-item-value hide-collapsed"> - <edit-form - v-if="edit" - :confidential="confidential" - :full-path="fullPath" - :issuable-type="issuableType" - /> - <div v-if="!confidential" class="no-value sidebar-item-value" data-testid="not-confidential"> - <gl-icon :size="16" name="eye" class="sidebar-item-icon inline" /> - {{ __('Not confidential') }} - </div> - <div v-else class="value sidebar-item-value hide-collapsed"> - <gl-icon :size="16" name="eye-slash" class="sidebar-item-icon inline is-active" /> - {{ confidentialText }} - </div> - </div> - </div> -</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue deleted file mode 100644 index 057224d5918..00000000000 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue +++ /dev/null @@ -1,64 +0,0 @@ -<script> -import { GlSprintf } from '@gitlab/ui'; -import { __ } from '../../../locale'; -import editFormButtons from './edit_form_buttons.vue'; - -export default { - components: { - editFormButtons, - GlSprintf, - }, - props: { - confidential: { - required: true, - type: Boolean, - }, - fullPath: { - required: true, - type: String, - }, - issuableType: { - required: true, - type: String, - }, - }, - computed: { - confidentialityOnWarning() { - return __( - 'You are going to turn on the confidentiality. This means that only team members with %{strongStart}at least Reporter access%{strongEnd} are able to see and leave comments on the %{issuableType}.', - ); - }, - confidentialityOffWarning() { - return __( - 'You are going to turn off the confidentiality. This means %{strongStart}everyone%{strongEnd} will be able to see and leave a comment on this %{issuableType}.', - ); - }, - }, -}; -</script> - -<template> - <div class="dropdown show"> - <div class="dropdown-menu sidebar-item-warning-message"> - <div> - <p v-if="!confidential"> - <gl-sprintf :message="confidentialityOnWarning"> - <template #strong="{ content }"> - <strong>{{ content }}</strong> - </template> - <template #issuableType>{{ issuableType }}</template> - </gl-sprintf> - </p> - <p v-else> - <gl-sprintf :message="confidentialityOffWarning"> - <template #strong="{ content }"> - <strong>{{ content }}</strong> - </template> - <template #issuableType>{{ issuableType }}</template> - </gl-sprintf> - </p> - <edit-form-buttons :full-path="fullPath" :confidential="confidential" /> - </div> - </div> - </div> -</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue deleted file mode 100644 index 154a228c978..00000000000 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue +++ /dev/null @@ -1,81 +0,0 @@ -<script> -import { GlButton } from '@gitlab/ui'; -import $ from 'jquery'; -import { mapActions } from 'vuex'; -import { deprecatedCreateFlash as Flash } from '~/flash'; -import { __ } from '~/locale'; -import eventHub from '../../event_hub'; - -export default { - components: { - GlButton, - }, - props: { - fullPath: { - required: true, - type: String, - }, - confidential: { - required: true, - type: Boolean, - }, - }, - data() { - return { - isLoading: false, - }; - }, - computed: { - toggleButtonText() { - if (this.isLoading) { - return __('Applying'); - } - - return this.confidential ? __('Turn Off') : __('Turn On'); - }, - }, - methods: { - ...mapActions(['updateConfidentialityOnIssuable']), - closeForm() { - eventHub.$emit('closeConfidentialityForm'); - $(this.$el).trigger('hidden.gl.dropdown'); - }, - submitForm() { - this.isLoading = true; - const confidential = !this.confidential; - - this.updateConfidentialityOnIssuable({ confidential, fullPath: this.fullPath }) - .then(() => { - eventHub.$emit('updateIssuableConfidentiality', confidential); - }) - .catch((err) => { - Flash( - err || __('Something went wrong trying to change the confidentiality of this issue'), - ); - }) - .finally(() => { - this.closeForm(); - this.isLoading = false; - }); - }, - }, -}; -</script> - -<template> - <div class="sidebar-item-warning-message-actions"> - <gl-button class="gl-mr-3" @click="closeForm"> - {{ __('Cancel') }} - </gl-button> - <gl-button - category="secondary" - variant="warning" - :disabled="isLoading" - :loading="isLoading" - data-testid="confidential-toggle" - @click.prevent="submitForm" - > - {{ toggleButtonText }} - </gl-button> - </div> -</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql b/app/assets/javascripts/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql deleted file mode 100644 index 5caf5f6b555..00000000000 --- a/app/assets/javascripts/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql +++ /dev/null @@ -1,8 +0,0 @@ -mutation updateIssueConfidential($input: IssueSetConfidentialInput!) { - issueSetConfidential(input: $input) { - issue { - confidential - } - errors - } -} diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue new file mode 100644 index 00000000000..37a44eb8f01 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue @@ -0,0 +1,64 @@ +<script> +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { __, sprintf } from '~/locale'; + +export default { + components: { + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + confidential: { + type: Boolean, + required: true, + }, + issuableType: { + type: String, + required: true, + }, + }, + computed: { + confidentialText() { + return this.confidential + ? sprintf(__('This %{issuableType} is confidential'), { + issuableType: this.issuableType, + }) + : __('Not confidential'); + }, + confidentialIcon() { + return this.confidential ? 'eye-slash' : 'eye'; + }, + tooltipLabel() { + return this.confidential ? __('Confidential') : __('Not confidential'); + }, + }, +}; +</script> + +<template> + <div> + <div + v-gl-tooltip.viewport.left + :title="tooltipLabel" + class="sidebar-collapsed-icon" + data-testid="sidebar-collapsed-icon" + @click="$emit('expandSidebar')" + > + <gl-icon + :size="16" + :name="confidentialIcon" + class="sidebar-item-icon inline" + :class="{ 'is-active': confidential }" + /> + </div> + <gl-icon + :size="16" + :name="confidentialIcon" + class="sidebar-item-icon inline hide-collapsed" + :class="{ 'is-active': confidential }" + /> + <span class="hide-collapsed" data-testid="confidential-text">{{ confidentialText }}</span> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue new file mode 100644 index 00000000000..a21ac73f131 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue @@ -0,0 +1,136 @@ +<script> +import { GlSprintf, GlButton } from '@gitlab/ui'; +import createFlash from '~/flash'; +import { IssuableType } from '~/issue_show/constants'; +import { __, sprintf } from '~/locale'; +import { confidentialityQueries } from '~/sidebar/constants'; + +export default { + i18n: { + confidentialityOnWarning: __( + 'You are going to turn on confidentiality. Only team members with %{strongStart}at least Reporter access%{strongEnd} will be able to see and leave comments on the %{issuableType}.', + ), + confidentialityOffWarning: __( + 'You are going to turn off the confidentiality. This means %{strongStart}everyone%{strongEnd} will be able to see and leave a comment on this %{issuableType}.', + ), + }, + components: { + GlSprintf, + GlButton, + }, + inject: ['fullPath', 'iid'], + props: { + confidential: { + required: true, + type: Boolean, + }, + issuableType: { + required: true, + type: String, + }, + }, + data() { + return { + loading: false, + }; + }, + computed: { + toggleButtonText() { + if (this.loading) { + return __('Applying'); + } + return this.confidential ? __('Turn off') : __('Turn on'); + }, + warningMessage() { + return this.confidential + ? this.$options.i18n.confidentialityOffWarning + : this.$options.i18n.confidentialityOnWarning; + }, + workspacePath() { + return this.issuableType === IssuableType.Issue + ? { + projectPath: this.fullPath, + } + : { + groupPath: this.fullPath, + }; + }, + }, + methods: { + submitForm() { + this.loading = true; + this.$apollo + .mutate({ + mutation: confidentialityQueries[this.issuableType].mutation, + variables: { + input: { + ...this.workspacePath, + iid: this.iid, + confidential: !this.confidential, + }, + }, + }) + .then( + ({ + data: { + issuableSetConfidential: { errors }, + }, + }) => { + if (errors.length) { + createFlash({ + message: errors[0], + }); + } else { + this.$emit('closeForm'); + } + }, + ) + .catch(() => { + createFlash({ + message: sprintf( + __('Something went wrong while setting %{issuableType} confidentiality.'), + { + issuableType: this.issuableType, + }, + ), + }); + }) + .finally(() => { + this.loading = false; + }); + }, + }, +}; +</script> + +<template> + <div class="dropdown show"> + <div class="dropdown-menu sidebar-item-warning-message"> + <div> + <p data-testid="warning-message"> + <gl-sprintf :message="warningMessage"> + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + <template #issuableType>{{ issuableType }}</template> + </gl-sprintf> + </p> + <div class="sidebar-item-warning-message-actions"> + <gl-button class="gl-mr-3" data-testid="confidential-cancel" @click="$emit('closeForm')"> + {{ __('Cancel') }} + </gl-button> + <gl-button + category="secondary" + variant="warning" + :disabled="loading" + :loading="loading" + data-testid="confidential-toggle" + @click.prevent="submitForm" + > + {{ toggleButtonText }} + </gl-button> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue new file mode 100644 index 00000000000..1db68d3d5b1 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_widget.vue @@ -0,0 +1,143 @@ +<script> +import produce from 'immer'; +import Vue from 'vue'; +import createFlash from '~/flash'; +import { __, sprintf } from '~/locale'; +import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; +import { confidentialityQueries } from '~/sidebar/constants'; +import SidebarConfidentialityContent from './sidebar_confidentiality_content.vue'; +import SidebarConfidentialityForm from './sidebar_confidentiality_form.vue'; + +export const confidentialWidget = Vue.observable({ + setConfidentiality: null, +}); + +const hideDropdownEvent = new CustomEvent('hiddenGlDropdown', { + bubbles: true, +}); + +export default { + tracking: { + event: 'click_edit_button', + label: 'right_sidebar', + property: 'confidentiality', + }, + components: { + SidebarEditableItem, + SidebarConfidentialityContent, + SidebarConfidentialityForm, + }, + inject: ['fullPath', 'iid'], + props: { + issuableType: { + required: true, + type: String, + }, + }, + data() { + return { + confidential: false, + }; + }, + apollo: { + confidential: { + query() { + return confidentialityQueries[this.issuableType].query; + }, + variables() { + return { + fullPath: this.fullPath, + iid: String(this.iid), + }; + }, + update(data) { + return data.workspace?.issuable?.confidential || false; + }, + result({ data }) { + this.$emit('confidentialityUpdated', data.workspace?.issuable?.confidential); + }, + error() { + createFlash({ + message: sprintf( + __('Something went wrong while setting %{issuableType} confidentiality.'), + { + issuableType: this.issuableType, + }, + ), + }); + }, + }, + }, + computed: { + isLoading() { + return this.$apollo.queries.confidential.loading; + }, + }, + mounted() { + confidentialWidget.setConfidentiality = this.setConfidentiality; + }, + destroyed() { + confidentialWidget.setConfidentiality = null; + }, + methods: { + closeForm() { + this.$refs.editable.collapse(); + this.$el.dispatchEvent(hideDropdownEvent); + this.$emit('closeForm'); + }, + // synchronizing the quick action with the sidebar widget + // this is a temporary solution until we have confidentiality real-time updates + setConfidentiality() { + const { defaultClient: client } = this.$apollo.provider.clients; + const sourceData = client.readQuery({ + query: confidentialityQueries[this.issuableType].query, + variables: { fullPath: this.fullPath, iid: this.iid }, + }); + + const data = produce(sourceData, (draftData) => { + // eslint-disable-next-line no-param-reassign + draftData.workspace.issuable.confidential = !this.confidential; + }); + + client.writeQuery({ + query: confidentialityQueries[this.issuableType].query, + variables: { fullPath: this.fullPath, iid: this.iid }, + data, + }); + }, + expandSidebar() { + this.$refs.editable.expand(); + this.$emit('expandSidebar'); + }, + }, +}; +</script> + +<template> + <sidebar-editable-item + ref="editable" + :title="__('Confidentiality')" + :tracking="$options.tracking" + :loading="isLoading" + class="block confidentiality" + > + <template #collapsed> + <div> + <sidebar-confidentiality-content + v-if="!isLoading" + :confidential="confidential" + :issuable-type="issuableType" + @expandSidebar="expandSidebar" + /> + </div> + </template> + <template #default> + <sidebar-confidentiality-content :confidential="confidential" :issuable-type="issuableType" /> + <sidebar-confidentiality-form + :confidential="confidential" + :issuable-type="issuableType" + @closeForm="closeForm" + /> + </template> + </sidebar-editable-item> +</template> 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 cbd68f2513a..dd1d54d67f2 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue @@ -1,5 +1,6 @@ <script> import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; import ReviewerAvatarLink from './reviewer_avatar_link.vue'; const LOADING_STATE = 'loading'; @@ -50,6 +51,9 @@ export default { }, }, methods: { + approvedByTooltipTitle(user) { + return sprintf(s__('MergeRequest|Approved by @%{username}'), user); + }, toggleShowLess() { this.showLess = !this.showLess; }, @@ -57,6 +61,7 @@ export default { this.loadingStates[userId] = LOADING_STATE; this.$emit('request-review', { userId, callback: this.requestReviewComplete }); }, + requestReviewComplete(userId, success) { if (success) { this.loadingStates[userId] = SUCCESS_STATE; @@ -86,10 +91,19 @@ export default { <div class="gl-ml-3">@{{ user.username }}</div> </reviewer-avatar-link> <gl-icon + v-if="user.approved" + v-gl-tooltip.left + :size="16" + :title="approvedByTooltipTitle(user)" + name="status-success" + class="float-right gl-my-2 gl-ml-2 gl-text-green-500" + data-testid="re-approved" + /> + <gl-icon v-if="loadingStates[user.id] === $options.SUCCESS_STATE" :size="24" name="check" - class="float-right gl-text-green-500" + class="float-right gl-py-2 gl-mr-2 gl-text-green-500" data-testid="re-request-success" /> <gl-button diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue index 9da839cd133..4ab4606ac1c 100644 --- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue +++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue @@ -3,7 +3,12 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui'; export default { components: { GlButton, GlLoadingIcon }, - inject: ['canUpdate'], + inject: { + canUpdate: {}, + isClassicSidebar: { + default: false, + }, + }, props: { title: { type: String, @@ -15,6 +20,15 @@ export default { required: false, default: false, }, + tracking: { + type: Object, + required: false, + default: () => ({ + event: null, + label: null, + property: null, + }), + }, }, data() { return { @@ -71,24 +85,33 @@ export default { <template> <div> - <div class="gl-display-flex gl-align-items-center gl-mb-3" @click.self="collapse"> - <span data-testid="title">{{ title }}</span> - <gl-loading-icon v-if="loading" inline class="gl-ml-2" /> + <div class="gl-display-flex gl-align-items-center" @click.self="collapse"> + <span class="hide-collapsed" data-testid="title">{{ title }}</span> + <gl-loading-icon v-if="loading" inline class="gl-ml-2 hide-collapsed" /> + <gl-loading-icon + v-if="loading && isClassicSidebar" + inline + class="gl-mx-auto gl-my-0 hide-expanded" + /> <gl-button v-if="canUpdate" variant="link" - class="gl-text-gray-900! gl-hover-text-blue-800! gl-ml-auto js-sidebar-dropdown-toggle" + class="gl-text-gray-900! gl-hover-text-blue-800! gl-ml-auto hide-collapsed" data-testid="edit-button" + :data-track-event="tracking.event" + :data-track-label="tracking.label" + :data-track-property="tracking.property" + data-qa-selector="edit_link" @keyup.esc="toggle" @click="toggle" > {{ __('Edit') }} </gl-button> </div> - <div v-show="!edit" class="gl-text-gray-500" data-testid="collapsed-content"> + <div v-show="!edit" data-testid="collapsed-content"> <slot name="collapsed">{{ __('None') }}</slot> </div> - <div v-show="edit" data-testid="expanded-content"> + <div v-show="edit" data-testid="expanded-content" :class="{ 'gl-mt-3': !isClassicSidebar }"> <slot :edit="edit"></slot> </div> </div> diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue index 9b06c20a6f3..c0424dc2873 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue @@ -122,6 +122,8 @@ export default { :value="subscribed" class="hide-collapsed" data-testid="subscription-toggle" + :label="__('Notifications')" + label-position="hidden" @change="toggleSubscription" /> </div> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue index e0f60b9af08..d1a5685fdd3 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue @@ -1,10 +1,14 @@ <script> /* eslint-disable vue/no-v-html */ +import { GlButton } from '@gitlab/ui'; import { joinPaths } from '~/lib/utils/url_utility'; import { sprintf, s__ } from '../../../locale'; export default { name: 'TimeTrackingHelpState', + components: { + GlButton, + }, computed: { href() { return joinPaths(gon.relative_url_root || '', '/help/user/project/time_tracking.md'); @@ -40,7 +44,7 @@ export default { <p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p> <p v-html="estimateText"></p> <p v-html="spendText"></p> - <a :href="href" class="btn btn-default learn-more-button"> {{ __('Learn more') }} </a> + <gl-button :href="href">{{ __('Learn more') }}</gl-button> </div> </div> </template> |