Commit 60f266de authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '228675-separate-filtering-users-from-sorting-users' into 'master'

Add filter bar to group members view

See merge request gitlab-org/gitlab!48272
parents 78749098 0df4a9db
export default {
issues: 'issue-recent-searches',
merge_requests: 'merge-request-recent-searches',
group_members: 'group-members-recent-searches',
group_invited_members: 'group-invited-members-recent-searches',
};
......@@ -2,12 +2,15 @@
import { mapState, mapMutations } from 'vuex';
import { GlAlert } from '@gitlab/ui';
import MembersTable from '~/members/components/table/members_table.vue';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
import { HIDE_ERROR } from '~/members/store/mutation_types';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'GroupMembersApp',
components: { MembersTable, GlAlert },
components: { MembersTable, FilterSortContainer, GlAlert },
mixins: [glFeatureFlagsMixin()],
computed: {
...mapState(['showError', 'errorMessage']),
},
......@@ -33,6 +36,7 @@ export default {
<gl-alert v-if="showError" ref="errorAlert" variant="danger" @dismiss="hideError">{{
errorMessage
}}</gl-alert>
<filter-sort-container v-if="glFeatures.groupMembersFilteredSearch" />
<members-table />
</div>
</template>
<script>
import { mapState } from 'vuex';
import MembersFilteredSearchBar from './members_filtered_search_bar.vue';
export default {
name: 'FilterSortContainer',
components: { MembersFilteredSearchBar },
computed: {
...mapState(['filteredSearchBar']),
},
};
</script>
<template>
<div v-if="filteredSearchBar.show" class="gl-bg-gray-10 gl-p-5">
<members-filtered-search-bar />
</div>
</template>
<script>
import { mapState } from 'vuex';
import { GlFilteredSearchToken } from '@gitlab/ui';
import { setUrlParams, queryToObject } from '~/lib/utils/url_utility';
import { getParameterByName } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { SEARCH_TOKEN_TYPE, SORT_PARAM } from '~/members/constants';
export default {
name: 'MembersFilteredSearchBar',
components: { FilteredSearchBar },
availableTokens: [
{
type: 'two_factor',
icon: 'lock',
title: s__('Members|2FA'),
token: GlFilteredSearchToken,
unique: true,
operators: [{ value: '=', description: 'is' }],
options: [
{ value: 'enabled', title: s__('Members|Enabled') },
{ value: 'disabled', title: s__('Members|Disabled') },
],
requiredPermissions: 'canManageMembers',
},
{
type: 'with_inherited_permissions',
icon: 'group',
title: s__('Members|Membership'),
token: GlFilteredSearchToken,
unique: true,
operators: [{ value: '=', description: 'is' }],
options: [
{ value: 'exclude', title: s__('Members|Direct') },
{ value: 'only', title: s__('Members|Inherited') },
],
},
],
data() {
return {
initialFilterValue: [],
};
},
computed: {
...mapState(['sourceId', 'filteredSearchBar', 'canManageMembers']),
tokens() {
return this.$options.availableTokens.filter(token => {
if (
Object.prototype.hasOwnProperty.call(token, 'requiredPermissions') &&
!this[token.requiredPermissions]
) {
return false;
}
return this.filteredSearchBar.tokens?.includes(token.type);
});
},
},
created() {
const query = queryToObject(window.location.search);
const tokens = this.tokens
.filter(token => query[token.type])
.map(token => ({
type: token.type,
value: {
data: query[token.type],
operator: '=',
},
}));
if (query[this.filteredSearchBar.searchParam]) {
tokens.push({
type: SEARCH_TOKEN_TYPE,
value: {
data: query[this.filteredSearchBar.searchParam],
},
});
}
this.initialFilterValue = tokens;
},
methods: {
handleFilter(tokens) {
const params = tokens.reduce((accumulator, token) => {
const { type, value } = token;
if (!type || !value) {
return accumulator;
}
if (type === SEARCH_TOKEN_TYPE) {
if (value.data !== '') {
return {
...accumulator,
[this.filteredSearchBar.searchParam]: value.data,
};
}
} else {
return {
...accumulator,
[type]: value.data,
};
}
return accumulator;
}, {});
const sortParam = getParameterByName(SORT_PARAM);
window.location.href = setUrlParams(
{ ...params, ...(sortParam && { sort: sortParam }) },
window.location.href,
true,
);
},
},
};
</script>
<template>
<filtered-search-bar
:namespace="sourceId.toString()"
:tokens="tokens"
:recent-searches-storage-key="filteredSearchBar.recentSearchesStorageKey"
:search-input-placeholder="filteredSearchBar.placeholder"
:initial-filter-value="initialFilterValue"
data-testid="members-filtered-search-bar"
@onFilter="handleFilter"
/>
</template>
......@@ -69,3 +69,7 @@ export const DAYS_TO_EXPIRE_SOON = 7;
export const LEAVE_MODAL_ID = 'member-leave-modal';
export const REMOVE_GROUP_LINK_MODAL_ID = 'remove-group-link-modal-id';
export const SEARCH_TOKEN_TYPE = 'filtered-search-term';
export const SORT_PARAM = 'sort';
......@@ -6,7 +6,7 @@ import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import { initGroupMembersApp } from '~/groups/members';
import { memberRequestFormatter, groupLinkRequestFormatter } from '~/groups/members/utils';
import { __ } from '~/locale';
import { s__ } from '~/locale';
function mountRemoveMemberModal() {
const el = document.querySelector('.js-remove-member-modal');
......@@ -33,7 +33,7 @@ initGroupMembersApp(document.querySelector('.js-group-members-list'), {
show: true,
tokens: ['two_factor', 'with_inherited_permissions'],
searchParam: 'search',
placeholder: __('Members|Filter members'),
placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'group_members',
},
});
......@@ -52,7 +52,7 @@ initGroupMembersApp(document.querySelector('.js-group-invited-members-list'), {
show: true,
tokens: [],
searchParam: 'search_invited',
placeholder: __('Members|Search invited'),
placeholder: s__('Members|Search invited'),
recentSearchesStorageKey: 'group_invited_members',
},
});
......
......@@ -14,6 +14,10 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
before_action do
push_frontend_feature_flag(:group_members_filtered_search, @group)
end
skip_before_action :check_two_factor_requirement, only: :leave
skip_cross_project_access_check :index, :create, :update, :destroy, :request_access,
:approve_access_request, :leave, :resend_invite,
......
......@@ -4,6 +4,7 @@
- show_access_requests = can_manage_members && @requesters.exists?
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group, default_enabled: true)
- filtered_search_enabled = Feature.enabled?(:group_members_filtered_search, @group)
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
......@@ -54,20 +55,21 @@
.tab-content
#tab-members.tab-pane{ class: ('active' unless invited_active) }
.card.card-without-border
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Members with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= form_tag group_group_members_path(@group), method: :get, class: 'user-search-form gl-display-flex gl-md-align-items-center gl-flex-wrap gl-flex-direction-column gl-md-flex-direction-row gl-mx-n3 gl-my-n3', data: { testid: 'user-search-form' } do
.gl-px-3.gl-py-2
.search-control-wrap.gl-relative
= render 'shared/members/search_field'
- if can_manage_members
- unless filtered_search_enabled
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Members with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= form_tag group_group_members_path(@group), method: :get, class: 'user-search-form gl-display-flex gl-md-align-items-center gl-flex-wrap gl-flex-direction-column gl-md-flex-direction-row gl-mx-n3 gl-my-n3', data: { testid: 'user-search-form' } do
.gl-px-3.gl-py-2
.search-control-wrap.gl-relative
= render 'shared/members/search_field'
- if can_manage_members
= render 'groups/group_members/tab_pane/form_item' do
= label_tag '2fa', _('2FA'), class: form_item_label_css_class
= render 'shared/members/filter_2fa_dropdown'
= render 'groups/group_members/tab_pane/form_item' do
= label_tag '2fa', _('2FA'), class: form_item_label_css_class
= render 'shared/members/filter_2fa_dropdown'
= render 'groups/group_members/tab_pane/form_item' do
= label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
= render 'shared/members/sort_dropdown'
= label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
= render 'shared/members/sort_dropdown'
- if vue_members_list_enabled
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
.loading
......@@ -83,9 +85,10 @@
- if @group.shared_with_group_links.any?
#tab-groups.tab-pane
.card.card-without-border
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- unless filtered_search_enabled
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- if vue_members_list_enabled
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
.loading
......@@ -97,11 +100,12 @@
- if show_invited_members
#tab-invited-members.tab-pane{ class: ('active' if invited_active) }
.card.card-without-border
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Members invited to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= form_tag group_group_members_path(@group), method: :get, class: 'user-search-form', data: { testid: 'user-search-form' } do
= render 'shared/members/search_field', name: 'search_invited'
- unless filtered_search_enabled
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Members invited to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= form_tag group_group_members_path(@group), method: :get, class: 'user-search-form', data: { testid: 'user-search-form' } do
= render 'shared/members/search_field', name: 'search_invited'
- if vue_members_list_enabled
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
.loading
......@@ -117,9 +121,10 @@
- if show_access_requests
#tab-access-requests.tab-pane
.card.card-without-border
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- unless filtered_search_enabled
= render 'groups/group_members/tab_pane/header' do
= render 'groups/group_members/tab_pane/title' do
= html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- if vue_members_list_enabled
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
.loading
......
---
name: group_members_filtered_search
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48272
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/289911
milestone: '13.7'
type: development
group: group::access
default_enabled: false
......@@ -16864,6 +16864,9 @@ msgstr ""
msgid "Members|%{userName} is currently an LDAP user. Editing their permissions will override the settings from the LDAP group sync."
msgstr ""
msgid "Members|2FA"
msgstr ""
msgid "Members|An error occurred while trying to enable LDAP override, please try again."
msgstr ""
......@@ -16897,9 +16900,18 @@ msgstr ""
msgid "Members|Are you sure you want to withdraw your access request for \"%{source}\""
msgstr ""
msgid "Members|Direct"
msgstr ""
msgid "Members|Disabled"
msgstr ""
msgid "Members|Edit permissions"
msgstr ""
msgid "Members|Enabled"
msgstr ""
msgid "Members|Expiration date removed successfully."
msgstr ""
......@@ -16912,12 +16924,18 @@ msgstr ""
msgid "Members|Filter members"
msgstr ""
msgid "Members|Inherited"
msgstr ""
msgid "Members|LDAP override enabled."
msgstr ""
msgid "Members|Leave \"%{source}\""
msgstr ""
msgid "Members|Membership"
msgstr ""
msgid "Members|No expiration set"
msgstr ""
......
......@@ -11,8 +11,7 @@ RSpec.describe 'Groups > Members > Filter members', :js do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
two_factor_auth_dropdown_toggle_selector = '[data-testid="member-filter-2fa-dropdown"] [data-testid="dropdown-toggle"]'
active_inherited_members_filter_selector = '[data-testid="filter-members-with-inherited-permissions"] a.is-active'
filtered_search_bar_selector = '[data-testid="members-filtered-search-bar"]'
before do
group.add_owner(user)
......@@ -27,7 +26,6 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user.name)
expect(member(1)).to include(user_with_2fa.name)
expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Everyone')
end
it 'shows only 2FA members' do
......@@ -35,7 +33,10 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user_with_2fa.name)
expect(all_rows.size).to eq(1)
expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Enabled')
within filtered_search_bar_selector do
expect(page).to have_content '2FA = Enabled'
end
end
it 'shows only non 2FA members' do
......@@ -43,7 +44,10 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user.name)
expect(all_rows.size).to eq(1)
expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Disabled')
within filtered_search_bar_selector do
expect(page).to have_content '2FA = Disabled'
end
end
it 'shows inherited members by default' do
......@@ -53,15 +57,16 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(1)).to include(user_with_2fa.name)
expect(member(2)).to include(nested_group_user.name)
expect(all_rows.size).to eq(3)
expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show all members', visible: false)
end
it 'shows only group members' do
visit_members_list(nested_group, with_inherited_permissions: 'exclude')
expect(member(0)).to include(nested_group_user.name)
expect(all_rows.size).to eq(1)
expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show only direct members', visible: false)
within filtered_search_bar_selector do
expect(page).to have_content 'Membership = Direct'
end
end
it 'shows only inherited members' do
......@@ -69,7 +74,10 @@ RSpec.describe 'Groups > Members > Filter members', :js do
expect(member(0)).to include(user.name)
expect(member(1)).to include(user_with_2fa.name)
expect(all_rows.size).to eq(2)
expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show only inherited members', visible: false)
within filtered_search_bar_selector do
expect(page).to have_content 'Membership = Inherited'
end
end
def visit_members_list(group, options = {})
......
......@@ -21,9 +21,10 @@ RSpec.describe 'Search group member', :js do
end
it 'renders member users' do
page.within '[data-testid="user-search-form"]' do
fill_in 'search', with: member.name
find('[data-testid="user-search-submit"]').click
page.within '[data-testid="members-filtered-search-bar"]' do
find_field('Filter members').click
find('input').native.send_keys(member.name)
click_button 'Search'
end
expect(members_table).to have_content(member.name)
......
......@@ -12,6 +12,8 @@ RSpec.describe 'Groups > Members > Sort members', :js do
dropdown_toggle_selector = '[data-testid="user-sort-dropdown"] [data-testid="dropdown-toggle"]'
before do
stub_feature_flags(group_members_filtered_search: false)
create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago)
create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago)
......
......@@ -62,9 +62,10 @@ RSpec.describe 'Groups > Members > Tabs' do
click_link 'Invited'
page.within '[data-testid="user-search-form"]' do
fill_in 'search_invited', with: 'email'
find('button[type="submit"]').click
page.within '[data-testid="members-filtered-search-bar"]' do
find_field('Search invited').click
find('input').native.send_keys('email')
click_button 'Search'
end
end
......@@ -74,9 +75,10 @@ RSpec.describe 'Groups > Members > Tabs' do
before do
click_link 'Members'
page.within '[data-testid="user-search-form"]' do
fill_in 'search', with: 'test'
find('button[type="submit"]').click
page.within '[data-testid="members-filtered-search-bar"]' do
find_field('Filter members').click
find('input').native.send_keys('test')
click_button 'Search'
end
end
......
......@@ -3,6 +3,7 @@ import { nextTick } from 'vue';
import Vuex from 'vuex';
import { GlAlert } from '@gitlab/ui';
import App from '~/groups/members/components/app.vue';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import * as commonUtils from '~/lib/utils/common_utils';
import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_types';
import mutations from '~/members/store/mutations';
......@@ -14,7 +15,7 @@ describe('GroupMembersApp', () => {
let wrapper;
let store;
const createComponent = (state = {}) => {
const createComponent = (state = {}, options = {}) => {
store = new Vuex.Store({
state: {
showError: true,
......@@ -27,10 +28,12 @@ describe('GroupMembersApp', () => {
wrapper = shallowMount(App, {
localVue,
store,
...options,
});
};
const findAlert = () => wrapper.find(GlAlert);
const findFilterSortContainer = () => wrapper.find(FilterSortContainer);
beforeEach(() => {
commonUtils.scrollToElement = jest.fn();
......@@ -83,4 +86,22 @@ describe('GroupMembersApp', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe.each`
featureFlagValue | exists
${true} | ${true}
${false} | ${false}
`(
'when `group_members_filtered_search` feature flag is $featureFlagValue',
({ featureFlagValue, exists }) => {
it(`${exists ? 'renders' : 'does not render'} FilterSortContainer`, () => {
createComponent(
{},
{ provide: { glFeatures: { groupMembersFilteredSearch: featureFlagValue } } },
);
expect(findFilterSortContainer().exists()).toBe(exists);
});
},
);
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('FilterSortContainer', () => {
let wrapper;
const createComponent = state => {
const store = new Vuex.Store({
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
},
...state,
},
});
wrapper = shallowMount(FilterSortContainer, {
localVue,
store,
});
};
describe('when `filteredSearchBar.show` is `false`', () => {
it('renders nothing', () => {
createComponent({
filteredSearchBar: {
show: false,
},
});
expect(wrapper.html()).toBe('');
});
});
describe('when `filteredSearchBar.show` is `true`', () => {
it('renders `MembersFilteredSearchBar`', () => {
createComponent({
filteredSearchBar: {
show: true,
},
});
expect(wrapper.find(MembersFilteredSearchBar).exists()).toBe(true);
});
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlFilteredSearchToken } from '@gitlab/ui';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('MembersFilteredSearchBar', () => {
let wrapper;
const createComponent = state => {
const store = new Vuex.Store({
state: {
sourceId: 1,
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
},
canManageMembers: true,
...state,
},
});
wrapper = shallowMount(MembersFilteredSearchBar, {
localVue,
store,
});
};
const findFilteredSearchBar = () => wrapper.find(FilteredSearchBar);
it('passes correct props to `FilteredSearchBar` component', () => {
createComponent();
expect(findFilteredSearchBar().props()).toMatchObject({
namespace: '1',
recentSearchesStorageKey: 'group_members',
searchInputPlaceholder: 'Filter members',
});
});
describe('filtering tokens', () => {
it('includes tokens set in `filteredSearchBar.tokens`', () => {
createComponent();
expect(findFilteredSearchBar().props('tokens')).toEqual([
{
type: 'two_factor',
icon: 'lock',
title: '2FA',
token: GlFilteredSearchToken,
unique: true,
operators: [{ value: '=', description: 'is' }],
options: [
{ value: 'enabled', title: 'Enabled' },
{ value: 'disabled', title: 'Disabled' },
],
requiredPermissions: 'canManageMembers',
},
]);
});
describe('when `canManageMembers` is false', () => {
it('excludes 2FA token', () => {
createComponent({
filteredSearchBar: {
show: true,
tokens: ['two_factor', 'with_inherited_permissions'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
},
canManageMembers: false,
});
expect(findFilteredSearchBar().props('tokens')).toEqual([
{
type: 'with_inherited_permissions',
icon: 'group',
title: 'Membership',
token: GlFilteredSearchToken,
unique: true,
operators: [{ value: '=', description: 'is' }],
options: [{ value: 'exclude', title: 'Direct' }, { value: 'only', title: 'Inherited' }],
},
]);
});
});
});
describe('when filters are set via query params', () => {
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost');
});
it('parses and passes tokens to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
window.location.search = '?two_factor=enabled&token_not_available=foobar';
createComponent();
expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([
{
type: 'two_factor',
value: {
data: 'enabled',
operator: '=',
},
},
]);
});
it('parses and passes search param to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
window.location.search = '?search=foobar';
createComponent();
expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([
{
type: 'filtered-search-term',
value: {
data: 'foobar',
},
},
]);
});
});
describe('when filter bar is submitted', () => {
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost');
});
it('adds correct filter query params', () => {
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
]);
expect(window.location.href).toBe('https://localhost/?two_factor=enabled');
});
it('adds search query param', () => {
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);
expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foobar');
});
it('adds sort query param', () => {
window.location.search = '?sort=name_asc';
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);
expect(window.location.href).toBe(
'https://localhost/?two_factor=enabled&search=foobar&sort=name_asc',
);
});
});
});
......@@ -50,7 +50,6 @@ RSpec.shared_examples 'Maintainer manages access requests' do
def expect_visible_access_request(entity, user)
if has_tabs
expect(page).to have_content "Access requests 1"
expect(page).to have_content "Users requesting access to #{entity.name}"
else
expect(page).to have_content "Users requesting access to #{entity.name} 1"
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