Commit dc2d0208 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'hide-groups-outside-hierarchy-from-modal-search' into 'master'

Filter Search Results in Group Modal Invite

See merge request gitlab-org/gitlab!63694
parents c8a15c23 82c623ed
......@@ -3,9 +3,9 @@ import { buildApiUrl } from './api_utils';
import { DEFAULT_PER_PAGE } from './constants';
const GROUPS_PATH = '/api/:version/groups.json';
const DESCENDANT_GROUPS_PATH = '/api/:version/groups/:id/descendant_groups';
export function getGroups(query, options, callback = () => {}) {
const url = buildApiUrl(GROUPS_PATH);
const axiosGet = (url, query, options, callback) => {
return axios
.get(url, {
params: {
......@@ -19,4 +19,14 @@ export function getGroups(query, options, callback = () => {}) {
return data;
});
};
export function getGroups(query, options, callback = () => {}) {
const url = buildApiUrl(GROUPS_PATH);
return axiosGet(url, query, options, callback);
}
export function getDescendentGroups(parentGroupId, query, options, callback = () => {}) {
const url = buildApiUrl(DESCENDANT_GROUPS_PATH.replace(':id', parentGroupId));
return axiosGet(url, query, options, callback);
}
......@@ -7,9 +7,9 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import Api from '~/api';
import { s__ } from '~/locale';
import { SEARCH_DELAY } from '../constants';
import { getGroups, getDescendentGroups } from '~/rest_api';
import { SEARCH_DELAY, GROUP_FILTERS } from '../constants';
export default {
name: 'GroupSelect',
......@@ -23,6 +23,18 @@ export default {
model: {
prop: 'selectedGroup',
},
props: {
groupsFilter: {
type: String,
required: false,
default: GROUP_FILTERS.ALL,
},
parentGroupId: {
type: Number,
required: false,
default: null,
},
},
data() {
return {
isFetching: false,
......@@ -50,7 +62,7 @@ export default {
methods: {
retrieveGroups: debounce(function debouncedRetrieveGroups() {
this.isFetching = true;
return Api.groups(this.searchTerm, this.$options.defaultFetchOptions)
return this.fetchGroups()
.then((response) => {
this.groups = response.map((group) => ({
id: group.id,
......@@ -69,6 +81,18 @@ export default {
this.$emit('input', this.selectedGroup);
},
fetchGroups() {
switch (this.groupsFilter) {
case GROUP_FILTERS.DESCENDANT_GROUPS:
return getDescendentGroups(
this.parentGroupId,
this.searchTerm,
this.$options.defaultFetchOptions,
);
default:
return getGroups(this.searchTerm, this.$options.defaultFetchOptions);
}
},
},
i18n: {
dropdownText: s__('GroupSelect|Select a group'),
......
......@@ -16,7 +16,7 @@ import GroupSelect from '~/invite_members/components/group_select.vue';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { s__, sprintf } from '~/locale';
import { INVITE_MEMBERS_IN_COMMENT } from '../constants';
import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS } from '../constants';
import eventHub from '../event_hub';
export default {
......@@ -54,6 +54,16 @@ export default {
type: Number,
required: true,
},
groupSelectFilter: {
type: String,
required: false,
default: GROUP_FILTERS.ALL,
},
groupSelectParentId: {
type: Number,
required: false,
default: null,
},
helpLink: {
type: String,
required: true,
......@@ -293,7 +303,12 @@ export default {
:aria-labelledby="$options.membersTokenSelectLabelId"
:placeholder="$options.labels[inviteeType].placeHolder"
/>
<group-select v-if="isInviteGroup" v-model="groupToBeSharedWith" />
<group-select
v-if="isInviteGroup"
v-model="groupToBeSharedWith"
:groups-filter="groupSelectFilter"
:parent-group-id="groupSelectParentId"
/>
</div>
<label class="gl-font-weight-bold gl-mt-3">{{ $options.labels.accessLevel }}</label>
......
export const SEARCH_DELAY = 200;
export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment';
export const GROUP_FILTERS = {
ALL: 'all',
DESCENDANT_GROUPS: 'descendant_groups',
};
......@@ -21,6 +21,8 @@ export default function initInviteMembersModal() {
isProject: parseBoolean(el.dataset.isProject),
accessLevels: JSON.parse(el.dataset.accessLevels),
defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
groupSelectFilter: el.dataset.groupsFilter,
groupSelectParentId: parseInt(el.dataset.parentId, 10),
},
}),
});
......
......@@ -31,4 +31,12 @@ module InviteMembersHelper
{ member_human_access: member.human_access, name: member.source.name }
end
end
def group_select_data(group)
if group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
{ groups_filter: 'descendant_groups', parent_id: group.root_ancestor.id }
else
{}
end
end
end
......@@ -4,4 +4,4 @@
is_project: 'false',
access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } }
help_link: help_page_url('user/permissions') }.merge(group_select_data(group)) }
- add_page_specific_style 'page_bundles/members'
- page_title _('Group members')
- groups_select_tag_data = @group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy ? { groups_filter: 'descendant_groups', parent_id: @group.root_ancestor.id, skip_groups: @skip_groups } : { skip_groups: @skip_groups }
- groups_select_tag_data = group_select_data(@group).merge({ skip_groups: @skip_groups })
.js-remove-member-modal
.row.gl-mt-3
......
......@@ -170,6 +170,18 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
expect(page).to have_text group_outside_hierarchy.name
end
end
context 'when the invite members group modal is enabled' do
it 'shows groups within and outside the hierarchy in search results' do
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
expect(page).to have_text group_within_hierarchy.name
expect(page).to have_text group_outside_hierarchy.name
end
end
end
context 'when sharing with groups outside the hierarchy is disabled' do
......@@ -192,6 +204,18 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
expect(page).not_to have_text group_outside_hierarchy.name
end
end
context 'when the invite members group modal is enabled' do
it 'shows only groups within the hierarchy in search results' do
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
expect(page).to have_text group_within_hierarchy.name
expect(page).not_to have_text group_outside_hierarchy.name
end
end
end
end
......
import { GlAvatarLabeled, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
import * as groupsApi from '~/api/groups_api';
import GroupSelect from '~/invite_members/components/group_select.vue';
const createComponent = () => {
......@@ -16,7 +16,7 @@ describe('GroupSelect', () => {
let wrapper;
beforeEach(() => {
jest.spyOn(Api, 'groups').mockResolvedValue(allGroups);
jest.spyOn(groupsApi, 'getGroups').mockResolvedValue(allGroups);
wrapper = createComponent();
});
......@@ -45,7 +45,7 @@ describe('GroupSelect', () => {
let resolveApiRequest;
beforeEach(() => {
jest.spyOn(Api, 'groups').mockImplementation(
jest.spyOn(groupsApi, 'getGroups').mockImplementation(
() =>
new Promise((resolve) => {
resolveApiRequest = resolve;
......@@ -58,7 +58,7 @@ describe('GroupSelect', () => {
it('calls the API', () => {
resolveApiRequest({ data: allGroups });
expect(Api.groups).toHaveBeenCalledWith(group1.name, {
expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, {
active: true,
exclude_internal: true,
});
......
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