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>2021-10-20 11:43:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 11:43:02 +0300
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/members
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/members')
-rw-r--r--app/assets/javascripts/members/components/action_buttons/remove_member_button.vue4
-rw-r--r--app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue11
-rw-r--r--app/assets/javascripts/members/components/modals/leave_modal.vue19
-rw-r--r--app/assets/javascripts/members/components/modals/remove_member_modal.vue24
-rw-r--r--app/assets/javascripts/members/components/table/expires_at.vue66
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue87
-rw-r--r--app/assets/javascripts/members/constants.js22
7 files changed, 126 insertions, 107 deletions
diff --git a/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue b/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue
index 665e8ee69f7..69137ce615b 100644
--- a/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue
+++ b/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue
@@ -42,7 +42,7 @@ export default {
required: false,
default: false,
},
- oncallSchedules: {
+ userDeletionObstacles: {
type: Object,
required: false,
default: () => ({}),
@@ -61,7 +61,7 @@ export default {
memberPath: this.memberPath.replace(':id', this.memberId),
memberType: this.memberType,
message: this.message,
- oncallSchedules: this.oncallSchedules,
+ userDeletionObstacles: this.userDeletionObstacles,
};
},
},
diff --git a/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue b/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue
index 0c20f935d50..44d658c90a0 100644
--- a/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue
+++ b/app/assets/javascripts/members/components/action_buttons/user_action_buttons.vue
@@ -1,5 +1,6 @@
<script>
import { s__, sprintf } from '~/locale';
+import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils';
import ActionButtonGroup from './action_button_group.vue';
import LeaveButton from './leave_button.vue';
import RemoveMemberButton from './remove_member_button.vue';
@@ -49,9 +50,11 @@ export default {
},
);
},
- oncallScheduleUserData() {
- const { user: { name, oncallSchedules: schedules } = {} } = this.member;
- return { name, schedules };
+ userDeletionObstaclesUserData() {
+ return {
+ name: this.member.user?.name,
+ obstacles: parseUserDeletionObstacles(this.member.user),
+ };
},
},
};
@@ -65,7 +68,7 @@ export default {
v-else
:member-id="member.id"
:member-type="member.type"
- :oncall-schedules="oncallScheduleUserData"
+ :user-deletion-obstacles="userDeletionObstaclesUserData"
:message="message"
:title="s__('Member|Remove member')"
/>
diff --git a/app/assets/javascripts/members/components/modals/leave_modal.vue b/app/assets/javascripts/members/components/modals/leave_modal.vue
index 44178981136..e39669e17dd 100644
--- a/app/assets/javascripts/members/components/modals/leave_modal.vue
+++ b/app/assets/javascripts/members/components/modals/leave_modal.vue
@@ -3,7 +3,8 @@ import { GlModal, GlForm, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { mapState } from 'vuex';
import csrf from '~/lib/utils/csrf';
import { __, s__, sprintf } from '~/locale';
-import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
+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';
export default {
@@ -20,7 +21,7 @@ export default {
csrf,
modalId: LEAVE_MODAL_ID,
modalContent: s__('Members|Are you sure you want to leave "%{source}"?'),
- components: { GlModal, GlForm, GlSprintf, OncallSchedulesList },
+ components: { GlModal, GlForm, GlSprintf, UserDeletionObstaclesList },
directives: {
GlTooltip: GlTooltipDirective,
},
@@ -43,11 +44,11 @@ export default {
modalTitle() {
return sprintf(s__('Members|Leave "%{source}"'), { source: this.member.source.fullName });
},
- schedules() {
- return this.member.user?.oncallSchedules;
+ obstacles() {
+ return parseUserDeletionObstacles(this.member.user);
},
- isPartOfOnCallSchedules() {
- return this.schedules?.length;
+ hasObstaclesToUserDeletion() {
+ return this.obstacles?.length;
},
},
methods: {
@@ -74,9 +75,9 @@ export default {
</gl-sprintf>
</p>
- <oncall-schedules-list
- v-if="isPartOfOnCallSchedules"
- :schedules="schedules"
+ <user-deletion-obstacles-list
+ v-if="hasObstaclesToUserDeletion"
+ :obstacles="obstacles"
:is-current-user="true"
/>
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 00b6ebf9a73..b82fb0030ff 100644
--- a/app/assets/javascripts/members/components/modals/remove_member_modal.vue
+++ b/app/assets/javascripts/members/components/modals/remove_member_modal.vue
@@ -3,7 +3,7 @@ import { GlFormCheckbox, GlModal } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import csrf from '~/lib/utils/csrf';
import { s__, __ } from '~/locale';
-import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
+import UserDeletionObstaclesList from '~/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.vue';
export default {
actionCancel: {
@@ -13,7 +13,7 @@ export default {
components: {
GlFormCheckbox,
GlModal,
- OncallSchedulesList,
+ UserDeletionObstaclesList,
},
inject: ['namespace'],
computed: {
@@ -33,8 +33,8 @@ export default {
message(state) {
return state[this.namespace].removeMemberModalData.message;
},
- oncallSchedules(state) {
- return state[this.namespace].removeMemberModalData.oncallSchedules ?? {};
+ userDeletionObstacles(state) {
+ return state[this.namespace].removeMemberModalData.userDeletionObstacles ?? {};
},
removeMemberModalVisible(state) {
return state[this.namespace].removeMemberModalVisible;
@@ -60,11 +60,11 @@ export default {
},
};
},
- showUnassignIssuablesCheckbox() {
+ hasWorkspaceAccess() {
return !this.isAccessRequest && !this.isInvite;
},
- isPartOfOncallSchedules() {
- return !this.isAccessRequest && this.oncallSchedules.schedules?.length;
+ hasObstaclesToUserDeletion() {
+ return this.hasWorkspaceAccess && this.userDeletionObstacles.obstacles?.length;
},
},
methods: {
@@ -95,10 +95,10 @@ export default {
<form ref="form" :action="memberPath" method="post">
<p>{{ message }}</p>
- <oncall-schedules-list
- v-if="isPartOfOncallSchedules"
- :schedules="oncallSchedules.schedules"
- :user-name="oncallSchedules.name"
+ <user-deletion-obstacles-list
+ v-if="hasObstaclesToUserDeletion"
+ :obstacles="userDeletionObstacles.obstacles"
+ :user-name="userDeletionObstacles.name"
/>
<input ref="method" type="hidden" name="_method" value="delete" />
@@ -106,7 +106,7 @@ export default {
<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="showUnassignIssuablesCheckbox" name="unassign_issuables">
+ <gl-form-checkbox v-if="hasWorkspaceAccess" name="unassign_issuables">
{{ __('Also unassign this user from related issues and merge requests') }}
</gl-form-checkbox>
</form>
diff --git a/app/assets/javascripts/members/components/table/expires_at.vue b/app/assets/javascripts/members/components/table/expires_at.vue
deleted file mode 100644
index c91de061b50..00000000000
--- a/app/assets/javascripts/members/components/table/expires_at.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<script>
-import { GlSprintf, GlTooltipDirective } from '@gitlab/ui';
-import {
- approximateDuration,
- differenceInSeconds,
- formatDate,
- getDayDifference,
-} from '~/lib/utils/datetime_utility';
-import { DAYS_TO_EXPIRE_SOON } from '../../constants';
-
-export default {
- name: 'ExpiresAt',
- components: { GlSprintf },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- date: {
- type: String,
- required: false,
- default: null,
- },
- },
- computed: {
- noExpirationSet() {
- return this.date === null;
- },
- parsed() {
- return new Date(this.date);
- },
- differenceInSeconds() {
- return differenceInSeconds(new Date(), this.parsed);
- },
- isExpired() {
- return this.differenceInSeconds <= 0;
- },
- inWords() {
- return approximateDuration(this.differenceInSeconds);
- },
- formatted() {
- return formatDate(this.parsed);
- },
- expiresSoon() {
- return getDayDifference(new Date(), this.parsed) < DAYS_TO_EXPIRE_SOON;
- },
- cssClass() {
- return {
- 'gl-text-red-500': this.isExpired,
- 'gl-text-orange-500': this.expiresSoon,
- };
- },
- },
-};
-</script>
-
-<template>
- <span v-if="noExpirationSet">{{ s__('Members|No expiration set') }}</span>
- <span v-else v-gl-tooltip.hover :title="formatted" :class="cssClass">
- <template v-if="isExpired">{{ s__('Members|Expired') }}</template>
- <gl-sprintf v-else :message="s__('Members|in %{time}')">
- <template #time>
- {{ inWords }}
- </template>
- </gl-sprintf>
- </span>
-</template>
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index debc3fc31f6..202f3aa89e1 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -5,12 +5,17 @@ import MembersTableCell from 'ee_else_ce/members/components/table/members_table_
import { canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members/utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import initUserPopovers from '~/user_popovers';
-import { FIELDS, ACTIVE_TAB_QUERY_PARAM_NAME } from '../../constants';
+import {
+ FIELDS,
+ ACTIVE_TAB_QUERY_PARAM_NAME,
+ MEMBER_STATE_AWAITING,
+ USER_STATE_BLOCKED_PENDING_APPROVAL,
+ BADGE_LABELS_PENDING_OWNER_APPROVAL,
+} from '../../constants';
import RemoveGroupLinkModal from '../modals/remove_group_link_modal.vue';
import RemoveMemberModal from '../modals/remove_member_modal.vue';
import CreatedAt from './created_at.vue';
import ExpirationDatepicker from './expiration_datepicker.vue';
-import ExpiresAt from './expires_at.vue';
import MemberActionButtons from './member_action_buttons.vue';
import MemberAvatar from './member_avatar.vue';
import MemberSource from './member_source.vue';
@@ -24,7 +29,6 @@ export default {
GlPagination,
MemberAvatar,
CreatedAt,
- ExpiresAt,
MembersTableCell,
MemberSource,
MemberActionButtons,
@@ -131,6 +135,74 @@ export default {
window.location.href,
);
},
+ /**
+ * Returns whether it's a new or existing user
+ *
+ * If memberInviteMetadata doesn't exist, it means we're adding an existing user
+ * to the Group/Project, so `isNewUser` should be false.
+ * If memberInviteMetadata exists but `userState` has content,
+ * the user has registered but is awaiting root approval
+ *
+ * @param {object} memberInviteMetadata - MemberEntity.invite
+ * @see {@link ~/app/serializers/member_entity.rb}
+ * @returns {boolean}
+ */
+ isNewUser(memberInviteMetadata) {
+ return memberInviteMetadata && !memberInviteMetadata.userState;
+ },
+ /**
+ * Returns whether the user is awaiting root approval
+ *
+ * This checks User.state exposed via MemberEntity
+ *
+ * @param {object} memberInviteMetadata - MemberEntity.invite
+ * @see {@link ~/app/serializers/member_entity.rb}
+ * @returns {boolean}
+ */
+ isUserPendingRootApproval(memberInviteMetadata) {
+ return memberInviteMetadata?.userState === USER_STATE_BLOCKED_PENDING_APPROVAL;
+ },
+ /**
+ * Returns whether the member is awaiting owner approval
+ *
+ * This checks Member.state exposed via MemberEntity
+ *
+ * @param {Number} memberState - Member.state exposed via MemberEntity.state
+ * @see {@link ~/ee/app/models/ee/member.rb}
+ * @see {@link ~/app/serializers/member_entity.rb}
+ * @returns {boolean}
+ */
+ isMemberPendingOwnerApproval(memberState) {
+ return memberState === MEMBER_STATE_AWAITING;
+ },
+ isUserAwaiting(memberInviteMetadata, memberState) {
+ return (
+ this.isUserPendingRootApproval(memberInviteMetadata) ||
+ this.isMemberPendingOwnerApproval(memberState)
+ );
+ },
+ shouldAddPendingOwnerApprovalBadge(memberInviteMetadata, memberState) {
+ return (
+ this.isUserAwaiting(memberInviteMetadata, memberState) &&
+ !this.isNewUser(memberInviteMetadata)
+ );
+ },
+ /**
+ * Returns the string to be used in the invite badge
+ *
+ * @param {object} memberInviteMetadata - MemberEntity.invite
+ * @see {@link ~/app/serializers/member_entity.rb}
+ * @param {Number} memberState - Member.state exposed via MemberEntity.state
+ * @see {@link ~/ee/app/models/ee/member.rb}
+ * @returns {string}
+ */
+ inviteBadge(memberInviteMetadata, memberState) {
+ if (this.shouldAddPendingOwnerApprovalBadge(memberInviteMetadata, memberState)) {
+ return BADGE_LABELS_PENDING_OWNER_APPROVAL;
+ }
+
+ return '';
+ },
},
};
</script>
@@ -174,18 +246,17 @@ export default {
<created-at :date="createdAt" :created-by="createdBy" />
</template>
- <template #cell(invited)="{ item: { createdAt, createdBy } }">
+ <template #cell(invited)="{ item: { createdAt, createdBy, invite, state } }">
<created-at :date="createdAt" :created-by="createdBy" />
+ <gl-badge v-if="inviteBadge(invite, state)" data-testid="invited-badge">{{
+ inviteBadge(invite, state)
+ }}</gl-badge>
</template>
<template #cell(requested)="{ item: { createdAt } }">
<created-at :date="createdAt" />
</template>
- <template #cell(expires)="{ item: { expiresAt } }">
- <expires-at :date="expiresAt" />
- </template>
-
<template #cell(maxRole)="{ item: member }">
<members-table-cell #default="{ permissions }" :member="member">
<role-dropdown v-if="permissions.canUpdate" :permissions="permissions" :member="member" />
diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js
index 6f465245d20..f5ca881ab0d 100644
--- a/app/assets/javascripts/members/constants.js
+++ b/app/assets/javascripts/members/constants.js
@@ -38,12 +38,6 @@ export const FIELDS = [
tdClass: 'col-meta',
},
{
- key: 'expires',
- label: __('Access expires'),
- thClass: 'col-meta',
- tdClass: 'col-meta',
- },
- {
key: 'maxRole',
label: __('Max role'),
thClass: 'col-max-role',
@@ -95,6 +89,22 @@ export const TAB_QUERY_PARAM_VALUES = {
accessRequest: 'access_requests',
};
+/**
+ * This user state value comes from the User model
+ * see the state machine in app/models/user.rb
+ */
+export const USER_STATE_BLOCKED_PENDING_APPROVAL = 'blocked_pending_approval';
+
+/**
+ * This and following member state constants' values
+ * come from ee/app/models/ee/member.rb
+ */
+export const MEMBER_STATE_CREATED = 0;
+export const MEMBER_STATE_AWAITING = 1;
+export const MEMBER_STATE_ACTIVE = 2;
+
+export const BADGE_LABELS_PENDING_OWNER_APPROVAL = __('Pending owner approval');
+
export const DAYS_TO_EXPIRE_SOON = 7;
export const LEAVE_MODAL_ID = 'member-leave-modal';