diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
commit | 7e9c479f7de77702622631cff2628a9c8dcbc627 (patch) | |
tree | c8f718a08e110ad7e1894510980d2155a6549197 /app/assets/javascripts/vue_shared/components/members/table | |
parent | e852b0ae16db4052c1c567d9efa4facc81146e88 (diff) | |
download | gitlab-ce-7e9c479f7de77702622631cff2628a9c8dcbc627.tar.gz |
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/members/table')
4 files changed, 184 insertions, 11 deletions
diff --git a/app/assets/javascripts/vue_shared/components/members/table/expiration_datepicker.vue b/app/assets/javascripts/vue_shared/components/members/table/expiration_datepicker.vue new file mode 100644 index 00000000000..0a8af81c1d1 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/members/table/expiration_datepicker.vue @@ -0,0 +1,99 @@ +<script> +import { GlDatepicker } from '@gitlab/ui'; +import { mapActions } from 'vuex'; +import { getDateInFuture } from '~/lib/utils/datetime_utility'; +import { s__ } from '~/locale'; + +export default { + name: 'ExpirationDatepicker', + components: { GlDatepicker }, + props: { + member: { + type: Object, + required: true, + }, + permissions: { + type: Object, + required: true, + }, + }, + data() { + return { + selectedDate: null, + busy: false, + }; + }, + computed: { + minDate() { + // Members expire at the beginning of the day. + // The first selectable day should be tomorrow. + const today = new Date(); + const beginningOfToday = new Date(today.setHours(0, 0, 0, 0)); + + return getDateInFuture(beginningOfToday, 1); + }, + disabled() { + return ( + this.busy || + !this.permissions.canUpdate || + (this.permissions.canOverride && !this.member.isOverridden) + ); + }, + }, + mounted() { + if (this.member.expiresAt) { + this.selectedDate = new Date(this.member.expiresAt); + } + }, + methods: { + ...mapActions(['updateMemberExpiration']), + handleInput(date) { + this.busy = true; + this.updateMemberExpiration({ + memberId: this.member.id, + expiresAt: date, + }) + .then(() => { + this.$toast.show(s__('Members|Expiration date updated successfully.')); + this.busy = false; + }) + .catch(() => { + this.busy = false; + }); + }, + handleClear() { + this.busy = true; + + this.updateMemberExpiration({ + memberId: this.member.id, + expiresAt: null, + }) + .then(() => { + this.$toast.show(s__('Members|Expiration date removed successfully.')); + this.selectedDate = null; + this.busy = false; + }) + .catch(() => { + this.busy = false; + }); + }, + }, +}; +</script> + +<template> + <!-- `:target="null"` allows the datepicker to be opened on focus --> + <!-- `:container="null"` renders the datepicker in the body to prevent conflicting CSS table styles --> + <gl-datepicker + v-model="selectedDate" + class="gl-max-w-full" + show-clear-button + :target="null" + :container="null" + :min-date="minDate" + :placeholder="__('Expiration date')" + :disabled="disabled" + @input="handleInput" + @clear="handleClear" + /> +</template> diff --git a/app/assets/javascripts/vue_shared/components/members/table/members_table.vue b/app/assets/javascripts/vue_shared/components/members/table/members_table.vue index c1a80a85dbe..a4f67caff31 100644 --- a/app/assets/javascripts/vue_shared/components/members/table/members_table.vue +++ b/app/assets/javascripts/vue_shared/components/members/table/members_table.vue @@ -1,6 +1,13 @@ <script> import { mapState } from 'vuex'; import { GlTable, GlBadge } from '@gitlab/ui'; +import MembersTableCell from 'ee_else_ce/vue_shared/components/members/table/members_table_cell.vue'; +import { + canOverride, + canRemove, + canResend, + canUpdate, +} from 'ee_else_ce/vue_shared/components/members/utils'; import { FIELDS } from '../constants'; import initUserPopovers from '~/user_popovers'; import MemberAvatar from './member_avatar.vue'; @@ -8,9 +15,9 @@ import MemberSource from './member_source.vue'; import CreatedAt from './created_at.vue'; import ExpiresAt from './expires_at.vue'; import MemberActionButtons from './member_action_buttons.vue'; -import MembersTableCell from './members_table_cell.vue'; import RoleDropdown from './role_dropdown.vue'; import RemoveGroupLinkModal from '../modals/remove_group_link_modal.vue'; +import ExpirationDatepicker from './expiration_datepicker.vue'; export default { name: 'MembersTable', @@ -25,23 +32,56 @@ export default { MemberActionButtons, RoleDropdown, RemoveGroupLinkModal, + ExpirationDatepicker, + LdapOverrideConfirmationModal: () => + import( + 'ee_component/vue_shared/components/members/ldap/ldap_override_confirmation_modal.vue' + ), }, computed: { - ...mapState(['members', 'tableFields']), + ...mapState(['members', 'tableFields', 'tableAttrs', 'currentUserId', 'sourceId']), filteredFields() { - return FIELDS.filter(field => this.tableFields.includes(field.key)); + return FIELDS.filter(field => this.tableFields.includes(field.key) && this.showField(field)); + }, + userIsLoggedIn() { + return this.currentUserId !== null; }, }, mounted() { initUserPopovers(this.$el.querySelectorAll('.js-user-link')); }, + methods: { + showField(field) { + if (!Object.prototype.hasOwnProperty.call(field, 'showFunction')) { + return true; + } + + return this[field.showFunction](); + }, + showActionsField() { + if (!this.userIsLoggedIn) { + return false; + } + + return this.members.some(member => { + return ( + canRemove(member, this.sourceId) || + canResend(member) || + canUpdate(member, this.currentUserId, this.sourceId) || + canOverride(member) + ); + }); + }, + }, }; </script> <template> <div> <gl-table + v-bind="tableAttrs.table" class="members-table" + data-testid="members-table" head-variant="white" stacked="lg" :fields="filteredFields" @@ -50,6 +90,7 @@ export default { thead-class="border-bottom" :empty-text="__('No members found')" show-empty + :tbody-tr-attr="tableAttrs.tr" > <template #cell(account)="{ item: member }"> <members-table-cell #default="{ memberType, isCurrentUser }" :member="member"> @@ -85,11 +126,17 @@ export default { <template #cell(maxRole)="{ item: member }"> <members-table-cell #default="{ permissions }" :member="member"> - <role-dropdown v-if="permissions.canUpdate" :member="member" /> + <role-dropdown v-if="permissions.canUpdate" :permissions="permissions" :member="member" /> <gl-badge v-else>{{ member.accessLevel.stringValue }}</gl-badge> </members-table-cell> </template> + <template #cell(expiration)="{ item: member }"> + <members-table-cell #default="{ permissions }" :member="member"> + <expiration-datepicker :permissions="permissions" :member="member" /> + </members-table-cell> + </template> + <template #cell(actions)="{ item: member }"> <members-table-cell #default="{ memberType, isCurrentUser, permissions }" :member="member"> <member-action-buttons @@ -106,5 +153,6 @@ export default { </template> </gl-table> <remove-group-link-modal /> + <ldap-override-confirmation-modal /> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/members/table/members_table_cell.vue b/app/assets/javascripts/vue_shared/components/members/table/members_table_cell.vue index 5602978bb6c..11e1aef9803 100644 --- a/app/assets/javascripts/vue_shared/components/members/table/members_table_cell.vue +++ b/app/assets/javascripts/vue_shared/components/members/table/members_table_cell.vue @@ -1,6 +1,7 @@ <script> import { mapState } from 'vuex'; import { MEMBER_TYPES } from '../constants'; +import { isGroup, isDirectMember, isCurrentUser, canRemove, canResend, canUpdate } from '../utils'; export default { name: 'MembersTableCell', @@ -13,7 +14,7 @@ export default { computed: { ...mapState(['sourceId', 'currentUserId']), isGroup() { - return Boolean(this.member.sharedWithGroup); + return isGroup(this.member); }, isInvite() { return Boolean(this.member.invite); @@ -33,19 +34,19 @@ export default { return MEMBER_TYPES.user; }, isDirectMember() { - return this.isGroup || this.member.source?.id === this.sourceId; + return isDirectMember(this.member, this.sourceId); }, isCurrentUser() { - return this.member.user?.id === this.currentUserId; + return isCurrentUser(this.member, this.currentUserId); }, canRemove() { - return this.isDirectMember && this.member.canRemove; + return canRemove(this.member, this.sourceId); }, canResend() { - return Boolean(this.member.invite?.canResend); + return canResend(this.member); }, canUpdate() { - return !this.isCurrentUser && this.isDirectMember && this.member.canUpdate; + return canUpdate(this.member, this.currentUserId, this.sourceId); }, }, render() { diff --git a/app/assets/javascripts/vue_shared/components/members/table/role_dropdown.vue b/app/assets/javascripts/vue_shared/components/members/table/role_dropdown.vue index 2b40ccc3a9d..6f6cae6072d 100644 --- a/app/assets/javascripts/vue_shared/components/members/table/role_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/members/table/role_dropdown.vue @@ -9,12 +9,18 @@ export default { components: { GlDropdown, GlDropdownItem, + LdapDropdownItem: () => + import('ee_component/vue_shared/components/members/ldap/ldap_dropdown_item.vue'), }, props: { member: { type: Object, required: true, }, + permissions: { + type: Object, + required: true, + }, }, data() { return { @@ -22,8 +28,21 @@ export default { busy: false, }; }, + computed: { + disabled() { + return this.busy || (this.permissions.canOverride && !this.member.isOverridden); + }, + }, mounted() { this.isDesktop = bp.isDesktop(); + + // Bootstrap Vue and GlDropdown to not support adding attributes to the dropdown toggle + // This can be changed once https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1060 is implemented + const dropdownToggle = this.$refs.glDropdown.$el.querySelector('.dropdown-toggle'); + + if (dropdownToggle) { + dropdownToggle.setAttribute('data-qa-selector', 'access_level_dropdown'); + } }, methods: { ...mapActions(['updateMemberRole']), @@ -52,19 +71,25 @@ export default { <template> <gl-dropdown + ref="glDropdown" :right="!isDesktop" :text="member.accessLevel.stringValue" :header-text="__('Change permissions')" - :disabled="busy" + :disabled="disabled" > <gl-dropdown-item v-for="(value, name) in member.validRoles" :key="value" is-check-item :is-checked="value === member.accessLevel.integerValue" + data-qa-selector="access_level_link" @click="handleSelect(value, name)" > {{ name }} </gl-dropdown-item> + <ldap-dropdown-item + v-if="permissions.canOverride && member.isOverridden" + :member-id="member.id" + /> </gl-dropdown> </template> |