diff options
Diffstat (limited to 'app/assets/javascripts/members/components/action_dropdowns')
4 files changed, 278 insertions, 0 deletions
diff --git a/app/assets/javascripts/members/components/action_dropdowns/constants.js b/app/assets/javascripts/members/components/action_dropdowns/constants.js new file mode 100644 index 00000000000..8ccfc57dc28 --- /dev/null +++ b/app/assets/javascripts/members/components/action_dropdowns/constants.js @@ -0,0 +1,22 @@ +import { __, s__ } from '~/locale'; + +export const I18N = { + actions: __('More actions'), + disableTwoFactor: s__('Members|Disable two-factor authentication'), + editPermissions: s__('Members|Edit permissions'), + leaveGroup: __('Leave group'), + removeMember: __('Remove member'), + confirmDisableTwoFactor: s__( + 'Members|Are you sure you want to disable the two-factor authentication for %{userName}?', + ), + confirmNormalUserRemoval: s__( + 'Members|Are you sure you want to remove %{userName} from "%{group}"?', + ), + confirmOrphanedUserRemoval: s__( + 'Members|Are you sure you want to remove this orphaned member from "%{group}"?', + ), + personalProjectOwnerCannotBeRemoved: s__("Members|A personal project's owner cannot be removed."), + lastGroupOwnerCannotBeRemoved: s__( + 'Members|A group must have at least one owner. To remove the member, assign a new owner.', + ), +}; diff --git a/app/assets/javascripts/members/components/action_dropdowns/leave_group_dropdown_item.vue b/app/assets/javascripts/members/components/action_dropdowns/leave_group_dropdown_item.vue new file mode 100644 index 00000000000..15606ad567c --- /dev/null +++ b/app/assets/javascripts/members/components/action_dropdowns/leave_group_dropdown_item.vue @@ -0,0 +1,36 @@ +<script> +import { GlDropdownItem, GlModalDirective } from '@gitlab/ui'; +import { LEAVE_MODAL_ID } from '../../constants'; +import LeaveModal from '../modals/leave_modal.vue'; + +export default { + name: 'LeaveGroupDropdownItem', + modalId: LEAVE_MODAL_ID, + components: { + GlDropdownItem, + LeaveModal, + }, + directives: { + GlModal: GlModalDirective, + }, + props: { + member: { + type: Object, + required: true, + }, + permissions: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <gl-dropdown-item v-gl-modal="$options.modalId"> + <span class="gl-text-red-500"> + <slot></slot> + </span> + <leave-modal :member="member" :permissions="permissions" /> + </gl-dropdown-item> +</template> diff --git a/app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue b/app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue new file mode 100644 index 00000000000..f224aaa31f7 --- /dev/null +++ b/app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue @@ -0,0 +1,86 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; +import { mapActions, mapState } from 'vuex'; + +export default { + name: 'RemoveMemberDropdownItem', + components: { GlDropdownItem }, + inject: ['namespace'], + props: { + memberId: { + type: Number, + required: true, + }, + /** + * `GroupMember` (`app/models/members/group_member.rb`) + * or + * `ProjectMember` (`app/models/members/project_member.rb`). + */ + memberModelType: { + type: String, + required: false, + default: null, + }, + modalMessage: { + type: String, + required: true, + }, + isAccessRequest: { + type: Boolean, + required: false, + default: false, + }, + isInvite: { + type: Boolean, + required: false, + default: false, + }, + userDeletionObstacles: { + type: Object, + required: false, + default: () => ({}), + }, + preventRemoval: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + ...mapState({ + memberPath(state) { + return state[this.namespace].memberPath; + }, + }), + modalData() { + return { + isAccessRequest: this.isAccessRequest, + isInvite: this.isInvite, + memberPath: this.memberPath.replace(':id', this.memberId), + memberModelType: this.memberModelType, + message: this.modalMessage, + userDeletionObstacles: this.userDeletionObstacles, + preventRemoval: this.preventRemoval, + }; + }, + }, + methods: { + ...mapActions({ + showRemoveMemberModal(dispatch, payload) { + return dispatch(`${this.namespace}/showRemoveMemberModal`, payload); + }, + }), + }, +}; +</script> + +<template> + <gl-dropdown-item + data-qa-selector="delete_member_dropdown_item" + @click="showRemoveMemberModal(modalData)" + > + <span class="gl-text-red-500"> + <slot></slot> + </span> + </gl-dropdown-item> +</template> diff --git a/app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue b/app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue new file mode 100644 index 00000000000..8f5c32956a2 --- /dev/null +++ b/app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue @@ -0,0 +1,134 @@ +<script> +import { GlDropdown, GlTooltipDirective } from '@gitlab/ui'; +import { sprintf } from '~/locale'; +import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils'; +import { + MEMBER_MODEL_TYPE_GROUP_MEMBER, + MEMBER_MODEL_TYPE_PROJECT_MEMBER, +} from '~/members/constants'; +import { I18N } from './constants'; +import LeaveGroupDropdownItem from './leave_group_dropdown_item.vue'; +import RemoveMemberDropdownItem from './remove_member_dropdown_item.vue'; + +export default { + name: 'UserActionDropdown', + i18n: I18N, + components: { + GlDropdown, + DisableTwoFactorDropdownItem: () => + import( + 'ee_component/members/components/action_dropdowns/disable_two_factor_dropdown_item.vue' + ), + LdapOverrideDropdownItem: () => + import('ee_component/members/components/ldap/ldap_override_dropdown_item.vue'), + LeaveGroupDropdownItem, + RemoveMemberDropdownItem, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + member: { + type: Object, + required: true, + }, + isCurrentUser: { + type: Boolean, + required: true, + }, + permissions: { + type: Object, + required: true, + }, + }, + computed: { + modalDisableTwoFactor() { + const userName = this.member.user.username; + return sprintf(this.$options.i18n.confirmDisableTwoFactor, { userName }, false); + }, + modalRemoveUser() { + const { user, source } = this.member; + + if (this.permissions.canRemoveBlockedByLastOwner) { + if (this.member.type === MEMBER_MODEL_TYPE_PROJECT_MEMBER) { + return I18N.personalProjectOwnerCannotBeRemoved; + } + + if (this.member.type === MEMBER_MODEL_TYPE_GROUP_MEMBER) { + return I18N.lastGroupOwnerCannotBeRemoved; + } + } + + if (user) { + return sprintf( + this.$options.i18n.confirmNormalUserRemoval, + { userName: user.name, group: source.fullName }, + false, + ); + } + + return sprintf(this.$options.i18n.confirmOrphanedUserRemoval, { group: source.fullName }); + }, + userDeletionObstaclesUserData() { + return { + name: this.member.user?.name, + obstacles: parseUserDeletionObstacles(this.member.user), + }; + }, + showDropdown() { + return ( + this.permissions.canDisableTwoFactor || this.showLeaveOrRemove || this.showLdapOverride + ); + }, + showLeaveOrRemove() { + return this.permissions.canRemove || this.permissions.canRemoveBlockedByLastOwner; + }, + showLdapOverride() { + return this.permissions.canOverride && !this.member.isOverridden; + }, + }, +}; +</script> + +<template> + <gl-dropdown + v-if="showDropdown" + v-gl-tooltip="$options.i18n.actions" + :text="$options.i18n.actions" + text-sr-only + icon="ellipsis_v" + category="tertiary" + no-caret + right + data-testid="user-action-dropdown" + data-qa-selector="user_action_dropdown" + > + <disable-two-factor-dropdown-item + v-if="permissions.canDisableTwoFactor" + :modal-message="modalDisableTwoFactor" + :user-id="member.user.id" + > + {{ $options.i18n.disableTwoFactor }} + </disable-two-factor-dropdown-item> + + <template v-if="showLeaveOrRemove"> + <leave-group-dropdown-item v-if="isCurrentUser" :member="member" :permissions="permissions">{{ + $options.i18n.leaveGroup + }}</leave-group-dropdown-item> + + <remove-member-dropdown-item + v-else + :member-id="member.id" + :member-model-type="member.type" + :user-deletion-obstacles="userDeletionObstaclesUserData" + :modal-message="modalRemoveUser" + :prevent-removal="permissions.canRemoveBlockedByLastOwner" + >{{ $options.i18n.removeMember }}</remove-member-dropdown-item + > + </template> + + <ldap-override-dropdown-item v-else-if="showLdapOverride" :member="member">{{ + $options.i18n.editPermissions + }}</ldap-override-dropdown-item> + </gl-dropdown> +</template> |