diff options
Diffstat (limited to 'app/assets/javascripts/members/components')
3 files changed, 165 insertions, 7 deletions
diff --git a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue index cc97d235a9c..cc0533391df 100644 --- a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue +++ b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue @@ -1,10 +1,11 @@ <script> import { GlFilteredSearchToken } from '@gitlab/ui'; import { mapState } from 'vuex'; -import { getParameterByName } from '~/lib/utils/common_utils'; -import { setUrlParams, queryToObject } from '~/lib/utils/url_utility'; +import { getParameterByName, urlParamsToObject } from '~/lib/utils/common_utils'; +import { setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import { SEARCH_TOKEN_TYPE, SORT_PARAM } from '~/members/constants'; +import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; export default { @@ -17,7 +18,7 @@ export default { title: s__('Members|2FA'), token: GlFilteredSearchToken, unique: true, - operators: [{ value: '=', description: 'is' }], + operators: OPERATOR_IS_ONLY, options: [ { value: 'enabled', title: s__('Members|Enabled') }, { value: 'disabled', title: s__('Members|Disabled') }, @@ -30,7 +31,7 @@ export default { title: s__('Members|Membership'), token: GlFilteredSearchToken, unique: true, - operators: [{ value: '=', description: 'is' }], + operators: OPERATOR_IS_ONLY, options: [ { value: 'exclude', title: s__('Members|Direct') }, { value: 'only', title: s__('Members|Inherited') }, @@ -63,7 +64,7 @@ export default { }, }, created() { - const query = queryToObject(window.location.search); + const query = urlParamsToObject(window.location.search); const tokens = this.tokens .filter((token) => query[token.type]) @@ -97,9 +98,12 @@ export default { if (type === SEARCH_TOKEN_TYPE) { if (value.data !== '') { + const { searchParam } = this.filteredSearchBar; + const { [searchParam]: searchParamValue } = accumulator; + return { ...accumulator, - [this.filteredSearchBar.searchParam]: value.data, + [searchParam]: searchParamValue ? `${searchParamValue} ${value.data}` : value.data, }; } } else { diff --git a/app/assets/javascripts/members/components/members_tabs.vue b/app/assets/javascripts/members/components/members_tabs.vue new file mode 100644 index 00000000000..37b9135126d --- /dev/null +++ b/app/assets/javascripts/members/components/members_tabs.vue @@ -0,0 +1,124 @@ +<script> +import { GlTabs, GlTab, GlBadge } from '@gitlab/ui'; +import { mapState } from 'vuex'; +import { urlParamsToObject } from '~/lib/utils/common_utils'; +import { __ } from '~/locale'; +import { MEMBER_TYPES } from '../constants'; +import MembersApp from './app.vue'; + +const countComputed = (state, namespace) => state[namespace]?.pagination?.totalItems || 0; + +export default { + name: 'MembersTabs', + tabs: [ + { + namespace: MEMBER_TYPES.user, + title: __('Members'), + }, + { + namespace: MEMBER_TYPES.group, + title: __('Groups'), + attrs: { 'data-qa-selector': 'groups_list_tab' }, + }, + { + namespace: MEMBER_TYPES.invite, + title: __('Invited'), + canManageMembersPermissionsRequired: true, + }, + { + namespace: MEMBER_TYPES.accessRequest, + title: __('Access requests'), + canManageMembersPermissionsRequired: true, + }, + ], + urlParams: [], + components: { MembersApp, GlTabs, GlTab, GlBadge }, + inject: ['canManageMembers'], + data() { + return { + selectedTabIndex: 0, + }; + }, + computed: { + ...mapState({ + userCount(state) { + return countComputed(state, MEMBER_TYPES.user); + }, + groupCount(state) { + return countComputed(state, MEMBER_TYPES.group); + }, + inviteCount(state) { + return countComputed(state, MEMBER_TYPES.invite); + }, + accessRequestCount(state) { + return countComputed(state, MEMBER_TYPES.accessRequest); + }, + }), + urlParams() { + return Object.keys(urlParamsToObject(window.location.search)); + }, + activeTabIndexCalculatedFromUrlParams() { + return this.$options.tabs.findIndex(({ namespace }) => { + return this.getTabUrlParams(namespace).some((urlParam) => + this.urlParams.includes(urlParam), + ); + }); + }, + }, + created() { + if (this.activeTabIndexCalculatedFromUrlParams === -1) { + return; + } + + this.selectedTabIndex = this.activeTabIndexCalculatedFromUrlParams; + }, + methods: { + getTabUrlParams(namespace) { + const state = this.$store.state[namespace]; + const urlParams = []; + + if (state?.pagination?.paramName) { + urlParams.push(state.pagination.paramName); + } + + if (state?.filteredSearchBar?.searchParam) { + urlParams.push(state.filteredSearchBar.searchParam); + } + + return urlParams; + }, + getTabCount({ namespace }) { + return this[`${namespace}Count`]; + }, + showTab(tab, index) { + if (tab.namespace === MEMBER_TYPES.user) { + return true; + } + + const { canManageMembersPermissionsRequired = false } = tab; + const tabCanBeShown = + this.getTabCount(tab) > 0 || this.activeTabIndexCalculatedFromUrlParams === index; + + if (canManageMembersPermissionsRequired) { + return this.canManageMembers && tabCanBeShown; + } + + return tabCanBeShown; + }, + }, +}; +</script> + +<template> + <gl-tabs v-model="selectedTabIndex"> + <template v-for="(tab, index) in $options.tabs"> + <gl-tab v-if="showTab(tab, index)" :key="tab.namespace" :title-link-attributes="tab.attrs"> + <template slot="title"> + <span>{{ tab.title }}</span> + <gl-badge size="sm" class="gl-tab-counter-badge">{{ getTabCount(tab) }}</gl-badge> + </template> + <members-app :namespace="tab.namespace" /> + </gl-tab> + </template> + </gl-tabs> +</template> diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue index 236aeaef418..09ef98ec411 100644 --- a/app/assets/javascripts/members/components/table/members_table.vue +++ b/app/assets/javascripts/members/components/table/members_table.vue @@ -1,8 +1,9 @@ <script> -import { GlTable, GlBadge } from '@gitlab/ui'; +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 { canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members/utils'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; import initUserPopovers from '~/user_popovers'; import { FIELDS } from '../../constants'; import RemoveGroupLinkModal from '../modals/remove_group_link_modal.vue'; @@ -19,6 +20,7 @@ export default { components: { GlTable, GlBadge, + GlPagination, MemberAvatar, CreatedAt, ExpiresAt, @@ -43,6 +45,9 @@ export default { tableAttrs(state) { return state[this.namespace].tableAttrs; }, + pagination(state) { + return state[this.namespace].pagination; + }, }), filteredFields() { return FIELDS.filter( @@ -59,6 +64,11 @@ export default { userIsLoggedIn() { return this.currentUserId !== null; }, + showPagination() { + const { paramName, currentPage, perPage, totalItems } = this.pagination; + + return paramName && currentPage && perPage && totalItems; + }, }, mounted() { initUserPopovers(this.$el.querySelectorAll('.js-user-link')); @@ -99,6 +109,11 @@ export default { ...(member?.id && { 'data-testid': `members-table-row-${member.id}` }), }; }, + paginationLinkGenerator(page) { + const { params = {}, paramName } = this.pagination; + + return mergeUrlParams({ ...params, [paramName]: page }, window.location.href); + }, }, }; </script> @@ -119,6 +134,9 @@ export default { show-empty :tbody-tr-attr="tbodyTrAttr" > + <template #head()="{ label }"> + {{ label }} + </template> <template #cell(account)="{ item: member }"> <members-table-cell #default="{ memberType, isCurrentUser }" :member="member"> <member-avatar @@ -179,6 +197,18 @@ export default { <span data-testid="col-actions" class="gl-sr-only">{{ label }}</span> </template> </gl-table> + <gl-pagination + v-if="showPagination" + :value="pagination.currentPage" + :per-page="pagination.perPage" + :total-items="pagination.totalItems" + :link-gen="paginationLinkGenerator" + :prev-text="__('Prev')" + :next-text="__('Next')" + :label-next-page="__('Go to next page')" + :label-prev-page="__('Go to previous page')" + align="center" + /> <remove-group-link-modal /> <ldap-override-confirmation-modal /> </div> |