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:
Diffstat (limited to 'app/assets/javascripts/members/components/table')
-rw-r--r--app/assets/javascripts/members/components/table/created_at.vue8
-rw-r--r--app/assets/javascripts/members/components/table/member_action_buttons.vue11
-rw-r--r--app/assets/javascripts/members/components/table/member_activity.vue38
-rw-r--r--app/assets/javascripts/members/components/table/member_source.vue41
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue42
-rw-r--r--app/assets/javascripts/members/components/table/members_table_cell.vue7
-rw-r--r--app/assets/javascripts/members/components/table/role_dropdown.vue40
7 files changed, 147 insertions, 40 deletions
diff --git a/app/assets/javascripts/members/components/table/created_at.vue b/app/assets/javascripts/members/components/table/created_at.vue
index 0bad70894f9..44d124ad0db 100644
--- a/app/assets/javascripts/members/components/table/created_at.vue
+++ b/app/assets/javascripts/members/components/table/created_at.vue
@@ -1,10 +1,10 @@
<script>
import { GlSprintf } from '@gitlab/ui';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserDate from '~/vue_shared/components/user_date.vue';
export default {
name: 'CreatedAt',
- components: { GlSprintf, TimeAgoTooltip },
+ components: { GlSprintf, UserDate },
props: {
date: {
type: String,
@@ -29,12 +29,12 @@ export default {
<span>
<gl-sprintf v-if="showCreatedBy" :message="s__('Members|%{time} by %{user}')">
<template #time>
- <time-ago-tooltip :time="date" />
+ <user-date :date="date" />
</template>
<template #user>
<a :href="createdBy.webUrl">{{ createdBy.name }}</a>
</template>
</gl-sprintf>
- <time-ago-tooltip v-else :time="date" />
+ <user-date v-else :date="date" />
</span>
</template>
diff --git a/app/assets/javascripts/members/components/table/member_action_buttons.vue b/app/assets/javascripts/members/components/table/member_action_buttons.vue
index ecc2ed82ad0..6ec7be608ba 100644
--- a/app/assets/javascripts/members/components/table/member_action_buttons.vue
+++ b/app/assets/javascripts/members/components/table/member_action_buttons.vue
@@ -3,12 +3,12 @@ import { MEMBER_TYPES, EE_ACTION_BUTTONS } from 'ee_else_ce/members/constants';
import AccessRequestActionButtons from '../action_buttons/access_request_action_buttons.vue';
import GroupActionButtons from '../action_buttons/group_action_buttons.vue';
import InviteActionButtons from '../action_buttons/invite_action_buttons.vue';
-import UserActionButtons from '../action_buttons/user_action_buttons.vue';
+import UserActionDropdown from '../action_dropdowns/user_action_dropdown.vue';
export default {
name: 'MemberActionButtons',
components: {
- UserActionButtons,
+ UserActionDropdown,
GroupActionButtons,
InviteActionButtons,
AccessRequestActionButtons,
@@ -32,15 +32,11 @@ export default {
type: Boolean,
required: true,
},
- isInvitedUser: {
- type: Boolean,
- required: true,
- },
},
computed: {
actionButtonComponent() {
const dictionary = {
- [MEMBER_TYPES.user]: 'user-action-buttons',
+ [MEMBER_TYPES.user]: 'user-action-dropdown',
[MEMBER_TYPES.group]: 'group-action-buttons',
[MEMBER_TYPES.invite]: 'invite-action-buttons',
[MEMBER_TYPES.accessRequest]: 'access-request-action-buttons',
@@ -60,6 +56,5 @@ export default {
:member="member"
:permissions="permissions"
:is-current-user="isCurrentUser"
- :is-invited-user="isInvitedUser"
/>
</template>
diff --git a/app/assets/javascripts/members/components/table/member_activity.vue b/app/assets/javascripts/members/components/table/member_activity.vue
new file mode 100644
index 00000000000..3b223cb1afa
--- /dev/null
+++ b/app/assets/javascripts/members/components/table/member_activity.vue
@@ -0,0 +1,38 @@
+<script>
+import UserDate from '~/vue_shared/components/user_date.vue';
+
+export default {
+ components: { UserDate },
+ props: {
+ member: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ userCreated() {
+ return this.member.user?.createdAt;
+ },
+ lastActivity() {
+ return this.member.user?.lastActivityOn;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div v-if="userCreated">
+ <strong>{{ s__('Members|User created') }}:</strong>
+ <user-date :date="userCreated" />
+ </div>
+ <div v-if="member.createdAt">
+ <strong>{{ s__('Members|Access granted') }}:</strong>
+ <user-date :date="member.createdAt" />
+ </div>
+ <div v-if="lastActivity">
+ <strong>{{ s__('Members|Last activity') }}:</strong>
+ <user-date :date="lastActivity" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/members/components/table/member_source.vue b/app/assets/javascripts/members/components/table/member_source.vue
index 30fcbfcd3f8..ed1971d020b 100644
--- a/app/assets/javascripts/members/components/table/member_source.vue
+++ b/app/assets/javascripts/members/components/table/member_source.vue
@@ -1,11 +1,19 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import { GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { s__, __ } from '~/locale';
export default {
name: 'MemberSource',
+ i18n: {
+ inherited: __('Inherited'),
+ directMember: __('Direct member'),
+ directMemberWithCreatedBy: s__('Members|Direct member by %{createdBy}'),
+ inheritedMemberWithCreatedBy: s__('Members|%{group} by %{createdBy}'),
+ },
directives: {
GlTooltip: GlTooltipDirective,
},
+ components: { GlSprintf },
props: {
memberSource: {
type: Object,
@@ -15,13 +23,40 @@ export default {
type: Boolean,
required: true,
},
+ createdBy: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ showCreatedBy() {
+ return this.createdBy?.name && this.createdBy?.webUrl;
+ },
+ messageWithCreatedBy() {
+ return this.isDirectMember
+ ? this.$options.i18n.directMemberWithCreatedBy
+ : this.$options.i18n.inheritedMemberWithCreatedBy;
+ },
},
};
</script>
<template>
- <span v-if="isDirectMember">{{ __('Direct member') }}</span>
- <a v-else v-gl-tooltip.hover :title="__('Inherited')" :href="memberSource.webUrl">{{
+ <span v-if="showCreatedBy">
+ <gl-sprintf :message="messageWithCreatedBy">
+ <template #group>
+ <a v-gl-tooltip.hover="$options.i18n.inherited" :href="memberSource.webUrl">{{
+ memberSource.fullName
+ }}</a>
+ </template>
+ <template #createdBy>
+ <a :href="createdBy.webUrl">{{ createdBy.name }}</a>
+ </template>
+ </gl-sprintf>
+ </span>
+ <span v-else-if="isDirectMember">{{ $options.i18n.directMember }}</span>
+ <a v-else v-gl-tooltip.hover="$options.i18n.inherited" :href="memberSource.webUrl">{{
memberSource.fullName
}}</a>
</template>
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index 0512bc04085..8f03a298e63 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -2,14 +2,20 @@
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 {
+ canDisableTwoFactor,
+ canUnban,
+ canOverride,
+ canRemove,
+ canRemoveBlockedByLastOwner,
+ canResend,
+ canUpdate,
+} from 'ee_else_ce/members/utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
-import UserDate from '~/vue_shared/components/user_date.vue';
import {
FIELD_KEY_ACTIONS,
FIELDS,
ACTIVE_TAB_QUERY_PARAM_NAME,
- TAB_QUERY_PARAM_VALUES,
MEMBER_STATE_AWAITING,
MEMBER_STATE_ACTIVE,
USER_STATE_BLOCKED,
@@ -23,6 +29,7 @@ import ExpirationDatepicker from './expiration_datepicker.vue';
import MemberActionButtons from './member_action_buttons.vue';
import MemberAvatar from './member_avatar.vue';
import MemberSource from './member_source.vue';
+import MemberActivity from './member_activity.vue';
import RoleDropdown from './role_dropdown.vue';
export default {
@@ -40,11 +47,13 @@ export default {
RemoveGroupLinkModal,
RemoveMemberModal,
ExpirationDatepicker,
- UserDate,
+ MemberActivity,
+ DisableTwoFactorModal: () =>
+ import('ee_component/members/components/modals/disable_two_factor_modal.vue'),
LdapOverrideConfirmationModal: () =>
import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'),
},
- inject: ['namespace', 'currentUserId'],
+ inject: ['namespace', 'currentUserId', 'canManageMembers'],
props: {
tabQueryParamValue: {
type: String,
@@ -80,18 +89,17 @@ export default {
return paramName && currentPage && perPage && totalItems;
},
- isInvitedUser() {
- return this.tabQueryParamValue === TAB_QUERY_PARAM_VALUES.invite;
- },
},
methods: {
hasActionButtons(member) {
return (
canRemove(member) ||
+ canRemoveBlockedByLastOwner(member, this.canManageMembers) ||
canResend(member) ||
canUpdate(member, this.currentUserId) ||
canOverride(member) ||
- canUnban(member)
+ canUnban(member) ||
+ canDisableTwoFactor(member)
);
},
showField(field) {
@@ -249,7 +257,11 @@ export default {
<template #cell(source)="{ item: member }">
<members-table-cell #default="{ isDirectMember }" :member="member">
- <member-source :is-direct-member="isDirectMember" :member-source="member.source" />
+ <member-source
+ :is-direct-member="isDirectMember"
+ :member-source="member.source"
+ :created-by="member.createdBy"
+ />
</members-table-cell>
</template>
@@ -281,12 +293,8 @@ export default {
</members-table-cell>
</template>
- <template #cell(userCreatedAt)="{ item: member }">
- <user-date :date="member.user.createdAt" />
- </template>
-
- <template #cell(lastActivityOn)="{ item: member }">
- <user-date :date="member.user.lastActivityOn" />
+ <template #cell(activity)="{ item: member }">
+ <member-activity :member="member" />
</template>
<template #cell(actions)="{ item: member }">
@@ -294,7 +302,6 @@ export default {
<member-action-buttons
:member-type="memberType"
:is-current-user="isCurrentUser"
- :is-invited-user="isInvitedUser"
:permissions="permissions"
:member="member"
/>
@@ -317,6 +324,7 @@ export default {
:label-prev-page="__('Go to previous page')"
align="center"
/>
+ <disable-two-factor-modal />
<remove-group-link-modal />
<remove-member-modal />
<ldap-override-confirmation-modal />
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/components/table/role_dropdown.vue b/app/assets/javascripts/members/components/table/role_dropdown.vue
index 6cd8bf57313..70808587d56 100644
--- a/app/assets/javascripts/members/components/table/role_dropdown.vue
+++ b/app/assets/javascripts/members/components/table/role_dropdown.vue
@@ -2,7 +2,9 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { mapActions } from 'vuex';
+import * as Sentry from '@sentry/browser';
import { s__ } from '~/locale';
+import { guestOverageConfirmAction } from 'ee_else_ce/members/guest_overage_confirm_action';
export default {
name: 'RoleDropdown',
@@ -11,7 +13,7 @@ export default {
GlDropdownItem,
LdapDropdownItem: () => import('ee_component/members/components/ldap/ldap_dropdown_item.vue'),
},
- inject: ['namespace'],
+ inject: ['namespace', 'group'],
props: {
member: {
type: Object,
@@ -30,7 +32,7 @@ export default {
},
computed: {
disabled() {
- return this.busy || (this.permissions.canOverride && !this.member.isOverridden);
+ return this.permissions.canOverride && !this.member.isOverridden;
},
},
mounted() {
@@ -50,22 +52,45 @@ export default {
return dispatch(`${this.namespace}/updateMemberRole`, payload);
},
}),
- handleSelect(value, name) {
- if (value === this.member.accessLevel.integerValue) {
+ async handleOverageConfirm(currentRoleValue, newRoleValue, newRoleName) {
+ return guestOverageConfirmAction({
+ currentRoleValue,
+ newRoleValue,
+ newRoleName,
+ group: this.group,
+ memberId: this.member.id,
+ memberType: this.namespace,
+ });
+ },
+ async handleSelect(newRoleValue, newRoleName) {
+ const currentRoleValue = this.member.accessLevel.integerValue;
+ if (newRoleValue === currentRoleValue) {
return;
}
this.busy = true;
+ const confirmed = await this.handleOverageConfirm(
+ currentRoleValue,
+ newRoleValue,
+ newRoleName,
+ );
+ if (!confirmed) {
+ this.busy = false;
+ return;
+ }
+
this.updateMemberRole({
memberId: this.member.id,
- accessLevel: { integerValue: value, stringValue: name },
+ accessLevel: { integerValue: newRoleValue, stringValue: newRoleName },
})
.then(() => {
this.$toast.show(s__('Members|Role updated successfully.'));
- this.busy = false;
})
- .catch(() => {
+ .catch((error) => {
+ Sentry.captureException(error);
+ })
+ .finally(() => {
this.busy = false;
});
},
@@ -80,6 +105,7 @@ export default {
:text="member.accessLevel.stringValue"
:header-text="__('Change role')"
:disabled="disabled"
+ :loading="busy"
>
<gl-dropdown-item
v-for="(value, name) in member.validRoles"