Commit fdd0423d authored by Abdul Wadood's avatar Abdul Wadood Committed by Mayra Cabrera

Hide ancestor groups in group invites

While inviting groups to a project show only the groups
where the current user is a member.
To return only the groups where the authenticated user
is a member an optional boolean parameter 'membership_groups`
has been added to the groups.json API.

Changelog: added
parent 053b7006
...@@ -28,6 +28,7 @@ const groupsSelect = () => { ...@@ -28,6 +28,7 @@ const groupsSelect = () => {
const skipGroups = $select.data('skipGroups') || []; const skipGroups = $select.data('skipGroups') || [];
const parentGroupID = $select.data('parentId'); const parentGroupID = $select.data('parentId');
const groupsFilter = $select.data('groupsFilter'); const groupsFilter = $select.data('groupsFilter');
const minAccessLevel = $select.data('minAccessLevel');
$select.select2({ $select.select2({
placeholder: __('Search for a group'), placeholder: __('Search for a group'),
...@@ -45,6 +46,7 @@ const groupsSelect = () => { ...@@ -45,6 +46,7 @@ const groupsSelect = () => {
page, page,
per_page: window.GROUP_SELECT_PER_PAGE, per_page: window.GROUP_SELECT_PER_PAGE,
all_available: allAvailable, all_available: allAvailable,
min_access_level: minAccessLevel,
}; };
}, },
results(data, page) { results(data, page) {
......
...@@ -24,6 +24,10 @@ export default { ...@@ -24,6 +24,10 @@ export default {
prop: 'selectedGroup', prop: 'selectedGroup',
}, },
props: { props: {
accessLevels: {
type: Object,
required: true,
},
groupsFilter: { groupsFilter: {
type: String, type: String,
required: false, required: false,
...@@ -50,6 +54,13 @@ export default { ...@@ -50,6 +54,13 @@ export default {
isFetchResultEmpty() { isFetchResultEmpty() {
return this.groups.length === 0; return this.groups.length === 0;
}, },
defaultFetchOptions() {
return {
exclude_internal: true,
active: true,
min_access_level: this.accessLevels.Guest,
};
},
}, },
watch: { watch: {
searchTerm() { searchTerm() {
...@@ -84,13 +95,9 @@ export default { ...@@ -84,13 +95,9 @@ export default {
fetchGroups() { fetchGroups() {
switch (this.groupsFilter) { switch (this.groupsFilter) {
case GROUP_FILTERS.DESCENDANT_GROUPS: case GROUP_FILTERS.DESCENDANT_GROUPS:
return getDescendentGroups( return getDescendentGroups(this.parentGroupId, this.searchTerm, this.defaultFetchOptions);
this.parentGroupId,
this.searchTerm,
this.$options.defaultFetchOptions,
);
default: default:
return getGroups(this.searchTerm, this.$options.defaultFetchOptions); return getGroups(this.searchTerm, this.defaultFetchOptions);
} }
}, },
}, },
...@@ -99,10 +106,6 @@ export default { ...@@ -99,10 +106,6 @@ export default {
searchPlaceholder: s__('GroupSelect|Search groups'), searchPlaceholder: s__('GroupSelect|Search groups'),
emptySearchResult: s__('GroupSelect|No matching results'), emptySearchResult: s__('GroupSelect|No matching results'),
}, },
defaultFetchOptions: {
exclude_internal: true,
active: true,
},
}; };
</script> </script>
<template> <template>
......
...@@ -428,6 +428,7 @@ export default { ...@@ -428,6 +428,7 @@ export default {
<group-select <group-select
v-if="isInviteGroup" v-if="isInviteGroup"
v-model="groupToBeSharedWith" v-model="groupToBeSharedWith"
:access-levels="accessLevels"
:groups-filter="groupSelectFilter" :groups-filter="groupSelectFilter"
:parent-group-id="groupSelectParentId" :parent-group-id="groupSelectParentId"
@input="handleMembersTokenSelectClear" @input="handleMembersTokenSelectClear"
......
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
default_access_level: ProjectGroupLink.default_access, default_access_level: ProjectGroupLink.default_access,
group_link_field: 'link_group_id', group_link_field: 'link_group_id',
group_access_field: 'link_group_access', group_access_field: 'link_group_access',
groups_select_tag_data: { skip_groups: @skip_groups } groups_select_tag_data: { min_access_level: Gitlab::Access::GUEST, skip_groups: @skip_groups }
- elsif !membership_locked? - elsif !membership_locked?
.invite-member .invite-member
= render 'shared/members/invite_member', = render 'shared/members/invite_member',
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
submit_url: project_group_links_path(@project), submit_url: project_group_links_path(@project),
group_link_field: 'link_group_id', group_link_field: 'link_group_id',
group_access_field: 'link_group_access', group_access_field: 'link_group_access',
groups_select_tag_data: { skip_groups: @skip_groups } groups_select_tag_data: { min_access_level: Gitlab::Access::GUEST, skip_groups: @skip_groups }
.js-project-members-list-app{ data: { members_data: project_members_app_data_json(@project, .js-project-members-list-app{ data: { members_data: project_members_app_data_json(@project,
members: @project_members, members: @project_members,
group_links: @group_links, group_links: @group_links,
......
...@@ -214,31 +214,88 @@ RSpec.describe 'Project > Members > Invite group', :js do ...@@ -214,31 +214,88 @@ RSpec.describe 'Project > Members > Invite group', :js do
end end
context 'for a project in a nested group' do context 'for a project in a nested group' do
let(:group) { create(:group) } let!(:parent_group) { create(:group, :public) }
let!(:nested_group) { create(:group, parent: group) } let!(:public_subgroup) { create(:group, :public, parent: parent_group) }
let!(:group_to_share_with) { create(:group) } let!(:public_sub_subgroup) { create(:group, :public, parent: public_subgroup) }
let!(:project) { create(:project, namespace: nested_group) } let!(:private_subgroup) { create(:group, :private, parent: parent_group) }
let!(:project) { create(:project, :public, namespace: public_subgroup) }
let!(:membership_group) { create(:group, :public) }
before do before do
project.add_maintainer(maintainer) project.add_maintainer(maintainer)
membership_group.add_guest(maintainer)
sign_in(maintainer) sign_in(maintainer)
group.add_maintainer(maintainer)
group_to_share_with.add_maintainer(maintainer)
end end
# This behavior should be changed to exclude the ancestor and project context 'when invite_members_group_modal feature enabled' do
# group from the options once issue is fixed for the modal: it 'does not show the groups inherited from projects' do
# https://gitlab.com/gitlab-org/gitlab/-/issues/329835 visit project_project_members_path(project)
it 'the groups dropdown does show ancestors and the project group' do
visit project_project_members_path(project)
click_on 'Invite a group' click_on 'Invite a group'
click_on 'Select a group' click_on 'Select a group'
wait_for_requests wait_for_requests
expect(page).to have_button(membership_group.name)
expect(page).not_to have_button(parent_group.name)
expect(page).not_to have_button(public_subgroup.name)
expect(page).not_to have_button(public_sub_subgroup.name)
expect(page).not_to have_button(private_subgroup.name)
end
# This behavior should be changed to exclude the ancestor and project
# group from the options once issue is fixed for the modal:
# https://gitlab.com/gitlab-org/gitlab/-/issues/329835
it 'does show ancestors and the project group' do
parent_group.add_maintainer(maintainer)
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
expect(page).to have_button(membership_group.name)
expect(page).to have_button(parent_group.name)
expect(page).to have_button(public_subgroup.name)
end
end
context 'when invite_members_group_modal feature disabled' do
let(:group_invite_dropdown) { find('#select2-results-2') }
before do
stub_feature_flags(invite_members_group_modal: false)
end
it 'does not show the groups inherited from projects' do
visit project_project_members_path(project)
expect(page).to have_button(group_to_share_with.name) click_on 'Invite group'
expect(page).to have_button(group.name) click_on 'Search for a group'
expect(page).to have_button(nested_group.name) wait_for_requests
expect(group_invite_dropdown).to have_text(membership_group.name)
expect(group_invite_dropdown).not_to have_text(parent_group.name)
expect(group_invite_dropdown).not_to have_text(public_subgroup.name)
expect(group_invite_dropdown).not_to have_text(public_sub_subgroup.name)
expect(group_invite_dropdown).not_to have_text(private_subgroup.name)
end
it 'does not show ancestors and the project group' do
parent_group.add_maintainer(maintainer)
visit project_project_members_path(project)
click_on 'Invite group'
click_on 'Search for a group'
wait_for_requests
expect(group_invite_dropdown).to have_text(membership_group.name)
expect(group_invite_dropdown).not_to have_text(parent_group.name, exact: true)
expect(group_invite_dropdown).not_to have_text(public_subgroup.name, exact: true)
end
end end
end end
end end
......
...@@ -4,8 +4,14 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -4,8 +4,14 @@ import waitForPromises from 'helpers/wait_for_promises';
import * as groupsApi from '~/api/groups_api'; import * as groupsApi from '~/api/groups_api';
import GroupSelect from '~/invite_members/components/group_select.vue'; import GroupSelect from '~/invite_members/components/group_select.vue';
const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
const createComponent = () => { const createComponent = () => {
return mount(GroupSelect, {}); return mount(GroupSelect, {
propsData: {
accessLevels,
},
});
}; };
const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' }; const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' };
...@@ -61,6 +67,7 @@ describe('GroupSelect', () => { ...@@ -61,6 +67,7 @@ describe('GroupSelect', () => {
expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, { expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, {
active: true, active: true,
exclude_internal: true, exclude_internal: true,
min_access_level: accessLevels.Guest,
}); });
}); });
......
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