diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index bd71c5ebc1143b30c22e9da5e4aedd4531f5c67f..64bba91eb4d112cddfbc997c33c13fc8032df6ed 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -28,6 +28,7 @@ const groupsSelect = () => { const skipGroups = $select.data('skipGroups') || []; const parentGroupID = $select.data('parentId'); const groupsFilter = $select.data('groupsFilter'); + const minAccessLevel = $select.data('minAccessLevel'); $select.select2({ placeholder: __('Search for a group'), @@ -45,6 +46,7 @@ const groupsSelect = () => { page, per_page: window.GROUP_SELECT_PER_PAGE, all_available: allAvailable, + min_access_level: minAccessLevel, }; }, results(data, page) { diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue index 216078ed35ebb4ea94bc507aba66db8e5fa1b8c8..1213413e8bbd404158f86c0d2d44bd28c90ef518 100644 --- a/app/assets/javascripts/invite_members/components/group_select.vue +++ b/app/assets/javascripts/invite_members/components/group_select.vue @@ -24,6 +24,10 @@ export default { prop: 'selectedGroup', }, props: { + accessLevels: { + type: Object, + required: true, + }, groupsFilter: { type: String, required: false, @@ -50,6 +54,13 @@ export default { isFetchResultEmpty() { return this.groups.length === 0; }, + defaultFetchOptions() { + return { + exclude_internal: true, + active: true, + min_access_level: this.accessLevels.Guest, + }; + }, }, watch: { searchTerm() { @@ -84,13 +95,9 @@ export default { fetchGroups() { switch (this.groupsFilter) { case GROUP_FILTERS.DESCENDANT_GROUPS: - return getDescendentGroups( - this.parentGroupId, - this.searchTerm, - this.$options.defaultFetchOptions, - ); + return getDescendentGroups(this.parentGroupId, this.searchTerm, this.defaultFetchOptions); default: - return getGroups(this.searchTerm, this.$options.defaultFetchOptions); + return getGroups(this.searchTerm, this.defaultFetchOptions); } }, }, @@ -99,10 +106,6 @@ export default { searchPlaceholder: s__('GroupSelect|Search groups'), emptySearchResult: s__('GroupSelect|No matching results'), }, - defaultFetchOptions: { - exclude_internal: true, - active: true, - }, }; </script> <template> diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue index 91a139a510585378aa872c94a18fd67975c5fc4f..15eeb51ec24aabea35284a5183ea800eff7a6f21 100644 --- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue @@ -428,6 +428,7 @@ export default { <group-select v-if="isInviteGroup" v-model="groupToBeSharedWith" + :access-levels="accessLevels" :groups-filter="groupSelectFilter" :parent-group-id="groupSelectParentId" @input="handleMembersTokenSelectClear" diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 12b2b33e3649006a7c4825b4e53571717e2e6ffa..f2a9b46321c5cc3367731c81c2cf8afc40dacfdf 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -61,7 +61,7 @@ default_access_level: ProjectGroupLink.default_access, group_link_field: 'link_group_id', 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? .invite-member = render 'shared/members/invite_member', @@ -78,7 +78,7 @@ submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', 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, members: @project_members, group_links: @group_links, diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index b674cad0312406ce58c5976372d8f38eca34e5a0..08ec2f54fba6c5380236c846e2c7f4660d2540fa 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -214,31 +214,88 @@ def setup end context 'for a project in a nested group' do - let(:group) { create(:group) } - let!(:nested_group) { create(:group, parent: group) } - let!(:group_to_share_with) { create(:group) } - let!(:project) { create(:project, namespace: nested_group) } + let!(:parent_group) { create(:group, :public) } + let!(:public_subgroup) { create(:group, :public, parent: parent_group) } + let!(:public_sub_subgroup) { create(:group, :public, parent: public_subgroup) } + 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 project.add_maintainer(maintainer) + membership_group.add_guest(maintainer) + sign_in(maintainer) - group.add_maintainer(maintainer) - group_to_share_with.add_maintainer(maintainer) 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 'the groups dropdown does show ancestors and the project group' do - visit project_project_members_path(project) + context 'when invite_members_group_modal feature enabled' do + it 'does not show the groups inherited from projects' do + visit project_project_members_path(project) - click_on 'Invite a group' - click_on 'Select a group' - wait_for_requests + click_on 'Invite a group' + click_on 'Select a group' + 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) - expect(page).to have_button(group.name) - expect(page).to have_button(nested_group.name) + 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) + 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 diff --git a/spec/frontend/invite_members/components/group_select_spec.js b/spec/frontend/invite_members/components/group_select_spec.js index 2ef8fe07650a3cbe28fe6eb64427615ad64ef52c..6face6a785cdbe6e2ef4b5569089cad8e416cfe4 100644 --- a/spec/frontend/invite_members/components/group_select_spec.js +++ b/spec/frontend/invite_members/components/group_select_spec.js @@ -4,8 +4,14 @@ import waitForPromises from 'helpers/wait_for_promises'; import * as groupsApi from '~/api/groups_api'; import GroupSelect from '~/invite_members/components/group_select.vue'; +const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 }; + const createComponent = () => { - return mount(GroupSelect, {}); + return mount(GroupSelect, { + propsData: { + accessLevels, + }, + }); }; const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' }; @@ -61,6 +67,7 @@ describe('GroupSelect', () => { expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, { active: true, exclude_internal: true, + min_access_level: accessLevels.Guest, }); });