diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-20 14:43:17 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-20 14:43:17 +0300 |
commit | dfc94207fec2d84314b1a5410cface22e8b369bd (patch) | |
tree | c54022f61ced104305889a64de080998a0dc773b /app/assets/javascripts/admin | |
parent | b874efeff674f6bf0355d5d242ecf81c6f7155df (diff) |
Add latest changes from gitlab-org/gitlab@15-11-stable-eev15.11.0-rc42
Diffstat (limited to 'app/assets/javascripts/admin')
21 files changed, 441 insertions, 177 deletions
diff --git a/app/assets/javascripts/admin/abuse_reports/components/abuse_report_actions.vue b/app/assets/javascripts/admin/abuse_reports/components/abuse_report_actions.vue new file mode 100644 index 00000000000..f2271f8af24 --- /dev/null +++ b/app/assets/javascripts/admin/abuse_reports/components/abuse_report_actions.vue @@ -0,0 +1,148 @@ +<script> +import { GlButton, GlDropdown, GlModal } from '@gitlab/ui'; +import axios from '~/lib/utils/axios_utils'; +import { __, sprintf } from '~/locale'; +import { createAlert, VARIANT_SUCCESS } from '~/alert'; +import { ACTIONS_I18N } from '../constants'; + +const modalActionButtonAttributes = { + block: { + text: __('OK'), + attributes: { + variant: 'confirm', + }, + }, + removeUserAndReport: { + text: __('OK'), + attributes: { + variant: 'danger', + }, + }, + secondary: { + text: __('Cancel'), + attributes: { + variant: 'default', + }, + }, +}; + +export default { + name: 'AbuseReportActions', + components: { + GlButton, + GlDropdown, + GlModal, + }, + modalId: 'abuse-report-row-action-confirm-modal', + modalActionButtonAttributes, + i18n: ACTIONS_I18N, + props: { + report: { + type: Object, + required: true, + }, + }, + data() { + return { + userBlocked: this.report.userBlocked, + confirmModalShown: false, + actionToConfirm: 'block', + }; + }, + computed: { + blockUserButtonText() { + const { alreadyBlocked, blockUser } = this.$options.i18n; + + return this.userBlocked ? alreadyBlocked : blockUser; + }, + removeUserAndReportConfirmText() { + return sprintf(this.$options.i18n.removeUserAndReportConfirm, { + user: this.report.reportedUser.name, + }); + }, + modalData() { + return { + block: { + action: this.blockUser, + confirmText: this.$options.i18n.blockUserConfirm, + }, + removeUserAndReport: { + action: this.removeUserAndReport, + confirmText: this.removeUserAndReportConfirmText, + }, + }; + }, + }, + methods: { + showConfirmModal(action) { + this.confirmModalShown = true; + this.actionToConfirm = action; + }, + blockUser() { + axios + .put(this.report.blockUserPath) + .then(this.handleBlockUserResponse) + .catch(this.handleError); + }, + removeUserAndReport() { + axios + .delete(this.report.removeUserAndReportPath) + .then(this.handleRemoveReportResponse) + .catch(this.handleError); + }, + removeReport() { + axios + .delete(this.report.removeReportPath) + .then(this.handleRemoveReportResponse) + .catch(this.handleError); + }, + handleRemoveReportResponse() { + window.location.reload(); + }, + handleBlockUserResponse({ data }) { + const message = data?.error || data?.notice; + const alertOptions = data?.notice ? { variant: VARIANT_SUCCESS } : {}; + + if (message) { + createAlert({ message, ...alertOptions }); + } + + if (!data?.error) { + this.userBlocked = true; + } + }, + handleError(error) { + createAlert({ + message: __('Something went wrong. Please try again.'), + captureError: true, + error, + }); + }, + }, +}; +</script> + +<template> + <gl-dropdown text="Actions" text-sr-only icon="ellipsis_v" category="tertiary" no-caret right> + <div class="gl-px-2"> + <gl-button block variant="danger" @click="showConfirmModal('removeUserAndReport')"> + {{ $options.i18n.removeUserAndReport }} + </gl-button> + <gl-button block :disabled="userBlocked" @click="showConfirmModal('block')"> + {{ blockUserButtonText }} + </gl-button> + <gl-button block @click="removeReport"> + {{ $options.i18n.removeReport }} + </gl-button> + </div> + <gl-modal + v-model="confirmModalShown" + :modal-id="$options.modalId" + :title="modalData[actionToConfirm].confirmText" + size="sm" + :action-primary="$options.modalActionButtonAttributes[actionToConfirm]" + :action-secondary="$options.modalActionButtonAttributes.secondary" + @primary="modalData[actionToConfirm].action" + /> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/admin/abuse_reports/components/abuse_report_details.vue b/app/assets/javascripts/admin/abuse_reports/components/abuse_report_details.vue new file mode 100644 index 00000000000..f49411604f1 --- /dev/null +++ b/app/assets/javascripts/admin/abuse_reports/components/abuse_report_details.vue @@ -0,0 +1,66 @@ +<script> +import { uniqueId } from 'lodash'; +import { GlButton, GlCollapse } from '@gitlab/ui'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import { __, sprintf } from '~/locale'; +import SafeHtml from '~/vue_shared/directives/safe_html'; + +export default { + components: { + GlButton, + GlCollapse, + }, + directives: { SafeHtml }, + props: { + report: { + type: Object, + required: true, + }, + }, + data() { + return { + isVisible: false, + collapseId: uniqueId('abuse-report-detail-'), + }; + }, + computed: { + toggleText() { + return this.isVisible ? __('Hide details') : __('Show details'); + }, + reportedUserCreatedAt() { + const { reportedUser } = this.report; + return sprintf(__('User joined %{timeAgo}'), { + timeAgo: getTimeago().format(reportedUser.createdAt), + }); + }, + }, + methods: { + toggleCollapse() { + this.isVisible = !this.isVisible; + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-flex-direction-column"> + <gl-collapse :id="collapseId" v-model="isVisible"> + <dl class="gl-mb-2"> + <dd>{{ reportedUserCreatedAt }}</dd> + + <dt>{{ __('Message') }}</dt> + <dd v-safe-html="report.message"></dd> + </dl> + </gl-collapse> + <div> + <gl-button + :aria-expanded="`${isVisible}`" + :aria-controls="collapseId" + size="small" + variant="link" + @click="toggleCollapse" + >{{ toggleText }} + </gl-button> + </div> + </div> +</template> diff --git a/app/assets/javascripts/admin/abuse_reports/components/abuse_report_row.vue b/app/assets/javascripts/admin/abuse_reports/components/abuse_report_row.vue index a4211002f71..a9fe59a7b85 100644 --- a/app/assets/javascripts/admin/abuse_reports/components/abuse_report_row.vue +++ b/app/assets/javascripts/admin/abuse_reports/components/abuse_report_row.vue @@ -1,11 +1,20 @@ <script> +import { GlSprintf, GlLink } from '@gitlab/ui'; import { getTimeago } from '~/lib/utils/datetime_utility'; +import { queryToObject } from '~/lib/utils/url_utility'; import { __, sprintf } from '~/locale'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; +import { SORT_UPDATED_AT } from '../constants'; +import AbuseReportActions from './abuse_report_actions.vue'; +import AbuseReportDetails from './abuse_report_details.vue'; export default { name: 'AbuseReportRow', components: { + AbuseReportDetails, + GlLink, + GlSprintf, + AbuseReportActions, ListItem, }, props: { @@ -15,14 +24,31 @@ export default { }, }, computed: { - updatedAt() { - const template = __('Updated %{timeAgo}'); - return sprintf(template, { timeAgo: getTimeago().format(this.report.updatedAt) }); + displayDate() { + const { sort } = queryToObject(window.location.search); + const { createdAt, updatedAt } = this.report; + const { template, timeAgo } = Object.values(SORT_UPDATED_AT.sortDirection).includes(sort) + ? { template: __('Updated %{timeAgo}'), timeAgo: updatedAt } + : { template: __('Created %{timeAgo}'), timeAgo: createdAt }; + + return sprintf(template, { timeAgo: getTimeago().format(timeAgo) }); + }, + reported() { + const { reportedUser } = this.report; + return sprintf('%{userLinkStart}%{reported}%{userLinkEnd}', { + reported: reportedUser.name, + }); + }, + reporter() { + const { reporter } = this.report; + return sprintf('%{reporterLinkStart}%{reporter}%{reporterLinkEnd}', { + reporter: reporter.name, + }); }, title() { - const { reportedUser, reporter, category } = this.report; + const { category } = this.report; const template = __('%{reported} reported for %{category} by %{reporter}'); - return sprintf(template, { reported: reportedUser.name, reporter: reporter.name, category }); + return sprintf(template, { reported: this.reported, reporter: this.reporter, category }); }, }, }; @@ -31,11 +57,25 @@ export default { <template> <list-item data-testid="abuse-report-row"> <template #left-primary> - <div class="gl-font-weight-normal" data-testid="title">{{ title }}</div> + <div class="gl-font-weight-normal gl-mb-2" data-testid="title"> + <gl-sprintf :message="title"> + <template #userLink="{ content }"> + <gl-link :href="report.reportedUserPath">{{ content }}</gl-link> + </template> + <template #reporterLink="{ content }"> + <gl-link :href="report.reporterPath">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> + </template> + + <template #left-secondary> + <abuse-report-details :report="report" /> </template> <template #right-secondary> - <div data-testid="updated-at">{{ updatedAt }}</div> + <div data-testid="abuse-report-date">{{ displayDate }}</div> + <abuse-report-actions :report="report" /> </template> </list-item> </template> diff --git a/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue b/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue index b60fe3ae9b8..e1989cadd86 100644 --- a/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue +++ b/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue @@ -8,7 +8,7 @@ import { SORT_OPTIONS, isValidSortKey, } from '~/admin/abuse_reports/constants'; -import { buildFilteredSearchCategoryToken } from '~/admin/abuse_reports/utils'; +import { buildFilteredSearchCategoryToken, isValidStatus } from '~/admin/abuse_reports/utils'; export default { name: 'AbuseReportsFilteredSearchBar', @@ -32,7 +32,7 @@ export default { // Backend shows open reports by default if status param is not specified. // To match that behavior, update the current URL to include status=open // query when no status query is specified on load. - if (!query.status) { + if (!isValidStatus(query.status)) { query.status = 'open'; updateHistory({ url: setUrlParams(query), replace: true }); } diff --git a/app/assets/javascripts/admin/abuse_reports/constants.js b/app/assets/javascripts/admin/abuse_reports/constants.js index ee2e9ab2cbf..ee002f269ac 100644 --- a/app/assets/javascripts/admin/abuse_reports/constants.js +++ b/app/assets/javascripts/admin/abuse_reports/constants.js @@ -40,25 +40,24 @@ export const FILTERED_SEARCH_TOKEN_STATUS = { }; export const DEFAULT_SORT = 'created_at_desc'; - -export const SORT_OPTIONS = [ - { - id: 10, - title: __('Created date'), - sortDirection: { - descending: DEFAULT_SORT, - ascending: 'created_at_asc', - }, +export const SORT_UPDATED_AT = Object.freeze({ + id: 20, + title: __('Updated date'), + sortDirection: { + descending: 'updated_at_desc', + ascending: 'updated_at_asc', }, - { - id: 20, - title: __('Updated date'), - sortDirection: { - descending: 'updated_at_desc', - ascending: 'updated_at_asc', - }, +}); +const SORT_CREATED_AT = Object.freeze({ + id: 10, + title: __('Created date'), + sortDirection: { + descending: DEFAULT_SORT, + ascending: 'created_at_asc', }, -]; +}); + +export const SORT_OPTIONS = [SORT_CREATED_AT, SORT_UPDATED_AT]; export const isValidSortKey = (key) => SORT_OPTIONS.some( @@ -79,3 +78,12 @@ export const FILTERED_SEARCH_TOKENS = [ FILTERED_SEARCH_TOKEN_REPORTER, FILTERED_SEARCH_TOKEN_STATUS, ]; + +export const ACTIONS_I18N = { + blockUserConfirm: __('USER WILL BE BLOCKED! Are you sure?'), + blockUser: __('Block user'), + alreadyBlocked: __('Already blocked'), + removeUserAndReportConfirm: __('USER %{user} WILL BE REMOVED! Are you sure?'), + removeUserAndReport: __('Remove user & report'), + removeReport: __('Remove report'), +}; diff --git a/app/assets/javascripts/admin/abuse_reports/utils.js b/app/assets/javascripts/admin/abuse_reports/utils.js index 84221901089..d30e8fb0ae5 100644 --- a/app/assets/javascripts/admin/abuse_reports/utils.js +++ b/app/assets/javascripts/admin/abuse_reports/utils.js @@ -1,6 +1,9 @@ -import { FILTERED_SEARCH_TOKEN_CATEGORY } from './constants'; +import { FILTERED_SEARCH_TOKEN_CATEGORY, FILTERED_SEARCH_TOKEN_STATUS } from './constants'; export const buildFilteredSearchCategoryToken = (categories) => { const options = categories.map((c) => ({ value: c, title: c })); return { ...FILTERED_SEARCH_TOKEN_CATEGORY, options }; }; + +export const isValidStatus = (status) => + FILTERED_SEARCH_TOKEN_STATUS.options.map((o) => o.value).includes(status); diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue index 4482198675d..3168d693234 100644 --- a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue +++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue @@ -3,6 +3,7 @@ import { GlButton, GlBroadcastMessage, GlForm, + GlFormGroup, GlFormCheckbox, GlFormCheckboxGroup, GlFormInput, @@ -18,7 +19,6 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { THEMES, TYPES, TYPE_BANNER } from '../constants'; -import MessageFormGroup from './message_form_group.vue'; import DatetimePicker from './datetime_picker.vue'; const FORM_HEADERS = { headers: { 'Content-Type': 'application/json; charset=utf-8' } }; @@ -31,13 +31,13 @@ export default { GlButton, GlBroadcastMessage, GlForm, + GlFormGroup, GlFormCheckbox, GlFormCheckboxGroup, GlFormInput, GlFormSelect, GlFormText, GlFormTextarea, - MessageFormGroup, }, directives: { SafeHtml, @@ -189,7 +189,7 @@ export default { <div v-safe-html:[$options.safeHtmlConfig]="messagePreview"></div> </gl-broadcast-message> - <message-form-group :label="$options.i18n.message" label-for="message-textarea"> + <gl-form-group :label="$options.i18n.message" label-for="message-textarea"> <gl-form-textarea id="message-textarea" v-model="message" @@ -198,23 +198,23 @@ export default { :placeholder="$options.i18n.messagePlaceholder" data-testid="message-input" /> - </message-form-group> + </gl-form-group> - <message-form-group :label="$options.i18n.type" label-for="type-select"> + <gl-form-group :label="$options.i18n.type" label-for="type-select"> <gl-form-select id="type-select" v-model="type" :options="$options.messageTypes" /> - </message-form-group> + </gl-form-group> <template v-if="isBanner"> - <message-form-group :label="$options.i18n.theme" label-for="theme-select"> + <gl-form-group :label="$options.i18n.theme" label-for="theme-select"> <gl-form-select id="theme-select" v-model="theme" :options="$options.messageThemes" data-testid="theme-select" /> - </message-form-group> + </gl-form-group> - <message-form-group :label="$options.i18n.dismissable" label-for="dismissable-checkbox"> + <gl-form-group :label="$options.i18n.dismissable" label-for="dismissable-checkbox"> <gl-form-checkbox id="dismissable-checkbox" v-model="dismissable" @@ -223,10 +223,10 @@ export default { > <span>{{ $options.i18n.dismissableDescription }}</span> </gl-form-checkbox> - </message-form-group> + </gl-form-group> </template> - <message-form-group + <gl-form-group v-if="glFeatures.roleTargetedBroadcastMessages" :label="$options.i18n.targetRoles" data-testid="target-roles-checkboxes" @@ -235,24 +235,24 @@ export default { <gl-form-text> {{ $options.i18n.targetRolesDescription }} </gl-form-text> - </message-form-group> + </gl-form-group> - <message-form-group :label="$options.i18n.targetPath" label-for="target-path-input"> + <gl-form-group :label="$options.i18n.targetPath" label-for="target-path-input"> <gl-form-input id="target-path-input" v-model="targetPath" /> <gl-form-text> {{ $options.i18n.targetPathDescription }} </gl-form-text> - </message-form-group> + </gl-form-group> - <message-form-group :label="$options.i18n.startsAt"> + <gl-form-group :label="$options.i18n.startsAt"> <datetime-picker v-model="startsAt" /> - </message-form-group> + </gl-form-group> - <message-form-group :label="$options.i18n.endsAt"> + <gl-form-group :label="$options.i18n.endsAt"> <datetime-picker v-model="endsAt" /> - </message-form-group> + </gl-form-group> - <div class="form-actions gl-my-3"> + <div class="gl-my-5"> <gl-button type="submit" variant="confirm" diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue deleted file mode 100644 index eec51c0c28b..00000000000 --- a/app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue +++ /dev/null @@ -1,34 +0,0 @@ -<script> -import { GlFormGroup } from '@gitlab/ui'; - -export default { - name: 'MessageFormGroup', - components: { - GlFormGroup, - }, - props: { - label: { - type: String, - required: true, - }, - labelFor: { - type: String, - required: false, - default: '', - }, - }, -}; -</script> -<template> - <div> - <gl-form-group - :label="label" - :label-for="labelFor" - label-cols-sm="2" - label-class="gl-mt-3" - label-align-sm="right" - > - <slot></slot> - </gl-form-group> - </div> -</template> diff --git a/app/assets/javascripts/admin/users/components/actions/activate.vue b/app/assets/javascripts/admin/users/components/actions/activate.vue index 0099c8da8e6..af09c7618e2 100644 --- a/app/assets/javascripts/admin/users/components/actions/activate.vue +++ b/app/assets/javascripts/admin/users/components/actions/activate.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; @@ -15,7 +15,7 @@ const messageHtml = ` export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -52,7 +52,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/approve.vue b/app/assets/javascripts/admin/users/components/actions/approve.vue index 52560ebe5b1..2060528c7a0 100644 --- a/app/assets/javascripts/admin/users/components/actions/approve.vue +++ b/app/assets/javascripts/admin/users/components/actions/approve.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; @@ -17,7 +17,7 @@ const messageHtml = ` export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -54,7 +54,9 @@ export default { </script> <template> - <gl-dropdown-item data-qa-selector="approve_user_button" @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item data-qa-selector="approve_user_button" @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/ban.vue b/app/assets/javascripts/admin/users/components/actions/ban.vue index 203d076914f..d7bdceb4798 100644 --- a/app/assets/javascripts/admin/users/components/actions/ban.vue +++ b/app/assets/javascripts/admin/users/components/actions/ban.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; @@ -30,7 +30,7 @@ const messageHtml = ` export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -67,7 +67,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/block.vue b/app/assets/javascripts/admin/users/components/actions/block.vue index d50b76aaa92..534e1c76b8f 100644 --- a/app/assets/javascripts/admin/users/components/actions/block.vue +++ b/app/assets/javascripts/admin/users/components/actions/block.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; @@ -18,7 +18,7 @@ const messageHtml = ` export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -53,7 +53,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/deactivate.vue b/app/assets/javascripts/admin/users/components/actions/deactivate.vue index ab1069601d2..40911131d6d 100644 --- a/app/assets/javascripts/admin/users/components/actions/deactivate.vue +++ b/app/assets/javascripts/admin/users/components/actions/deactivate.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; @@ -25,7 +25,7 @@ const messageHtml = ` export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -62,7 +62,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/delete.vue b/app/assets/javascripts/admin/users/components/actions/delete.vue index d4f9ff4e529..83aa78c9f03 100644 --- a/app/assets/javascripts/admin/users/components/actions/delete.vue +++ b/app/assets/javascripts/admin/users/components/actions/delete.vue @@ -1,11 +1,11 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { s__ } from '~/locale'; import eventHub, { EVENT_OPEN_DELETE_USER_MODAL } from '../modals/delete_user_modal_event_hub'; export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -49,9 +49,11 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <span class="gl-text-red-500"> - <slot></slot> - </span> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <span class="gl-text-red-500"> + <slot></slot> + </span> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue b/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue index 413804c9a3b..24f0cac73f5 100644 --- a/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue +++ b/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { GlDisclosureDropdownItem, GlLoadingIcon } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import { associationsCount } from '~/api/user_api'; import eventHub, { EVENT_OPEN_DELETE_USER_MODAL } from '../modals/delete_user_modal_event_hub'; @@ -9,7 +9,7 @@ export default { loading: __('Loading'), }, components: { - GlDropdownItem, + GlDisclosureDropdownItem, GlLoadingIcon, }, props: { @@ -71,13 +71,15 @@ export default { </script> <template> - <gl-dropdown-item :disabled="loading" :aria-busy="loading" @click.capture.native.stop="onClick"> - <div v-if="loading" class="gl-display-flex gl-align-items-center"> - <gl-loading-icon class="gl-mr-3" /> - {{ $options.i18n.loading }} - </div> - <span v-else class="gl-text-red-500"> - <slot></slot> - </span> - </gl-dropdown-item> + <gl-disclosure-dropdown-item :disabled="loading" :aria-busy="loading" @action="onClick"> + <template #list-item> + <div v-if="loading" class="gl-display-flex gl-align-items-center"> + <gl-loading-icon class="gl-mr-3" /> + {{ $options.i18n.loading }} + </div> + <span v-else class="gl-text-red-500"> + <slot></slot> + </span> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/reject.vue b/app/assets/javascripts/admin/users/components/actions/reject.vue index 2b9c4acfcb5..7f786991709 100644 --- a/app/assets/javascripts/admin/users/components/actions/reject.vue +++ b/app/assets/javascripts/admin/users/components/actions/reject.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; @@ -28,7 +28,7 @@ const messageHtml = ` export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -65,7 +65,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/unban.vue b/app/assets/javascripts/admin/users/components/actions/unban.vue index 42b6fb3bdd4..f84c7594f87 100644 --- a/app/assets/javascripts/admin/users/components/actions/unban.vue +++ b/app/assets/javascripts/admin/users/components/actions/unban.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; @@ -11,7 +11,7 @@ const messageHtml = `<p>${s__( export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -48,7 +48,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/unblock.vue b/app/assets/javascripts/admin/users/components/actions/unblock.vue index f94e128a945..064f05ef8b1 100644 --- a/app/assets/javascripts/admin/users/components/actions/unblock.vue +++ b/app/assets/javascripts/admin/users/components/actions/unblock.vue @@ -1,12 +1,12 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -42,7 +42,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/unlock.vue b/app/assets/javascripts/admin/users/components/actions/unlock.vue index c78c260b4fe..039ab3d651e 100644 --- a/app/assets/javascripts/admin/users/components/actions/unlock.vue +++ b/app/assets/javascripts/admin/users/components/actions/unlock.vue @@ -1,12 +1,12 @@ <script> -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDisclosureDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; export default { components: { - GlDropdownItem, + GlDisclosureDropdownItem, }, props: { username: { @@ -41,7 +41,9 @@ export default { </script> <template> - <gl-dropdown-item @click="onClick"> - <slot></slot> - </gl-dropdown-item> + <gl-disclosure-dropdown-item @action="onClick"> + <template #list-item> + <slot></slot> + </template> + </gl-disclosure-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue index c1fb80959cf..38c7d3f9b90 100644 --- a/app/assets/javascripts/admin/users/components/user_actions.vue +++ b/app/assets/javascripts/admin/users/components/user_actions.vue @@ -1,10 +1,9 @@ <script> import { GlButton, - GlDropdown, - GlDropdownItem, - GlDropdownSectionHeader, - GlDropdownDivider, + GlDisclosureDropdown, + GlDisclosureDropdownItem, + GlDisclosureDropdownGroup, GlTooltipDirective, } from '@gitlab/ui'; import { convertArrayToCamelCase } from '~/lib/utils/common_utils'; @@ -17,10 +16,9 @@ import Actions from './actions'; export default { components: { GlButton, - GlDropdown, - GlDropdownItem, - GlDropdownSectionHeader, - GlDropdownDivider, + GlDisclosureDropdown, + GlDisclosureDropdownItem, + GlDisclosureDropdownGroup, ...Actions, }, directives: { @@ -63,6 +61,9 @@ export default { hasEditAction() { return this.userActions.includes('edit'); }, + hasEditActionOnly() { + return this.hasEditAction === true && this.hasDeleteActions === false; + }, userPaths() { return generateUserPaths(this.paths, this.user.username); }, @@ -93,10 +94,13 @@ export default { class="gl-display-flex gl-justify-content-end gl-my-n2 gl-mx-n2" :data-testid="`user-actions-${user.id}`" > - <div v-if="hasEditAction" class="gl-p-2"> - <gl-button v-if="showButtonLabels" v-bind="editButtonAttrs" icon="pencil-square">{{ - $options.i18n.edit - }}</gl-button> + <div v-if="hasEditAction" class="gl-p-2" :class="{ 'gl-mr-3': hasEditActionOnly }"> + <gl-button + v-if="showButtonLabels" + v-bind="editButtonAttrs" + :class="{ 'gl-mr-7': hasEditActionOnly }" + >{{ $options.i18n.edit }}</gl-button + > <gl-button v-else v-gl-tooltip="$options.i18n.edit" @@ -107,12 +111,15 @@ export default { </div> <div v-if="hasDropdownActions" class="gl-p-2"> - <gl-dropdown - :text="$options.i18n.userAdministration" + <gl-disclosure-dropdown + icon="ellipsis_v" + category="tertiary" + :toggle-text="$options.i18n.userAdministration" + text-sr-only data-testid="dropdown-toggle" data-qa-selector="user_actions_dropdown_toggle" :data-qa-username="user.username" - left + no-caret > <template v-for="action in dropdownSafeActions"> <component @@ -125,28 +132,32 @@ export default { > {{ $options.i18n[action] }} </component> - <gl-dropdown-item v-else-if="isLdapAction(action)" :key="action" :data-testid="action"> - {{ $options.i18n[action] }} - </gl-dropdown-item> - </template> - - <gl-dropdown-divider v-if="hasDeleteActions" /> - - <template v-for="action in dropdownDeleteActions"> - <component - :is="getActionComponent(action)" - v-if="getActionComponent(action)" + <gl-disclosure-dropdown-item + v-else-if="isLdapAction(action)" :key="action" - :paths="userPaths" - :username="user.name" - :user-id="user.id" - :user-deletion-obstacles="obstaclesForUserDeletion" - :data-testid="`delete-${action}`" + :data-testid="action" > {{ $options.i18n[action] }} - </component> + </gl-disclosure-dropdown-item> </template> - </gl-dropdown> + + <gl-disclosure-dropdown-group v-if="hasDeleteActions" bordered> + <template v-for="action in dropdownDeleteActions"> + <component + :is="getActionComponent(action)" + v-if="getActionComponent(action)" + :key="action" + :paths="userPaths" + :username="user.name" + :user-id="user.id" + :user-deletion-obstacles="obstaclesForUserDeletion" + :data-testid="`delete-${action}`" + > + {{ $options.i18n[action] }} + </component> + </template> + </gl-disclosure-dropdown-group> + </gl-disclosure-dropdown> </div> </div> </template> diff --git a/app/assets/javascripts/admin/users/components/users_table.vue b/app/assets/javascripts/admin/users/components/users_table.vue index e55622d40ba..2d2c598f953 100644 --- a/app/assets/javascripts/admin/users/components/users_table.vue +++ b/app/assets/javascripts/admin/users/components/users_table.vue @@ -135,7 +135,7 @@ export default { </template> <template #cell(settings)="{ item: user }"> - <user-actions :user="user" :paths="paths" /> + <user-actions :user="user" :paths="paths" :show-button-labels="true" /> </template> </gl-table> </div> |