Commit 6ec19865 authored by Scott Stern's avatar Scott Stern Committed by Nicolò Maria Mezzopera

Add glfilteredsearch to group boards

Adding GLFilteredSearch to group issue baords
as first iteration to replace old filtered search
implementation
parent a6f7fc24
<script>
import { mapActions } from 'vuex';
import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
export default {
i18n: {
search: __('Search'),
},
components: { FilteredSearch },
props: {
search: {
type: String,
required: false,
default: '',
},
},
computed: {
initialSearch() {
return [{ type: 'filtered-search-term', value: { data: this.search } }];
},
},
methods: {
...mapActions(['performSearch']),
handleSearch(filters) {
let itemValue = '';
const [item] = filters;
if (filters.length === 0) {
itemValue = '';
} else {
itemValue = item?.value?.data;
}
historyPushState(setUrlParams({ search: itemValue }, window.location.href));
this.performSearch();
},
},
};
</script>
<template>
<filtered-search
class="gl-w-full"
namespace=""
:tokens="[]"
:search-input-placeholder="$options.i18n.search"
:initial-filter-value="initialSearch"
@onFilter="handleSearch"
/>
</template>
import Vue from 'vue';
import store from '~/boards/stores';
import { queryToObject } from '~/lib/utils/url_utility';
import FilteredSearch from './components/filtered_search.vue';
export default () => {
const queryParams = queryToObject(window.location.search);
const el = document.getElementById('js-board-filtered-search');
/*
When https://github.com/vuejs/vue-apollo/pull/1153 is merged and deployed
we can remove apolloProvider option from here. Currently without it its causing
an error
*/
return new Vue({
el,
store,
apolloProvider: {},
render: (createElement) =>
createElement(FilteredSearch, {
props: { search: queryParams.search },
}),
});
};
...@@ -53,7 +53,6 @@ let issueBoardsApp; ...@@ -53,7 +53,6 @@ let issueBoardsApp;
export default () => { export default () => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
// check for browser back and trigger a hard reload to circumvent browser caching. // check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', (event) => { window.addEventListener('pageshow', (event) => {
const isNavTypeBackForward = const isNavTypeBackForward =
...@@ -73,6 +72,14 @@ export default () => { ...@@ -73,6 +72,14 @@ export default () => {
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours); boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
} }
if (gon?.features?.boardsFilteredSearch) {
import('~/boards/filtered_search')
.then(({ default: initFilteredSearch }) => {
initFilteredSearch(apolloProvider);
})
.catch(() => {});
}
// eslint-disable-next-line @gitlab/no-runtime-template-compiler // eslint-disable-next-line @gitlab/no-runtime-template-compiler
issueBoardsApp = new Vue({ issueBoardsApp = new Vue({
el: $boardApp, el: $boardApp,
...@@ -164,8 +171,15 @@ export default () => { ...@@ -164,8 +171,15 @@ export default () => {
eventHub.$off('initialBoardLoad', this.initialBoardLoad); eventHub.$off('initialBoardLoad', this.initialBoardLoad);
}, },
mounted() { mounted() {
this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit); if (!gon.features?.boardsFilteredSearch) {
this.filterManager.setup(); this.filterManager = new FilteredSearchBoards(
boardsStore.filter,
true,
boardsStore.cantEdit,
);
this.filterManager.setup();
}
this.performSearch(); this.performSearch();
......
...@@ -9,6 +9,7 @@ class Groups::BoardsController < Groups::ApplicationController ...@@ -9,6 +9,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars before_action :assign_endpoint_vars
before_action do before_action do
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false) push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
push_frontend_feature_flag(:boards_filtered_search, group)
end end
feature_category :boards feature_category :boards
......
...@@ -21,178 +21,181 @@ ...@@ -21,178 +21,181 @@
- if @can_bulk_update - if @can_bulk_update
.check-all-holder.d-none.d-sm-block.hidden .check-all-holder.d-none.d-sm-block.hidden
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left" = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row - if Feature.enabled?(:boards_filtered_search, @group)
.filtered-search-box #js-board-filtered-search
- if type != :boards_modal && type != :boards - else
- text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline") .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
= dropdown_tag(text, .filtered-search-box
options: { wrapper_class: "filtered-search-history-dropdown-wrapper", - if type != :boards_modal && type != :boards
toggle_class: "btn filtered-search-history-dropdown-toggle-button", - text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
dropdown_class: "filtered-search-history-dropdown", = dropdown_tag(text,
content_class: "filtered-search-history-dropdown-content" }) do options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
.js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } } toggle_class: "btn filtered-search-history-dropdown-toggle-button",
.filtered-search-box-input-container.droplab-dropdown dropdown_class: "filtered-search-history-dropdown",
.scroll-container content_class: "filtered-search-history-dropdown-content" }) do
%ul.tokens-container.list-unstyled .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
%li.input-token .filtered-search-box-input-container.droplab-dropdown
%input.form-control.filtered-search{ search_filter_input_options(type, placeholder) } .scroll-container
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown %ul.tokens-container.list-unstyled
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.input-token
%li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } } %input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
%button.btn.btn-link{ type: 'button' } #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
-# Encapsulate static class name `{{icon}}` inside #{} to bypass %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
-# haml lint's ClassAttributeWithStaticValue %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
%svg %button.btn.btn-link{ type: 'button' }
%use{ 'xlink:href': "#{'{{icon}}'}" } -# Encapsulate static class name `{{icon}}` inside #{} to bypass
%span.js-filter-hint -# haml lint's ClassAttributeWithStaticValue
{{formattedKey}} %svg
#js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu %use{ 'xlink:href': "#{'{{icon}}'}" }
%ul.filter-dropdown{ data: { dropdown: true, dynamic: true } } %span.js-filter-hint
%li.filter-dropdown-item{ data: { value: "{{ title }}" } } {{formattedKey}}
%button.btn.btn-link{ type: 'button' } #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
{{ title }} %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
%span.btn-helptext %li.filter-dropdown-item{ data: { value: "{{ title }}" } }
{{ help }} %button.btn.btn-link{ type: 'button' }
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu {{ title }}
- if current_user %span.btn-helptext
%ul{ data: { dropdown: true } } {{ help }}
= render 'shared/issuable/user_dropdown_item', #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } }
%button.btn.btn-link{ type: 'button' }
= _('None')
%li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' }
= _('Any')
%li.divider.droplab-item-ignore
- if current_user - if current_user
%ul{ data: { dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item', = render 'shared/issuable/user_dropdown_item',
user: current_user user: User.new(username: '{{username}}', name: '{{name}}'),
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } avatar: { lazy: true, url: '{{avatar_url}}' }
= render 'shared/issuable/user_dropdown_item', #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
user: User.new(username: '{{username}}', name: '{{name}}'), %ul{ data: { dropdown: true } }
avatar: { lazy: true, url: '{{avatar_url}}' } %li.filter-dropdown-item{ data: { value: 'None' } }
#js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu %button.btn.btn-link{ type: 'button' }
%ul{ data: { dropdown: true } } = _('None')
%li.filter-dropdown-item{ data: { value: 'None' } } %li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('None') = _('Any')
%li.filter-dropdown-item{ data: { value: 'Any' } } %li.divider.droplab-item-ignore
%button.btn.btn-link{ type: 'button' } - if current_user
= _('Any') = render 'shared/issuable/user_dropdown_item',
%li.divider.droplab-item-ignore user: current_user
- if current_user %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
#js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } }
%button.btn.btn-link{ type: 'button' }
= _('None')
%li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' }
= _('Any')
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item', = render 'shared/issuable/user_dropdown_item',
user: current_user user: User.new(username: '{{username}}', name: '{{name}}'),
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } avatar: { lazy: true, url: '{{avatar_url}}' }
= render 'shared/issuable/user_dropdown_item', = render_if_exists 'shared/issuable/approver_dropdown'
user: User.new(username: '{{username}}', name: '{{name}}'), = render_if_exists 'shared/issuable/approved_by_dropdown'
avatar: { lazy: true, url: '{{avatar_url}}' } #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
= render_if_exists 'shared/issuable/approver_dropdown' %ul{ data: { dropdown: true } }
= render_if_exists 'shared/issuable/approved_by_dropdown' %li.filter-dropdown-item{ data: { value: 'None' } }
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu %button.btn.btn-link{ type: 'button' }
%ul{ data: { dropdown: true } } = _('None')
%li.filter-dropdown-item{ data: { value: 'None' } } %li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('None') = _('Any')
%li.filter-dropdown-item{ data: { value: 'Any' } } %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('Any') = _('Upcoming')
%li.filter-dropdown-item{ data: { value: 'Upcoming' } } %li.filter-dropdown-item{ data: { value: 'Started' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('Upcoming') = _('Started')
%li.filter-dropdown-item{ data: { value: 'Started' } } %li.divider.droplab-item-ignore
%button.btn.btn-link{ type: 'button' } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= _('Started') %li.filter-dropdown-item
%li.divider.droplab-item-ignore %button.btn.btn-link.js-data-value{ type: 'button' }
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } {{title}}
%li.filter-dropdown-item = render_if_exists 'shared/issuable/filter_iteration', type: type
%button.btn.btn-link.js-data-value{ type: 'button' } #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
{{title}} %ul{ data: { dropdown: true } }
= render_if_exists 'shared/issuable/filter_iteration', type: type %li.filter-dropdown-item{ data: { value: 'None' } }
#js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu %button.btn.btn-link{ type: 'button' }
%ul{ data: { dropdown: true } } = _('None')
%li.filter-dropdown-item{ data: { value: 'None' } } %li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('None') = _('Any')
%li.filter-dropdown-item{ data: { value: 'Any' } } %li.divider.droplab-item-ignore
%button.btn.btn-link{ type: 'button' } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= _('Any') %li.filter-dropdown-item
%li.divider.droplab-item-ignore %button.btn.btn-link.js-data-value{ type: 'button' }
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value{ type: 'button' }
{{title}}
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } }
%button.btn.btn-link{ type: 'button' }
= _('None')
%li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' }
= _('Any')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' }
%span.dropdown-label-box{ style: 'background: {{color}}' }
%span.label-title.js-data-value
{{title}} {{title}}
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } } %li.filter-dropdown-item{ data: { value: 'None' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('None') = _('None')
%li.filter-dropdown-item{ data: { value: 'Any' } } %li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('Any') = _('Any')
%li.divider.droplab-item-ignore %li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item %li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
%gl-emoji %span.dropdown-label-box{ style: 'background: {{color}}' }
%span.js-data-value.gl-ml-3 %span.label-title.js-data-value
{{name}} {{title}}
#js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } %li.filter-dropdown-item{ data: { value: 'None' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('Yes') = _('None')
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } %li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('No') = _('Any')
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu %li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dropdown: true } } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } %li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('Yes') %gl-emoji
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } %span.js-data-value.gl-ml-3
%button.btn.btn-link{ type: 'button' } {{name}}
= _('No') #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
- unless disable_target_branch %ul.filter-dropdown{ data: { dropdown: true } }
#js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('Yes')
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('No')
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('Yes')
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('No')
- unless disable_target_branch
#js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value.monospace
{{title}}
#js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item %li.filter-dropdown-item
%button.btn.btn-link.js-data-value.monospace %button.btn.btn-link.js-data-value{ type: 'button' }
{{title}} {{title}}
#js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value{ type: 'button' }
{{title}}
= render_if_exists 'shared/issuable/filter_weight', type: type = render_if_exists 'shared/issuable/filter_weight', type: type
= render_if_exists 'shared/issuable/filter_epic', type: type = render_if_exists 'shared/issuable/filter_epic', type: type
%button.clear-search.hidden{ type: 'button' } %button.clear-search.hidden{ type: 'button' }
= sprite_icon('close', size: 16, css_class: 'clear-search-icon') = sprite_icon('close', size: 16, css_class: 'clear-search-icon')
.filter-dropdown-container.d-flex.flex-column.flex-md-row .filter-dropdown-container.d-flex.flex-column.flex-md-row
- if type == :boards - if type == :boards
#js-board-labels-toggle #js-board-labels-toggle
......
---
name: boards_filtered_search
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54641
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/322778
milestone: '13.10'
type: development
group: group::project management
default_enabled: false
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import FilteredSearch from '~/boards/components/filtered_search.vue';
import { createStore } from '~/boards/stores';
import * as commonUtils from '~/lib/utils/common_utils';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('FilteredSearch', () => {
let wrapper;
let store;
const createComponent = () => {
wrapper = shallowMount(FilteredSearch, {
localVue,
propsData: { search: '' },
store,
attachTo: document.body,
});
};
beforeEach(() => {
// this needed for actions call for performSearch
window.gon = { features: {} };
});
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
beforeEach(() => {
store = createStore();
jest.spyOn(store, 'dispatch');
createComponent();
});
it('finds FilteredSearch', () => {
expect(wrapper.find(FilteredSearchBarRoot).exists()).toBe(true);
});
describe('when onFilter is emitted', () => {
it('calls performSearch', () => {
wrapper.find(FilteredSearchBarRoot).vm.$emit('onFilter', [{ value: { data: '' } }]);
expect(store.dispatch).toHaveBeenCalledWith('performSearch');
});
it('calls historyPushState', () => {
commonUtils.historyPushState = jest.fn();
wrapper
.find(FilteredSearchBarRoot)
.vm.$emit('onFilter', [{ value: { data: 'searchQuery' } }]);
expect(commonUtils.historyPushState).toHaveBeenCalledWith(
'http://test.host/?search=searchQuery',
);
});
});
});
});
...@@ -16,6 +16,8 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr ...@@ -16,6 +16,8 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
end end
before do before do
stub_feature_flags(boards_filtered_search: false)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
......
...@@ -246,6 +246,12 @@ RSpec.configure do |config| ...@@ -246,6 +246,12 @@ RSpec.configure do |config|
stub_feature_flags(unified_diff_components: false) stub_feature_flags(unified_diff_components: false)
# Disable this feature flag as we iterate and
# refactor filtered search to use gitlab ui
# components to meet feature parody. More details found
# https://gitlab.com/groups/gitlab-org/-/epics/5501
stub_feature_flags(boards_filtered_search: false)
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged) allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
else else
unstub_all_feature_flags unstub_all_feature_flags
......
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