Commit dc963512 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch...

Merge branch '238314-search-ui-implement-issue-scope-results-filter-by-confidentiality' into 'master'

Search UI - Issue scope results filter by confidentiality

See merge request gitlab-org/gitlab!40793
parents 045489f6 034140b3
import Search from './search';
import initStateFilter from '~/search/state_filter';
import initConfidentialFilter from '~/search/confidential_filter';
document.addEventListener('DOMContentLoaded', () => {
initStateFilter();
initConfidentialFilter();
return new Search();
});
<script>
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import {
FILTER_STATES,
SCOPES,
FILTER_STATES_BY_SCOPE,
FILTER_HEADER,
FILTER_TEXT,
} from '../constants';
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
const FILTERS_ARRAY = Object.values(FILTER_STATES);
import { sprintf, s__ } from '~/locale';
export default {
name: 'StateFilter',
name: 'DropdownFilter',
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
},
props: {
scope: {
initialFilter: {
type: String,
required: false,
default: null,
},
filters: {
type: Object,
required: true,
},
state: {
filtersArray: {
type: Array,
required: true,
},
header: {
type: String,
required: false,
default: FILTER_STATES.ANY.value,
validator: v => FILTERS_ARRAY.some(({ value }) => value === v),
required: true,
},
param: {
type: String,
required: true,
},
scope: {
type: String,
required: true,
},
supportedScopes: {
type: Array,
required: true,
},
},
computed: {
filter() {
return this.initialFilter || this.filters.ANY.value;
},
selectedFilterText() {
const filter = FILTERS_ARRAY.find(({ value }) => value === this.selectedFilter);
if (!filter || filter === FILTER_STATES.ANY) {
return FILTER_TEXT;
const f = this.filtersArray.find(({ value }) => value === this.selectedFilter);
if (!f || f === this.filters.ANY) {
return sprintf(s__('Any %{header}'), { header: this.header });
}
return filter.label;
return f.label;
},
showDropdown() {
return Object.values(SCOPES).includes(this.scope);
return this.supportedScopes.includes(this.scope);
},
selectedFilter: {
get() {
if (FILTERS_ARRAY.some(({ value }) => value === this.state)) {
return this.state;
if (this.filtersArray.some(({ value }) => value === this.filter)) {
return this.filter;
}
return FILTER_STATES.ANY.value;
return this.filters.ANY.value;
},
set(state) {
visitUrl(setUrlParams({ state }));
set(filter) {
visitUrl(setUrlParams({ [this.param]: filter }));
},
},
},
......@@ -59,36 +73,39 @@ export default {
dropDownItemClass(filter) {
return {
'gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2':
filter === FILTER_STATES.ANY,
filter === this.filters.ANY,
};
},
isFilterSelected(filter) {
return filter === this.selectedFilter;
},
handleFilterChange(state) {
this.selectedFilter = state;
handleFilterChange(filter) {
this.selectedFilter = filter;
},
},
filterStates: FILTER_STATES,
filterHeader: FILTER_HEADER,
filtersByScope: FILTER_STATES_BY_SCOPE,
};
</script>
<template>
<gl-dropdown v-if="showDropdown" :text="selectedFilterText" class="col-sm-3 gl-pt-4 gl-pl-0">
<gl-dropdown
v-if="showDropdown"
:text="selectedFilterText"
class="col-3 gl-pt-4 gl-pl-0 gl-pr-0 gl-mr-4"
menu-class="gl-w-full! gl-pl-0"
>
<header class="gl-text-center gl-font-weight-bold gl-font-lg">
{{ $options.filterHeader }}
{{ header }}
</header>
<gl-dropdown-divider />
<gl-dropdown-item
v-for="filter in $options.filtersByScope[scope]"
:key="filter.value"
v-for="f in filtersArray"
:key="f.value"
:is-check-item="true"
:is-checked="isFilterSelected(filter.value)"
:class="dropDownItemClass(filter)"
@click="handleFilterChange(filter.value)"
>{{ filter.label }}</gl-dropdown-item
:is-checked="isFilterSelected(f.value)"
:class="dropDownItemClass(f)"
@click="handleFilterChange(f.value)"
>
{{ f.label }}
</gl-dropdown-item>
</gl-dropdown>
</template>
import { __ } from '~/locale';
export const FILTER_HEADER = __('Confidentiality');
export const FILTER_STATES = {
ANY: {
label: __('Any'),
value: null,
},
CONFIDENTIAL: {
label: __('Confidential'),
value: 'yes',
},
NOT_CONFIDENTIAL: {
label: __('Not confidential'),
value: 'no',
},
};
export const SCOPES = {
ISSUES: 'issues',
};
export const FILTER_STATES_BY_SCOPE = {
[SCOPES.ISSUES]: [FILTER_STATES.ANY, FILTER_STATES.CONFIDENTIAL, FILTER_STATES.NOT_CONFIDENTIAL],
};
export const FILTER_PARAM = 'confidential';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import DropdownFilter from '../components/dropdown_filter.vue';
import {
FILTER_HEADER,
FILTER_PARAM,
FILTER_STATES_BY_SCOPE,
FILTER_STATES,
SCOPES,
} from './constants';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-search-filter-by-confidential');
if (!el) return false;
return new Vue({
el,
data() {
return { ...el.dataset };
},
render(createElement) {
return createElement(DropdownFilter, {
props: {
initialFilter: this.filter,
filtersArray: FILTER_STATES_BY_SCOPE[this.scope],
filters: FILTER_STATES,
header: FILTER_HEADER,
param: FILTER_PARAM,
scope: this.scope,
supportedScopes: Object.values(SCOPES),
},
});
},
});
};
......@@ -2,8 +2,6 @@ import { __ } from '~/locale';
export const FILTER_HEADER = __('Status');
export const FILTER_TEXT = __('Any Status');
export const FILTER_STATES = {
ANY: {
label: __('Any'),
......@@ -37,3 +35,5 @@ export const FILTER_STATES_BY_SCOPE = {
FILTER_STATES.CLOSED,
],
};
export const FILTER_PARAM = 'state';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import StateFilter from './components/state_filter.vue';
import DropdownFilter from '../components/dropdown_filter.vue';
import {
FILTER_HEADER,
FILTER_PARAM,
FILTER_STATES_BY_SCOPE,
FILTER_STATES,
SCOPES,
} from './constants';
Vue.use(Translate);
......@@ -11,22 +18,20 @@ export default () => {
return new Vue({
el,
components: {
StateFilter,
},
data() {
const { dataset } = this.$options.el;
return {
scope: dataset.scope,
state: dataset.state,
};
return { ...el.dataset };
},
render(createElement) {
return createElement('state-filter', {
return createElement(DropdownFilter, {
props: {
initialFilter: this.filter,
filtersArray: FILTER_STATES_BY_SCOPE[this.scope],
filters: FILTER_STATES,
header: FILTER_HEADER,
param: FILTER_PARAM,
scope: this.scope,
state: this.state,
supportedScopes: Object.values(SCOPES),
},
});
},
......
- if @search_objects.to_a.empty?
= render partial: "search/results/filters"
= render partial: "search/results/empty"
= render_if_exists 'shared/promotions/promote_advanced_search'
= render_if_exists 'search/form_revert_to_basic'
......@@ -21,8 +22,7 @@
- link_to_group = link_to(@group.name, @group, class: 'ml-md-1')
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
= render_if_exists 'shared/promotions/promote_advanced_search'
#js-search-filter-by-state{ 'v-cloak': true, data: { scope: @scope, state: params[:state] } }
= render partial: "search/results/filters"
.results.gl-mt-3
- if @scope == 'commits'
......
.d-lg-flex.align-items-end
#js-search-filter-by-state{ 'v-cloak': true, data: { scope: @scope, filter: params[:state]} }
- if Feature.enabled?(:search_filter_by_confidential, @group)
#js-search-filter-by-confidential{ 'v-cloak': true, data: { scope: @scope, filter: params[:confidential] } }
- if %w(issues merge_requests).include?(@scope)
%hr.gl-mt-4.gl-mb-4
---
name: search_filter_by_confidential
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40793
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/244923
group: group::global search
type: development
default_enabled: false
\ No newline at end of file
......@@ -3037,10 +3037,10 @@ msgstr ""
msgid "Any"
msgstr ""
msgid "Any Author"
msgid "Any %{header}"
msgstr ""
msgid "Any Status"
msgid "Any Author"
msgstr ""
msgid "Any branch"
......
import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import StateFilter from '~/search/state_filter/components/state_filter.vue';
import DropdownFilter from '~/search/components/dropdown_filter.vue';
import {
FILTER_STATES,
SCOPES,
FILTER_STATES_BY_SCOPE,
FILTER_TEXT,
FILTER_HEADER,
SCOPES,
} from '~/search/state_filter/constants';
import * as urlUtils from '~/lib/utils/url_utility';
......@@ -15,14 +15,19 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
function createComponent(props = { scope: 'issues' }) {
return shallowMount(StateFilter, {
return shallowMount(DropdownFilter, {
propsData: {
filtersArray: FILTER_STATES_BY_SCOPE.issues,
filters: FILTER_STATES,
header: FILTER_HEADER,
param: 'state',
supportedScopes: Object.values(SCOPES),
...props,
},
});
}
describe('StateFilter', () => {
describe('DropdownFilter', () => {
let wrapper;
beforeEach(() => {
......@@ -41,7 +46,7 @@ describe('StateFilter', () => {
describe('template', () => {
describe.each`
scope | showStateDropdown
scope | showDropdown
${'issues'} | ${true}
${'merge_requests'} | ${true}
${'projects'} | ${false}
......@@ -50,26 +55,25 @@ describe('StateFilter', () => {
${'notes'} | ${false}
${'wiki_blobs'} | ${false}
${'blobs'} | ${false}
`(`state dropdown`, ({ scope, showStateDropdown }) => {
`(`dropdown`, ({ scope, showDropdown }) => {
beforeEach(() => {
wrapper = createComponent({ scope });
});
it(`does${showStateDropdown ? '' : ' not'} render when scope is ${scope}`, () => {
expect(findGlDropdown().exists()).toBe(showStateDropdown);
it(`does${showDropdown ? '' : ' not'} render when scope is ${scope}`, () => {
expect(findGlDropdown().exists()).toBe(showDropdown);
});
});
describe.each`
state | label
${FILTER_STATES.ANY.value} | ${FILTER_TEXT}
initialFilter | label
${FILTER_STATES.ANY.value} | ${`Any ${FILTER_HEADER}`}
${FILTER_STATES.OPEN.value} | ${FILTER_STATES.OPEN.label}
${FILTER_STATES.CLOSED.value} | ${FILTER_STATES.CLOSED.label}
${FILTER_STATES.MERGED.value} | ${FILTER_STATES.MERGED.label}
`(`filter text`, ({ state, label }) => {
describe(`when state is ${state}`, () => {
`(`filter text`, ({ initialFilter, label }) => {
describe(`when initialFilter is ${initialFilter}`, () => {
beforeEach(() => {
wrapper = createComponent({ scope: 'issues', state });
wrapper = createComponent({ scope: 'issues', initialFilter });
});
it(`sets dropdown label to ${label}`, () => {
......
......@@ -60,6 +60,28 @@ RSpec.describe 'search/_results' do
expect(rendered).to have_selector('#js-search-filter-by-state')
end
context 'Feature search_filter_by_confidential' do
context 'when disabled' do
before do
stub_feature_flags(search_filter_by_confidential: false)
end
it 'does not render the confidential drop down' do
render
expect(rendered).not_to have_selector('#js-search-filter-by-confidential')
end
end
context 'when enabled' do
it 'renders the confidential drop down' do
render
expect(rendered).to have_selector('#js-search-filter-by-confidential')
end
end
end
end
end
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