Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-01-09 21:10:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-01-09 21:10:06 +0300
commit17deb2a503bb8163514fe37618bf36f75376b9ae (patch)
treef0bc3819cb3f9f19f30301191e250d069461c8d3 /app/assets/javascripts/members
parente9de69b545c25c9cb7fd410f9bf8ba34c6bb727b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/members')
-rw-r--r--app/assets/javascripts/members/components/action_dropdowns/constants.js4
-rw-r--r--app/assets/javascripts/members/components/action_dropdowns/leave_group_dropdown_item.vue6
-rw-r--r--app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue6
-rw-r--r--app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue24
-rw-r--r--app/assets/javascripts/members/components/modals/leave_modal.vue63
-rw-r--r--app/assets/javascripts/members/components/modals/remove_member_modal.vue36
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue12
-rw-r--r--app/assets/javascripts/members/components/table/members_table_cell.vue7
-rw-r--r--app/assets/javascripts/members/utils.js3
9 files changed, 129 insertions, 32 deletions
diff --git a/app/assets/javascripts/members/components/action_dropdowns/constants.js b/app/assets/javascripts/members/components/action_dropdowns/constants.js
index f6718713e2b..eb5b2182ece 100644
--- a/app/assets/javascripts/members/components/action_dropdowns/constants.js
+++ b/app/assets/javascripts/members/components/action_dropdowns/constants.js
@@ -11,4 +11,8 @@ export const I18N = {
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
index b5e0317d54f..15606ad567c 100644
--- 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
@@ -18,6 +18,10 @@ export default {
type: Object,
required: true,
},
+ permissions: {
+ type: Object,
+ required: true,
+ },
},
};
</script>
@@ -27,6 +31,6 @@ export default {
<span class="gl-text-red-500">
<slot></slot>
</span>
- <leave-modal :member="member" />
+ <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
index 18f1a8739bf..f224aaa31f7 100644
--- 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
@@ -40,6 +40,11 @@ export default {
required: false,
default: () => ({}),
},
+ preventRemoval: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
...mapState({
@@ -55,6 +60,7 @@ export default {
memberModelType: this.memberModelType,
message: this.modalMessage,
userDeletionObstacles: this.userDeletionObstacles,
+ preventRemoval: this.preventRemoval,
};
},
},
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
index 4e5e6b0cab4..816481d4329 100644
--- a/app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue
+++ b/app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue
@@ -2,6 +2,10 @@
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';
@@ -37,6 +41,16 @@ export default {
modalMessage() {
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,
@@ -54,7 +68,10 @@ export default {
};
},
showDropdown() {
- return this.permissions.canRemove || this.showLdapOverride;
+ return this.showLeaveOrRemove || this.showLdapOverride;
+ },
+ showLeaveOrRemove() {
+ return this.permissions.canRemove || this.permissions.canRemoveBlockedByLastOwner;
},
showLdapOverride() {
return this.permissions.canOverride && !this.member.isOverridden;
@@ -76,8 +93,8 @@ export default {
data-testid="user-action-dropdown"
data-qa-selector="user_action_dropdown"
>
- <template v-if="permissions.canRemove">
- <leave-group-dropdown-item v-if="isCurrentUser" :member="member">{{
+ <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
@@ -86,6 +103,7 @@ export default {
:member-model-type="member.type"
:user-deletion-obstacles="userDeletionObstaclesUserData"
:modal-message="modalMessage"
+ :prevent-removal="permissions.canRemoveBlockedByLastOwner"
>{{ $options.i18n.removeMember }}</remove-member-dropdown-item
>
</template>
diff --git a/app/assets/javascripts/members/components/modals/leave_modal.vue b/app/assets/javascripts/members/components/modals/leave_modal.vue
index e39669e17dd..8bc6aca9cc1 100644
--- a/app/assets/javascripts/members/components/modals/leave_modal.vue
+++ b/app/assets/javascripts/members/components/modals/leave_modal.vue
@@ -5,22 +5,30 @@ import csrf from '~/lib/utils/csrf';
import { __, s__, sprintf } from '~/locale';
import UserDeletionObstaclesList from '~/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.vue';
import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils';
-import { LEAVE_MODAL_ID } from '../../constants';
+import {
+ LEAVE_MODAL_ID,
+ MEMBER_MODEL_TYPE_GROUP_MEMBER,
+ MEMBER_MODEL_TYPE_PROJECT_MEMBER,
+} from '../../constants';
export default {
name: 'LeaveModal',
actionCancel: {
text: __('Cancel'),
},
- actionPrimary: {
- text: __('Leave'),
- attributes: {
- variant: 'danger',
- },
- },
csrf,
modalId: LEAVE_MODAL_ID,
- modalContent: s__('Members|Are you sure you want to leave "%{source}"?'),
+ i18n: {
+ title: s__('Members|Leave "%{source}"'),
+ body: s__('Members|Are you sure you want to leave "%{source}"?'),
+ preventedTitle: s__('Members|Cannot leave "%{source}"'),
+ preventedBodyProjectMemberModelType: s__(
+ 'Members|You cannot remove yourself from a personal project.',
+ ),
+ preventedBodyGroupMemberModelType: s__(
+ 'Members|A group must have at least one owner. To leave this group, assign a new owner.',
+ ),
+ },
components: { GlModal, GlForm, GlSprintf, UserDeletionObstaclesList },
directives: {
GlTooltip: GlTooltipDirective,
@@ -31,6 +39,10 @@ export default {
type: Object,
required: true,
},
+ permissions: {
+ type: Object,
+ required: true,
+ },
},
computed: {
...mapState({
@@ -42,7 +54,35 @@ export default {
return this.memberPath.replace(/:id$/, 'leave');
},
modalTitle() {
- return sprintf(s__('Members|Leave "%{source}"'), { source: this.member.source.fullName });
+ return sprintf(
+ this.permissions.canRemoveBlockedByLastOwner
+ ? this.$options.i18n.preventedTitle
+ : this.$options.i18n.title,
+ { source: this.member.source.fullName },
+ );
+ },
+ preventedModalBody() {
+ if (this.member.type === MEMBER_MODEL_TYPE_PROJECT_MEMBER) {
+ return this.$options.i18n.preventedBodyProjectMemberModelType;
+ }
+
+ if (this.member.type === MEMBER_MODEL_TYPE_GROUP_MEMBER) {
+ return this.$options.i18n.preventedBodyGroupMemberModelType;
+ }
+
+ return null;
+ },
+ actionPrimary() {
+ if (this.permissions.canRemoveBlockedByLastOwner) {
+ return null;
+ }
+
+ return {
+ text: __('Leave'),
+ attributes: {
+ variant: 'danger',
+ },
+ };
},
obstacles() {
return parseUserDeletionObstacles(this.member.user);
@@ -64,13 +104,14 @@ export default {
v-bind="$attrs"
:modal-id="$options.modalId"
:title="modalTitle"
- :action-primary="$options.actionPrimary"
+ :action-primary="actionPrimary"
:action-cancel="$options.actionCancel"
@primary="handlePrimary"
>
<gl-form ref="form" :action="leavePath" method="post">
<p>
- <gl-sprintf :message="$options.modalContent">
+ <template v-if="permissions.canRemoveBlockedByLastOwner">{{ preventedModalBody }}</template>
+ <gl-sprintf v-else :message="$options.i18n.body">
<template #source>{{ member.source.fullName }}</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/members/components/modals/remove_member_modal.vue b/app/assets/javascripts/members/components/modals/remove_member_modal.vue
index edd464574fb..337379d8b4e 100644
--- a/app/assets/javascripts/members/components/modals/remove_member_modal.vue
+++ b/app/assets/javascripts/members/components/modals/remove_member_modal.vue
@@ -42,6 +42,9 @@ export default {
userDeletionObstacles(state) {
return state[this.namespace].removeMemberModalData.userDeletionObstacles ?? {};
},
+ preventRemoval(state) {
+ return state[this.namespace].removeMemberModalData.preventRemoval;
+ },
removeMemberModalVisible(state) {
return state[this.namespace].removeMemberModalVisible;
},
@@ -59,6 +62,10 @@ export default {
return __('Remove member');
},
actionPrimary() {
+ if (this.preventRemoval) {
+ return null;
+ }
+
return {
text: this.actionText,
attributes: {
@@ -101,21 +108,22 @@ export default {
>
<form ref="form" :action="memberPath" method="post">
<p>{{ message }}</p>
+ <template v-if="!preventRemoval">
+ <user-deletion-obstacles-list
+ v-if="hasObstaclesToUserDeletion"
+ :obstacles="userDeletionObstacles.obstacles"
+ :user-name="userDeletionObstacles.name"
+ />
- <user-deletion-obstacles-list
- v-if="hasObstaclesToUserDeletion"
- :obstacles="userDeletionObstacles.obstacles"
- :user-name="userDeletionObstacles.name"
- />
-
- <input ref="method" type="hidden" name="_method" value="delete" />
- <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
- <gl-form-checkbox v-if="isGroupMember" name="remove_sub_memberships">
- {{ __('Also remove direct user membership from subgroups and projects') }}
- </gl-form-checkbox>
- <gl-form-checkbox v-if="hasWorkspaceAccess" name="unassign_issuables">
- {{ __('Also unassign this user from related issues and merge requests') }}
- </gl-form-checkbox>
+ <input ref="method" type="hidden" name="_method" value="delete" />
+ <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
+ <gl-form-checkbox v-if="isGroupMember" name="remove_sub_memberships">
+ {{ __('Also remove direct user membership from subgroups and projects') }}
+ </gl-form-checkbox>
+ <gl-form-checkbox v-if="hasWorkspaceAccess" name="unassign_issuables">
+ {{ __('Also unassign this user from related issues and merge requests') }}
+ </gl-form-checkbox>
+ </template>
</form>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index c847f9c8311..6d242f38d0e 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -2,7 +2,14 @@
import { GlTable, GlBadge, GlPagination } from '@gitlab/ui';
import { mapState } from 'vuex';
import MembersTableCell from 'ee_else_ce/members/components/table/members_table_cell.vue';
-import { canUnban, canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members/utils';
+import {
+ canUnban,
+ canOverride,
+ canRemove,
+ canRemoveBlockedByLastOwner,
+ canResend,
+ canUpdate,
+} from 'ee_else_ce/members/utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import {
FIELD_KEY_ACTIONS,
@@ -43,7 +50,7 @@ export default {
LdapOverrideConfirmationModal: () =>
import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'),
},
- inject: ['namespace', 'currentUserId'],
+ inject: ['namespace', 'currentUserId', 'canManageMembers'],
props: {
tabQueryParamValue: {
type: String,
@@ -84,6 +91,7 @@ export default {
hasActionButtons(member) {
return (
canRemove(member) ||
+ canRemoveBlockedByLastOwner(member, this.canManageMembers) ||
canResend(member) ||
canUpdate(member, this.currentUserId) ||
canOverride(member) ||
diff --git a/app/assets/javascripts/members/components/table/members_table_cell.vue b/app/assets/javascripts/members/components/table/members_table_cell.vue
index 51eff428d63..407cbc55dd3 100644
--- a/app/assets/javascripts/members/components/table/members_table_cell.vue
+++ b/app/assets/javascripts/members/components/table/members_table_cell.vue
@@ -5,13 +5,14 @@ import {
isDirectMember,
isCurrentUser,
canRemove,
+ canRemoveBlockedByLastOwner,
canResend,
canUpdate,
} from '../../utils';
export default {
name: 'MembersTableCell',
- inject: ['currentUserId'],
+ inject: ['currentUserId', 'canManageMembers'],
props: {
member: {
type: Object,
@@ -45,6 +46,9 @@ export default {
isCurrentUser() {
return isCurrentUser(this.member, this.currentUserId);
},
+ canRemoveBlockedByLastOwner() {
+ return canRemoveBlockedByLastOwner(this.member, this.canManageMembers);
+ },
canRemove() {
return canRemove(this.member);
},
@@ -62,6 +66,7 @@ export default {
isCurrentUser: this.isCurrentUser,
permissions: {
canRemove: this.canRemove,
+ canRemoveBlockedByLastOwner: this.canRemoveBlockedByLastOwner,
canResend: this.canResend,
canUpdate: this.canUpdate,
},
diff --git a/app/assets/javascripts/members/utils.js b/app/assets/javascripts/members/utils.js
index bf87ab53d36..b7af97ebac5 100644
--- a/app/assets/javascripts/members/utils.js
+++ b/app/assets/javascripts/members/utils.js
@@ -51,6 +51,9 @@ export const canRemove = (member) => {
return isDirectMember(member) && member.canRemove;
};
+export const canRemoveBlockedByLastOwner = (member, canManageMembers) =>
+ isDirectMember(member) && canManageMembers && member.isLastOwner;
+
export const canResend = (member) => {
return Boolean(member.invite?.canResend);
};