diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/members | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/members')
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'; |