Commit b65a7c7a authored by Peter Hegman's avatar Peter Hegman Committed by Nick Thomas

Convert bootstrap tabs on group/members page to `GlTabs`

parent 8a6e9d96
...@@ -9,7 +9,17 @@ import MembersTable from './table/members_table.vue'; ...@@ -9,7 +9,17 @@ import MembersTable from './table/members_table.vue';
export default { export default {
name: 'MembersApp', name: 'MembersApp',
components: { MembersTable, FilterSortContainer, GlAlert }, components: { MembersTable, FilterSortContainer, GlAlert },
inject: ['namespace'], provide() {
return {
namespace: this.namespace,
};
},
props: {
namespace: {
type: String,
required: true,
},
},
computed: { computed: {
...mapState({ ...mapState({
showError(state) { showError(state) {
......
...@@ -2,20 +2,11 @@ import { GlToast } from '@gitlab/ui'; ...@@ -2,20 +2,11 @@ import { GlToast } from '@gitlab/ui';
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { parseDataAttributes } from '~/members/utils'; import { parseDataAttributes } from '~/members/utils';
import App from './components/app.vue'; import MembersTabs from './components/members_tabs.vue';
import { MEMBER_TYPES } from './constants';
import membersStore from './store'; import membersStore from './store';
export const initMembersApp = ( export const initMembersApp = (el, options) => {
el,
{
namespace,
tableFields = [],
tableAttrs = {},
tableSortableFields = [],
requestFormatter = () => {},
filteredSearchBar = { show: false },
},
) => {
if (!el) { if (!el) {
return () => {}; return () => {};
} }
...@@ -25,29 +16,45 @@ export const initMembersApp = ( ...@@ -25,29 +16,45 @@ export const initMembersApp = (
const { sourceId, canManageMembers, ...vuexStoreAttributes } = parseDataAttributes(el); const { sourceId, canManageMembers, ...vuexStoreAttributes } = parseDataAttributes(el);
const store = new Vuex.Store({ const modules = Object.keys(MEMBER_TYPES).reduce((accumulator, namespace) => {
modules: { const namespacedOptions = options[namespace];
if (!namespacedOptions) {
return accumulator;
}
const {
tableFields = [],
tableAttrs = {},
tableSortableFields = [],
requestFormatter = () => {},
filteredSearchBar = { show: false },
} = namespacedOptions;
return {
...accumulator,
[namespace]: membersStore({ [namespace]: membersStore({
...vuexStoreAttributes, ...vuexStoreAttributes[namespace],
tableFields, tableFields,
tableAttrs, tableAttrs,
tableSortableFields, tableSortableFields,
requestFormatter, requestFormatter,
filteredSearchBar, filteredSearchBar,
}), }),
}, };
}); }, {});
const store = new Vuex.Store({ modules });
return new Vue({ return new Vue({
el, el,
components: { App }, components: { MembersTabs },
store, store,
provide: { provide: {
namespace,
currentUserId: gon.current_user_id || null, currentUserId: gon.current_user_id || null,
sourceId, sourceId,
canManageMembers, canManageMembers,
}, },
render: (createElement) => createElement('app'), render: (createElement) => createElement('members-tabs'),
}); });
}; };
...@@ -29,46 +29,43 @@ function mountRemoveMemberModal() { ...@@ -29,46 +29,43 @@ function mountRemoveMemberModal() {
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-group-members-list'), { initMembersApp(document.querySelector('.js-group-members-list-app'), {
namespace: MEMBER_TYPES.user, [MEMBER_TYPES.user]: {
tableFields: SHARED_FIELDS.concat(['source', 'granted']), tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } }, tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'], tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
requestFormatter: groupMemberRequestFormatter, requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: { filteredSearchBar: {
show: true, show: true,
tokens: ['two_factor', 'with_inherited_permissions'], tokens: ['two_factor', 'with_inherited_permissions'],
searchParam: 'search', searchParam: 'search',
placeholder: s__('Members|Filter members'), placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'group_members', recentSearchesStorageKey: 'group_members',
},
}, },
}); [MEMBER_TYPES.group]: {
tableFields: SHARED_FIELDS.concat('granted'),
initMembersApp(document.querySelector('.js-group-group-links-list'), { tableAttrs: {
namespace: MEMBER_TYPES.group, table: { 'data-qa-selector': 'groups_list' },
tableFields: SHARED_FIELDS.concat('granted'), tr: { 'data-qa-selector': 'group_row' },
tableAttrs: { },
table: { 'data-qa-selector': 'groups_list' }, requestFormatter: groupLinkRequestFormatter,
tr: { 'data-qa-selector': 'group_row' },
}, },
requestFormatter: groupLinkRequestFormatter, [MEMBER_TYPES.invite]: {
}); tableFields: SHARED_FIELDS.concat('invited'),
initMembersApp(document.querySelector('.js-group-invited-members-list'), { requestFormatter: groupMemberRequestFormatter,
namespace: MEMBER_TYPES.invite, filteredSearchBar: {
tableFields: SHARED_FIELDS.concat('invited'), show: true,
requestFormatter: groupMemberRequestFormatter, tokens: [],
filteredSearchBar: { searchParam: 'search_invited',
show: true, placeholder: s__('Members|Search invited'),
tokens: [], recentSearchesStorageKey: 'group_invited_members',
searchParam: 'search_invited', },
placeholder: s__('Members|Search invited'), },
recentSearchesStorageKey: 'group_invited_members', [MEMBER_TYPES.accessRequest]: {
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: groupMemberRequestFormatter,
}, },
});
initMembersApp(document.querySelector('.js-group-access-requests-list'), {
namespace: MEMBER_TYPES.accessRequest,
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: groupMemberRequestFormatter,
}); });
groupsSelect(); groupsSelect();
......
...@@ -42,46 +42,41 @@ initInviteMembersForm(); ...@@ -42,46 +42,41 @@ initInviteMembersForm();
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-project-members-list'), { initMembersApp(document.querySelector('.js-project-members-list-app'), {
namespace: MEMBER_TYPES.user, [MEMBER_TYPES.user]: {
tableFields: SHARED_FIELDS.concat(['source', 'granted']), tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } }, tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'], tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
requestFormatter: projectMemberRequestFormatter, requestFormatter: projectMemberRequestFormatter,
filteredSearchBar: { filteredSearchBar: {
show: true, show: true,
tokens: ['with_inherited_permissions'], tokens: ['with_inherited_permissions'],
searchParam: 'search', searchParam: 'search',
placeholder: s__('Members|Filter members'), placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'project_members', recentSearchesStorageKey: 'project_members',
},
}, },
}); [MEMBER_TYPES.group]: {
tableFields: SHARED_FIELDS.concat('granted'),
initMembersApp(document.querySelector('.js-project-group-links-list'), { tableAttrs: {
namespace: MEMBER_TYPES.group, table: { 'data-qa-selector': 'groups_list' },
tableFields: SHARED_FIELDS.concat('granted'), tr: { 'data-qa-selector': 'group_row' },
tableAttrs: { },
table: { 'data-qa-selector': 'groups_list' }, requestFormatter: groupLinkRequestFormatter,
tr: { 'data-qa-selector': 'group_row' }, filteredSearchBar: {
show: true,
tokens: [],
searchParam: 'search_groups',
placeholder: s__('Members|Search groups'),
recentSearchesStorageKey: 'project_group_links',
},
}, },
requestFormatter: groupLinkRequestFormatter, [MEMBER_TYPES.invite]: {
filteredSearchBar: { tableFields: SHARED_FIELDS.concat('invited'),
show: true, requestFormatter: projectMemberRequestFormatter,
tokens: [], },
searchParam: 'search_groups', [MEMBER_TYPES.accessRequest]: {
placeholder: s__('Members|Search groups'), tableFields: SHARED_FIELDS.concat('requested'),
recentSearchesStorageKey: 'project_group_links', requestFormatter: projectMemberRequestFormatter,
}, },
});
initMembersApp(document.querySelector('.js-project-invited-members-list'), {
namespace: MEMBER_TYPES.invite,
tableFields: SHARED_FIELDS.concat('invited'),
requestFormatter: projectMemberRequestFormatter,
});
initMembersApp(document.querySelector('.js-project-access-requests-list'), {
namespace: MEMBER_TYPES.accessRequest,
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: projectMemberRequestFormatter,
}); });
...@@ -13,12 +13,15 @@ module Groups::GroupMembersHelper ...@@ -13,12 +13,15 @@ module Groups::GroupMembersHelper
render 'shared/members/invite_member', submit_url: group_group_members_path(group), access_levels: group.access_level_roles, default_access_level: default_access_level render 'shared/members/invite_member', submit_url: group_group_members_path(group), access_levels: group.access_level_roles, default_access_level: default_access_level
end end
def group_members_list_data_json(group, members, pagination = {}) def group_members_app_data_json(group, members:, invited:, access_requests:)
group_members_list_data(group, members, pagination).to_json {
end user: group_members_list_data(group, members, { param_name: :page, params: { invited_members_page: nil, search_invited: nil } }),
group: group_group_links_list_data(group),
def group_group_links_list_data_json(group) invite: group_members_list_data(group, invited.nil? ? [] : invited, { param_name: :invited_members_page, params: { page: nil } }),
group_group_links_list_data(group).to_json access_request: group_members_list_data(group, access_requests.nil? ? [] : access_requests),
source_id: group.id,
can_manage_members: can?(current_user, :admin_group_member, group)
}.to_json
end end
private private
...@@ -32,13 +35,11 @@ module Groups::GroupMembersHelper ...@@ -32,13 +35,11 @@ module Groups::GroupMembersHelper
end end
# Overridden in `ee/app/helpers/ee/groups/group_members_helper.rb` # Overridden in `ee/app/helpers/ee/groups/group_members_helper.rb`
def group_members_list_data(group, members, pagination) def group_members_list_data(group, members, pagination = {})
{ {
members: group_members_serialized(group, members), members: group_members_serialized(group, members),
pagination: members_pagination_data(members, pagination), pagination: members_pagination_data(members, pagination),
member_path: group_group_member_path(group, ':id'), member_path: group_group_member_path(group, ':id')
source_id: group.id,
can_manage_members: can?(current_user, :admin_group_member, group)
} }
end end
...@@ -48,8 +49,7 @@ module Groups::GroupMembersHelper ...@@ -48,8 +49,7 @@ module Groups::GroupMembersHelper
{ {
members: group_group_links_serialized(group_links), members: group_group_links_serialized(group_links),
pagination: members_pagination_data(group_links), pagination: members_pagination_data(group_links),
member_path: group_group_link_path(group, ':id'), member_path: group_group_link_path(group, ':id')
source_id: group.id
} }
end end
end end
......
...@@ -27,12 +27,15 @@ module Projects::ProjectMembersHelper ...@@ -27,12 +27,15 @@ module Projects::ProjectMembersHelper
project.group.has_owner?(current_user) project.group.has_owner?(current_user)
end end
def project_members_list_data_json(project, members, pagination = {}) def project_members_app_data_json(project, members:, group_links:, invited:, access_requests:)
project_members_list_data(project, members, pagination).to_json {
end user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }),
group: project_group_links_list_data(project, group_links),
def project_group_links_list_data_json(project, group_links) invite: project_members_list_data(project, invited.nil? ? [] : invited),
project_group_links_list_data(project, group_links).to_json access_request: project_members_list_data(project, access_requests.nil? ? [] : access_requests),
source_id: project.id,
can_manage_members: can_manage_project_members?(project)
}.to_json
end end
private private
...@@ -45,13 +48,11 @@ module Projects::ProjectMembersHelper ...@@ -45,13 +48,11 @@ module Projects::ProjectMembersHelper
GroupLink::ProjectGroupLinkSerializer.new.represent(group_links, { current_user: current_user }) GroupLink::ProjectGroupLinkSerializer.new.represent(group_links, { current_user: current_user })
end end
def project_members_list_data(project, members, pagination) def project_members_list_data(project, members, pagination = {})
{ {
members: project_members_serialized(project, members), members: project_members_serialized(project, members),
pagination: members_pagination_data(members, pagination), pagination: members_pagination_data(members, pagination),
member_path: project_project_member_path(project, ':id'), member_path: project_project_member_path(project, ':id')
source_id: project.id,
can_manage_members: can_manage_project_members?(project)
} }
end end
...@@ -59,9 +60,7 @@ module Projects::ProjectMembersHelper ...@@ -59,9 +60,7 @@ module Projects::ProjectMembersHelper
{ {
members: project_group_links_serialized(group_links), members: project_group_links_serialized(group_links),
pagination: members_pagination_data(group_links), pagination: members_pagination_data(group_links),
member_path: project_group_link_path(project, ':id'), member_path: project_group_link_path(project, ':id')
source_id: project.id,
can_manage_members: can_manage_project_members?(project)
} }
end end
end end
- add_page_specific_style 'page_bundles/members' - add_page_specific_style 'page_bundles/members'
- page_title _('Group members') - page_title _('Group members')
- show_invited_members = can_manage_members? && @invited_members.load.any?
- show_access_requests = can_manage_members? && @requesters.load.any?
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
.js-remove-member-modal .js-remove-member-modal
.row.gl-mt-3 .row.gl-mt-3
...@@ -35,47 +32,9 @@ ...@@ -35,47 +32,9 @@
= render_if_exists 'groups/group_members/ldap_sync' = render_if_exists 'groups/group_members/ldap_sync'
%ul.nav-links.mobile-separator.nav.nav-tabs .js-group-members-list-app{ data: { members_data: group_members_app_data_json(@group,
%li.nav-item members: @members,
= link_to '#tab-members', class: ['nav-link', ('active' unless invited_active)], data: { toggle: 'tab' } do invited: @invited_members,
%span access_requests: @requesters) } }
= _('Members') .loading
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @members.total_count .gl-spinner.gl-spinner-md
- if @group.shared_with_group_links.present?
%li.nav-item
= link_to '#tab-groups', class: ['nav-link'] , data: { toggle: 'tab', qa_selector: 'groups_list_tab' } do
%span
= _('Groups')
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @group.shared_with_group_links.count
- if show_invited_members
%li.nav-item
= link_to '#tab-invited-members', class: ['nav-link', ('active' if invited_active)], data: { toggle: 'tab' } do
%span
= _('Invited')
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @invited_members.total_count
- if show_access_requests
%li.nav-item
= link_to '#tab-access-requests', class: 'nav-link', data: { toggle: 'tab' } do
%span
= _('Access requests')
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @requesters.count
.tab-content
#tab-members.tab-pane{ class: ('active' unless invited_active) }
.js-group-members-list{ data: { members_data: group_members_list_data_json(@group, @members, { param_name: :page, params: { invited_members_page: nil, search_invited: nil } }) } }
.loading
.gl-spinner.gl-spinner-md
- if @group.shared_with_group_links.present?
#tab-groups.tab-pane
.js-group-group-links-list{ data: { members_data: group_group_links_list_data_json(@group) } }
.loading
.gl-spinner.gl-spinner-md
- if show_invited_members
#tab-invited-members.tab-pane{ class: ('active' if invited_active) }
.js-group-invited-members-list{ data: { members_data: group_members_list_data_json(@group, @invited_members, { param_name: :invited_members_page, params: { page: nil } }) } }
.loading
.gl-spinner.gl-spinner-md
- if show_access_requests
#tab-access-requests.tab-pane
.js-group-access-requests-list{ data: { members_data: group_members_list_data_json(@group, @requesters) } }
.loading
.gl-spinner.gl-spinner-md
...@@ -56,47 +56,10 @@ ...@@ -56,47 +56,10 @@
.invite-member= render 'shared/members/invite_member', submit_url: project_project_members_path(@project), access_levels: ProjectMember.access_level_roles, default_access_level: @project_member.access_level, can_import_members?: can_import_members?, import_path: import_project_project_members_path(@project) .invite-member= render 'shared/members/invite_member', submit_url: project_project_members_path(@project), access_levels: ProjectMember.access_level_roles, default_access_level: @project_member.access_level, can_import_members?: can_import_members?, import_path: import_project_project_members_path(@project)
- elsif @project.allowed_to_share_with_group? - elsif @project.allowed_to_share_with_group?
.invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access' .invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access'
%ul.nav-links.mobile-separator.nav.nav-tabs .js-project-members-list-app{ data: { members_data: project_members_app_data_json(@project,
%li.nav-item members: @project_members,
= link_to '#tab-members', class: ['nav-link', ('active' unless groups_tab_active?)], data: { toggle: 'tab' } do group_links: @group_links,
%span invited: @invited_members,
= _('Members') access_requests: @requesters) } }
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @project_members.total_count .loading
- if show_groups?(@group_links) .gl-spinner.gl-spinner-md
%li.nav-item
= link_to '#tab-groups', class: ['nav-link', ('active' if groups_tab_active?)] , data: { toggle: 'tab', qa_selector: 'groups_list_tab' } do
%span
= _('Groups')
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @group_links.count
- if show_invited_members?(@project, @invited_members)
%li.nav-item
= link_to '#tab-invited-members', class: 'nav-link', data: { toggle: 'tab' } do
%span
= _('Invited')
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @invited_members.count
- if show_access_requests?(@project, @requesters)
%li.nav-item
= link_to '#tab-access-requests', class: 'nav-link', data: { toggle: 'tab' } do
%span
= _('Access requests')
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @requesters.count
.tab-content
#tab-members.tab-pane{ class: ('active' unless groups_tab_active?) }
.js-project-members-list{ data: { members_data: project_members_list_data_json(@project, @project_members, { param_name: :page, params: { search_groups: nil } }) } }
.loading
.gl-spinner.gl-spinner-md
- if show_groups?(@group_links)
#tab-groups.tab-pane{ class: ('active' if groups_tab_active?) }
.js-project-group-links-list{ data: { members_data: project_group_links_list_data_json(@project, @group_links) } }
.loading
.gl-spinner.gl-spinner-md
- if show_invited_members?(@project, @invited_members)
#tab-invited-members.tab-pane
.js-project-invited-members-list{ data: { members_data: project_members_list_data_json(@project, @invited_members) } }
.loading
.gl-spinner.gl-spinner-md
- if show_access_requests?(@project, @requesters)
#tab-access-requests.tab-pane
.js-project-access-requests-list{ data: { members_data: project_members_list_data_json(@project, @requesters) } }
.loading
.gl-spinner.gl-spinner-md
---
title: Update group/project member tabs to comply with Pajamas design system
merge_request:
author:
type: other
...@@ -8,7 +8,7 @@ describe('initMembersApp', () => { ...@@ -8,7 +8,7 @@ describe('initMembersApp', () => {
const createVm = () => { const createVm = () => {
vm = initMembersApp(el, { vm = initMembersApp(el, {
namespace: MEMBER_TYPES.user, [MEMBER_TYPES.user]: {},
}); });
}; };
......
...@@ -22,8 +22,17 @@ RSpec.describe Groups::GroupMembersHelper do ...@@ -22,8 +22,17 @@ RSpec.describe Groups::GroupMembersHelper do
end end
end end
describe '#group_members_list_data_json' do describe '#group_members_app_data_json' do
subject { Gitlab::Json.parse(helper.group_members_list_data_json(group, [])) } subject do
Gitlab::Json.parse(
helper.group_members_app_data_json(
group,
members: [],
invited: [],
access_requests: []
)
)
end
before do before do
allow(helper).to receive(:override_group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id/override') allow(helper).to receive(:override_group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id/override')
...@@ -32,7 +41,7 @@ RSpec.describe Groups::GroupMembersHelper do ...@@ -32,7 +41,7 @@ RSpec.describe Groups::GroupMembersHelper do
end end
it 'adds `ldap_override_path` to returned json' do it 'adds `ldap_override_path` to returned json' do
expect(subject['ldap_override_path']).to eq('/groups/foo-bar/-/group_members/:id/override') expect(subject['user']['ldap_override_path']).to eq('/groups/foo-bar/-/group_members/:id/override')
end end
end end
end end
...@@ -26,7 +26,7 @@ module QA ...@@ -26,7 +26,7 @@ module QA
element :delete_member_button element :delete_member_button
end end
view 'app/views/groups/group_members/index.html.haml' do view 'app/assets/javascripts/members/components/members_tabs.vue' do
element :groups_list_tab element :groups_list_tab
end end
......
...@@ -6,7 +6,7 @@ module QA ...@@ -6,7 +6,7 @@ module QA
class Members < Page::Base class Members < Page::Base
include QA::Page::Component::InviteMembersModal include QA::Page::Component::InviteMembersModal
view 'app/views/projects/project_members/index.html.haml' do view 'app/assets/javascripts/members/components/members_tabs.vue' do
element :groups_list_tab element :groups_list_tab
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Groups > Members > Tabs' do RSpec.describe 'Groups > Members > Tabs', :js do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
shared_examples 'active "Members" tab' do shared_examples 'active "Members" tab' do
...@@ -56,7 +56,7 @@ RSpec.describe 'Groups > Members > Tabs' do ...@@ -56,7 +56,7 @@ RSpec.describe 'Groups > Members > Tabs' do
it_behaves_like 'active "Members" tab' it_behaves_like 'active "Members" tab'
end end
context 'when searching "Invited"', :js do context 'when searching "Invited"' do
before do before do
visit group_group_members_path(group) visit group_group_members_path(group)
...@@ -86,7 +86,7 @@ RSpec.describe 'Groups > Members > Tabs' do ...@@ -86,7 +86,7 @@ RSpec.describe 'Groups > Members > Tabs' do
end end
end end
context 'when using "Invited" pagination', :js do context 'when using "Invited" pagination' do
before do before do
visit group_group_members_path(group) visit group_group_members_path(group)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Projects > Members > Tabs' do RSpec.describe 'Projects > Members > Tabs', :js do
include Spec::Support::Helpers::Features::MembersHelpers include Spec::Support::Helpers::Features::MembersHelpers
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
...@@ -44,7 +44,7 @@ RSpec.describe 'Projects > Members > Tabs' do ...@@ -44,7 +44,7 @@ RSpec.describe 'Projects > Members > Tabs' do
end end
end end
context 'when searching "Groups"', :js do context 'when searching "Groups"' do
before do before do
click_link 'Groups' click_link 'Groups'
......
...@@ -33,7 +33,7 @@ describe('MembersApp', () => { ...@@ -33,7 +33,7 @@ describe('MembersApp', () => {
wrapper = shallowMount(MembersApp, { wrapper = shallowMount(MembersApp, {
localVue, localVue,
provide: { propsData: {
namespace: MEMBER_TYPES.user, namespace: MEMBER_TYPES.user,
}, },
store, store,
......
...@@ -6,7 +6,7 @@ import MembersTabs from '~/members/components/members_tabs.vue'; ...@@ -6,7 +6,7 @@ import MembersTabs from '~/members/components/members_tabs.vue';
import { MEMBER_TYPES } from '~/members/constants'; import { MEMBER_TYPES } from '~/members/constants';
import { pagination } from '../mock_data'; import { pagination } from '../mock_data';
describe('MembersApp', () => { describe('MembersTabs', () => {
Vue.use(Vuex); Vue.use(Vuex);
let wrapper; let wrapper;
...@@ -111,10 +111,10 @@ describe('MembersApp', () => { ...@@ -111,10 +111,10 @@ describe('MembersApp', () => {
const membersApps = wrapper.findAllComponents(MembersApp).wrappers; const membersApps = wrapper.findAllComponents(MembersApp).wrappers;
expect(membersApps[0].attributes('namespace')).toBe(MEMBER_TYPES.user); expect(membersApps[0].props('namespace')).toBe(MEMBER_TYPES.user);
expect(membersApps[1].attributes('namespace')).toBe(MEMBER_TYPES.group); expect(membersApps[1].props('namespace')).toBe(MEMBER_TYPES.group);
expect(membersApps[2].attributes('namespace')).toBe(MEMBER_TYPES.invite); expect(membersApps[2].props('namespace')).toBe(MEMBER_TYPES.invite);
expect(membersApps[3].attributes('namespace')).toBe(MEMBER_TYPES.accessRequest); expect(membersApps[3].props('namespace')).toBe(MEMBER_TYPES.accessRequest);
}); });
}); });
......
import { createWrapper } from '@vue/test-utils'; import { createWrapper } from '@vue/test-utils';
import MembersApp from '~/members/components/app.vue'; import MembersTabs from '~/members/components/members_tabs.vue';
import { MEMBER_TYPES } from '~/members/constants'; import { MEMBER_TYPES } from '~/members/constants';
import { initMembersApp } from '~/members/index'; import { initMembersApp } from '~/members/index';
import { members, pagination, dataAttribute } from './mock_data'; import { members, pagination, dataAttribute } from './mock_data';
...@@ -11,12 +11,13 @@ describe('initMembersApp', () => { ...@@ -11,12 +11,13 @@ describe('initMembersApp', () => {
const setup = () => { const setup = () => {
vm = initMembersApp(el, { vm = initMembersApp(el, {
namespace: MEMBER_TYPES.user, [MEMBER_TYPES.user]: {
tableFields: ['account'], tableFields: ['account'],
tableAttrs: { table: { 'data-qa-selector': 'members_list' } }, tableAttrs: { table: { 'data-qa-selector': 'members_list' } },
tableSortableFields: ['account'], tableSortableFields: ['account'],
requestFormatter: () => ({}), requestFormatter: () => ({}),
filteredSearchBar: { show: false }, filteredSearchBar: { show: false },
},
}); });
wrapper = createWrapper(vm); wrapper = createWrapper(vm);
}; };
...@@ -35,10 +36,10 @@ describe('initMembersApp', () => { ...@@ -35,10 +36,10 @@ describe('initMembersApp', () => {
wrapper = null; wrapper = null;
}); });
it('renders `MembersApp`', () => { it('renders `MembersTabs`', () => {
setup(); setup();
expect(wrapper.find(MembersApp).exists()).toBe(true); expect(wrapper.find(MembersTabs).exists()).toBe(true);
}); });
it('parses and sets `members` in Vuex store', () => { it('parses and sets `members` in Vuex store', () => {
......
import { MEMBER_TYPES } from '~/members/constants';
export const member = { export const member = {
requestedAt: null, requestedAt: null,
canUpdate: false, canUpdate: false,
...@@ -97,10 +99,12 @@ export const pagination = { ...@@ -97,10 +99,12 @@ export const pagination = {
}; };
export const dataAttribute = JSON.stringify({ export const dataAttribute = JSON.stringify({
members, [MEMBER_TYPES.user]: {
pagination: paginationData, members,
pagination: paginationData,
member_path: '/groups/foo-bar/-/group_members/:id',
ldap_override_path: '/groups/ldap-group/-/group_members/:id/override',
},
source_id: 234, source_id: 234,
can_manage_members: true, can_manage_members: true,
member_path: '/groups/foo-bar/-/group_members/:id',
ldap_override_path: '/groups/ldap-group/-/group_members/:id/override',
}); });
import { DEFAULT_SORT } from '~/members/constants'; import { DEFAULT_SORT, MEMBER_TYPES } from '~/members/constants';
import { import {
generateBadges, generateBadges,
isGroup, isGroup,
...@@ -268,11 +268,13 @@ describe('Members Utils', () => { ...@@ -268,11 +268,13 @@ describe('Members Utils', () => {
it('correctly parses the data attribute', () => { it('correctly parses the data attribute', () => {
expect(parseDataAttributes(el)).toMatchObject({ expect(parseDataAttributes(el)).toMatchObject({
members, [MEMBER_TYPES.user]: {
pagination, members,
pagination,
memberPath: '/groups/foo-bar/-/group_members/:id',
},
sourceId: 234, sourceId: 234,
canManageMembers: true, canManageMembers: true,
memberPath: '/groups/foo-bar/-/group_members/:id',
}); });
}); });
}); });
......
...@@ -23,58 +23,79 @@ RSpec.describe Groups::GroupMembersHelper do ...@@ -23,58 +23,79 @@ RSpec.describe Groups::GroupMembersHelper do
end end
end end
describe '#group_members_list_data_json' do describe '#group_members_app_data_json' do
let(:group_members) { create_list(:group_member, 2, group: group, created_by: current_user) } include_context 'group_group_link'
let(:pagination) { {} }
let(:collection) { group_members }
let(:presented_members) { present_members(collection) }
subject { Gitlab::Json.parse(helper.group_members_list_data_json(group, presented_members, pagination)) } let(:members) { create_list(:group_member, 2, group: shared_group, created_by: current_user) }
let(:invited) { create_list(:group_member, 2, :invited, group: shared_group, created_by: current_user) }
let!(:access_requests) { create_list(:group_member, 2, :access_request, group: shared_group, created_by: current_user) }
let(:members_collection) { members }
subject do
Gitlab::Json.parse(
helper.group_members_app_data_json(
shared_group,
members: present_members(members_collection),
invited: present_members(invited),
access_requests: present_members(access_requests)
)
)
end
shared_examples 'members.json' do shared_examples 'members.json' do |member_type|
it 'returns `members` property that matches json schema' do it 'returns `members` property that matches json schema' do
expect(subject['members'].to_json).to match_schema('members') expect(subject[member_type]['members'].to_json).to match_schema('members')
end
it 'sets `member_path` property' do
expect(subject[member_type]['member_path']).to eq('/groups/foo-bar/-/group_members/:id')
end end
end end
before do before do
allow(helper).to receive(:group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id') allow(helper).to receive(:group_group_member_path).with(shared_group, ':id').and_return('/groups/foo-bar/-/group_members/:id')
allow(helper).to receive(:can?).with(current_user, :admin_group_member, group).and_return(true) allow(helper).to receive(:group_group_link_path).with(shared_group, ':id').and_return('/groups/foo-bar/-/group_links/:id')
allow(helper).to receive(:can?).with(current_user, :admin_group_member, shared_group).and_return(true)
end end
it 'returns expected json' do it 'returns expected json' do
expected = { expected = {
member_path: '/groups/foo-bar/-/group_members/:id', source_id: shared_group.id,
source_id: group.id,
can_manage_members: true can_manage_members: true
}.as_json }.as_json
expect(subject).to include(expected) expect(subject).to include(expected)
end end
context 'for a group member' do context 'group members' do
it_behaves_like 'members.json' it_behaves_like 'members.json', 'user'
context 'with user status set' do context 'with user status set' do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:status) { create(:user_status, user: user) } let!(:status) { create(:user_status, user: user) }
let(:group_members) { [create(:group_member, group: group, user: user, created_by: current_user)] } let(:members) { [create(:group_member, group: shared_group, user: user, created_by: current_user)] }
it_behaves_like 'members.json' it_behaves_like 'members.json', 'user'
end end
end end
context 'for an invited group member' do context 'invited group members' do
let(:group_members) { create_list(:group_member, 2, :invited, group: group, created_by: current_user) } it_behaves_like 'members.json', 'invite'
end
it_behaves_like 'members.json' context 'access requests' do
it_behaves_like 'members.json', 'access_request'
end end
context 'for an access request' do context 'group links' do
let(:group_members) { create_list(:group_member, 2, :access_request, group: group, created_by: current_user) } it 'sets `group.members` property that matches json schema' do
expect(subject['group']['members'].to_json).to match_schema('group_link/group_group_links')
end
it_behaves_like 'members.json' it 'sets `member_path` property' do
expect(subject['group']['member_path']).to eq('/groups/foo-bar/-/group_links/:id')
end
end end
context 'when pagination is not available' do context 'when pagination is not available' do
...@@ -87,13 +108,12 @@ RSpec.describe Groups::GroupMembersHelper do ...@@ -87,13 +108,12 @@ RSpec.describe Groups::GroupMembersHelper do
params: {} params: {}
}.as_json }.as_json
expect(subject['pagination']).to include(expected) expect(subject['access_request']['pagination']).to include(expected)
end end
end end
context 'when pagination is available' do context 'when pagination is available' do
let(:collection) { Kaminari.paginate_array(group_members).page(1).per(1) } let(:members_collection) { Kaminari.paginate_array(members).page(1).per(1) }
let(:pagination) { { param_name: :page, params: { search_groups: nil } } }
it 'sets `pagination` attribute to expected json' do it 'sets `pagination` attribute to expected json' do
expected = { expected = {
...@@ -101,41 +121,11 @@ RSpec.describe Groups::GroupMembersHelper do ...@@ -101,41 +121,11 @@ RSpec.describe Groups::GroupMembersHelper do
per_page: 1, per_page: 1,
total_items: 2, total_items: 2,
param_name: :page, param_name: :page,
params: { search_groups: nil } params: { invited_members_page: nil, search_invited: nil }
}.as_json }.as_json
expect(subject['pagination']).to include(expected) expect(subject['user']['pagination']).to include(expected)
end end
end end
end end
describe '#group_group_links_list_data_json' do
include_context 'group_group_link'
subject { Gitlab::Json.parse(helper.group_group_links_list_data_json(shared_group)) }
before do
allow(helper).to receive(:group_group_link_path).with(shared_group, ':id').and_return('/groups/foo-bar/-/group_links/:id')
end
it 'returns expected json' do
expected = {
pagination: {
current_page: nil,
per_page: nil,
total_items: 1,
param_name: nil,
params: {}
},
member_path: '/groups/foo-bar/-/group_links/:id',
source_id: shared_group.id
}.as_json
expect(subject).to include(expected)
end
it 'returns `members` property that matches json schema' do
expect(subject['members'].to_json).to match_schema('group_link/group_group_links')
end
end
end end
...@@ -147,16 +147,27 @@ RSpec.describe Projects::ProjectMembersHelper do ...@@ -147,16 +147,27 @@ RSpec.describe Projects::ProjectMembersHelper do
end end
describe 'project members' do describe 'project members' do
let_it_be(:project_members) { create_list(:project_member, 2, project: project) } let_it_be(:members) { create_list(:project_member, 2, project: project) }
let_it_be(:group_links) { create_list(:project_group_link, 1, project: project) }
let_it_be(:invited) { create_list(:project_member, 2, :invited, project: project) }
let_it_be(:access_requests) { create_list(:project_member, 2, :access_request, project: project) }
let(:collection) { project_members } let(:members_collection) { members }
let(:presented_members) { present_members(collection) }
describe '#project_members_list_data_json' do describe '#project_members_app_data_json' do
let(:allow_admin_project) { true } let(:allow_admin_project) { true }
let(:pagination) { {} }
subject { Gitlab::Json.parse(helper.project_members_list_data_json(project, presented_members, pagination)) } subject do
Gitlab::Json.parse(
helper.project_members_app_data_json(
project,
members: present_members(members_collection),
group_links: group_links,
invited: present_members(invited),
access_requests: present_members(access_requests)
)
)
end
before do before do
allow(helper).to receive(:project_project_member_path).with(project, ':id').and_return('/foo-bar/-/project_members/:id') allow(helper).to receive(:project_project_member_path).with(project, ':id').and_return('/foo-bar/-/project_members/:id')
...@@ -164,7 +175,6 @@ RSpec.describe Projects::ProjectMembersHelper do ...@@ -164,7 +175,6 @@ RSpec.describe Projects::ProjectMembersHelper do
it 'returns expected json' do it 'returns expected json' do
expected = { expected = {
member_path: '/foo-bar/-/project_members/:id',
source_id: project.id, source_id: project.id,
can_manage_members: true can_manage_members: true
}.as_json }.as_json
...@@ -172,8 +182,12 @@ RSpec.describe Projects::ProjectMembersHelper do ...@@ -172,8 +182,12 @@ RSpec.describe Projects::ProjectMembersHelper do
expect(subject).to include(expected) expect(subject).to include(expected)
end end
it 'returns `members` property that matches json schema' do it 'sets `members` property that matches json schema' do
expect(subject['members'].to_json).to match_schema('members') expect(subject['user']['members'].to_json).to match_schema('members')
end
it 'sets `member_path` property' do
expect(subject['user']['member_path']).to eq('/foo-bar/-/project_members/:id')
end end
context 'when pagination is not available' do context 'when pagination is not available' do
...@@ -186,13 +200,12 @@ RSpec.describe Projects::ProjectMembersHelper do ...@@ -186,13 +200,12 @@ RSpec.describe Projects::ProjectMembersHelper do
params: {} params: {}
}.as_json }.as_json
expect(subject['pagination']).to include(expected) expect(subject['invite']['pagination']).to include(expected)
end end
end end
context 'when pagination is available' do context 'when pagination is available' do
let(:collection) { Kaminari.paginate_array(project_members).page(1).per(1) } let(:members_collection) { Kaminari.paginate_array(members).page(1).per(1) }
let(:pagination) { { param_name: :page, params: { search_groups: nil } } }
it 'sets `pagination` attribute to expected json' do it 'sets `pagination` attribute to expected json' do
expected = { expected = {
...@@ -203,45 +216,9 @@ RSpec.describe Projects::ProjectMembersHelper do ...@@ -203,45 +216,9 @@ RSpec.describe Projects::ProjectMembersHelper do
params: { search_groups: nil } params: { search_groups: nil }
}.as_json }.as_json
expect(subject['pagination']).to match(expected) expect(subject['user']['pagination']).to match(expected)
end end
end end
end end
end end
describe 'project group links' do
let_it_be(:project_group_links) { create_list(:project_group_link, 1, project: project) }
let(:allow_admin_project) { true }
describe '#project_group_links_list_data_json' do
subject { Gitlab::Json.parse(helper.project_group_links_list_data_json(project, project_group_links)) }
before do
allow(helper).to receive(:project_group_link_path).with(project, ':id').and_return('/foo-bar/-/group_links/:id')
allow(helper).to receive(:can?).with(current_user, :admin_project_member, project).and_return(true)
end
it 'returns expected json' do
expected = {
pagination: {
current_page: nil,
per_page: nil,
total_items: 1,
param_name: nil,
params: {}
},
member_path: '/foo-bar/-/group_links/:id',
source_id: project.id,
can_manage_members: true
}.as_json
expect(subject).to include(expected)
end
it 'returns `members` property that matches json schema' do
expect(subject['members'].to_json).to match_schema('group_link/project_group_links')
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment