Commit e9aa261b authored by Terri Chu's avatar Terri Chu Committed by Stan Hu

Allow search issues scope to filter by status

Add filter ability to basic and advanced search
issues scope. Results can be filtered by state
(any, opened, or closed). Default is any.
parent 21607734
import Search from './search';
import initStateFilter from '~/search/state_filter';
document.addEventListener('DOMContentLoaded', () => new Search());
document.addEventListener('DOMContentLoaded', () => {
initStateFilter();
return new Search();
});
<script>
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import { FILTER_STATES, FILTER_HEADER, FILTER_TEXT } from '../constants';
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
const FILTERS_ARRAY = Object.values(FILTER_STATES);
export default {
name: 'StateFilter',
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
},
props: {
scope: {
type: String,
required: true,
},
state: {
type: String,
required: false,
default: FILTER_STATES.ANY.value,
validator: v => FILTERS_ARRAY.some(({ value }) => value === v),
},
},
computed: {
selectedFilterText() {
let filterText = FILTER_TEXT;
if (this.selectedFilter === FILTER_STATES.CLOSED.value) {
filterText = FILTER_STATES.CLOSED.label;
} else if (this.selectedFilter === FILTER_STATES.OPEN.value) {
filterText = FILTER_STATES.OPEN.label;
}
return filterText;
},
selectedFilter: {
get() {
if (FILTERS_ARRAY.some(({ value }) => value === this.state)) {
return this.state;
}
return FILTER_STATES.ANY.value;
},
set(state) {
visitUrl(setUrlParams({ state }));
},
},
},
methods: {
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,
};
},
isFilterSelected(filter) {
return filter === this.selectedFilter;
},
handleFilterChange(state) {
this.selectedFilter = state;
},
},
filterStates: FILTER_STATES,
filterHeader: FILTER_HEADER,
filtersArray: FILTERS_ARRAY,
};
</script>
<template>
<gl-dropdown
v-if="scope === 'issues'"
:text="selectedFilterText"
class="col-sm-3 gl-pt-4 gl-pl-0"
>
<header class="gl-text-center gl-font-weight-bold gl-font-lg">
{{ $options.filterHeader }}
</header>
<gl-dropdown-divider />
<gl-dropdown-item
v-for="filter in $options.filtersArray"
:key="filter.value"
:is-check-item="true"
:is-checked="isFilterSelected(filter.value)"
:class="dropDownItemClass(filter)"
@click="handleFilterChange(filter.value)"
>
{{ filter.label }}
</gl-dropdown-item>
</gl-dropdown>
</template>
import { __ } from '~/locale';
export const FILTER_HEADER = __('Status');
export const FILTER_TEXT = __('Any Status');
export const FILTER_STATES = {
ANY: {
label: __('Any'),
value: 'all',
},
OPEN: {
label: __('Open'),
value: 'opened',
},
CLOSED: {
label: __('Closed'),
value: 'closed',
},
};
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import StateFilter from './components/state_filter.vue';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-search-filter-by-state');
if (!el) return false;
return new Vue({
el,
components: {
StateFilter,
},
data() {
const { dataset } = this.$options.el;
return {
scope: dataset.scope,
state: dataset.state,
};
},
render(createElement) {
return createElement('state-filter', {
props: {
scope: this.scope,
state: this.state,
},
});
},
});
};
......@@ -8,7 +8,7 @@
# current_user - which user use
# params:
# scope: 'created_by_me' or 'assigned_to_me' or 'all'
# state: 'open' or 'closed' or 'all'
# state: 'opened' or 'closed' or 'all'
# group_id: integer
# project_id: integer
# milestone_title: string
......
# frozen_string_literal: true
module SearchHelper
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets].freeze
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets, :state].freeze
def search_autocomplete_opts(term)
return unless current_user
......
......@@ -13,7 +13,8 @@ module Search
def execute
Gitlab::SearchResults.new(current_user,
params[:search],
projects)
projects,
filters: { state: params[:state] })
end
def projects
......
......@@ -15,7 +15,8 @@ module Search
current_user,
params[:search],
projects,
group: group
group: group,
filters: { state: params[:state] }
)
end
......
......@@ -12,7 +12,8 @@ module Search
Gitlab::ProjectSearchResults.new(current_user,
params[:search],
project: project,
repository_ref: params[:repository_ref])
repository_ref: params[:repository_ref],
filters: { state: params[:state] })
end
def scope
......
......@@ -22,6 +22,8 @@
= _("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] } }
.results.gl-mt-3
- if @scope == 'commits'
%ul.content-list.commit-list
......
---
title: Search UI Allow issue scope results filtering by state
merge_request: 39881
author:
type: changed
......@@ -15,7 +15,8 @@ module EE
current_user,
params[:search],
projects,
public_and_internal_projects: elastic_global
public_and_internal_projects: elastic_global,
filters: { state: params[:state] }
)
end
......
......@@ -24,7 +24,8 @@ module EE
params[:search],
projects,
group: group,
public_and_internal_projects: elastic_global
public_and_internal_projects: elastic_global,
filters: { state: params[:state] }
)
end
end
......
......@@ -14,7 +14,8 @@ module EE
current_user,
params[:search],
project: project,
repository_ref: repository_ref
repository_ref: repository_ref,
filters: { state: params[:state] }
)
end
......
......@@ -21,12 +21,25 @@ module Elastic
options[:features] = 'issues'
query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options)
query_hash = state_filter(query_hash, options)
search(query_hash, options)
end
private
def state_filter(query_hash, options)
state = options[:state]
return query_hash if state.blank? || state == 'all'
return query_hash unless %w(all opened closed).include?(state)
filter = { match: { state: state } }
query_hash[:query][:bool][:filter] << filter
query_hash
end
def confidentiality_filter(query_hash, options)
current_user = options[:current_user]
project_ids = options[:project_ids]
......
......@@ -6,7 +6,7 @@ module Elastic
def as_indexed_json(options = {})
data = {}
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# We don't use as_json(only: ...) because it calls all virtual and serialized attributes
# https://gitlab.com/gitlab-org/gitlab/issues/349
[:id, :iid, :title, :description, :created_at, :updated_at, :state, :project_id, :author_id, :confidential].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
......
......@@ -9,13 +9,14 @@ module Gitlab
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
attr_reader :group, :default_project_filter
attr_reader :group, :default_project_filter, :filters
def initialize(current_user, query, limit_projects = nil, group:, public_and_internal_projects: false, default_project_filter: false)
def initialize(current_user, query, limit_projects = nil, group:, public_and_internal_projects: false, default_project_filter: false, filters: {})
@group = group
@default_project_filter = default_project_filter
@filters = filters
super(current_user, query, limit_projects, public_and_internal_projects: public_and_internal_projects)
super(current_user, query, limit_projects, public_and_internal_projects: public_and_internal_projects, filters: filters)
end
def generic_search_results
......@@ -23,7 +24,8 @@ module Gitlab
current_user,
query,
limit_projects,
group: group
group: group,
filters: filters
)
end
end
......
......@@ -6,16 +6,16 @@ module Gitlab
# superclass inside a module, because autoloading can occur in a
# different order between execution environments.
class ProjectSearchResults < Gitlab::Elastic::SearchResults
attr_reader :project, :repository_ref
attr_reader :project, :repository_ref, :filters
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, project:, repository_ref: nil)
def initialize(current_user, query, project:, repository_ref: nil, filters: {})
@project = project
@repository_ref = repository_ref.presence || project.default_branch
super(current_user, query, [project], public_and_internal_projects: false)
super(current_user, query, [project], public_and_internal_projects: false, filters: filters)
end
def generic_search_results
......@@ -23,7 +23,8 @@ module Gitlab
current_user,
query,
project: project,
repository_ref: repository_ref
repository_ref: repository_ref,
filters: filters
)
end
......
......@@ -7,7 +7,7 @@ module Gitlab
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
attr_reader :current_user, :query, :public_and_internal_projects
attr_reader :current_user, :query, :public_and_internal_projects, :filters
# Limit search results by passed projects
# It allows us to search only for projects user has access to
......@@ -16,11 +16,12 @@ module Gitlab
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, limit_projects = nil, public_and_internal_projects: true)
def initialize(current_user, query, limit_projects = nil, public_and_internal_projects: true, filters: {})
@current_user = current_user
@query = query
@limit_projects = limit_projects
@public_and_internal_projects = public_and_internal_projects
@filters = filters
end
def objects(scope, page: 1, per_page: DEFAULT_PER_PAGE, preload_method: nil)
......@@ -221,7 +222,10 @@ module Gitlab
def issues
strong_memoize(:issues) do
Issue.elastic_search(query, options: base_options)
options = base_options
options[:state] = filters[:state] if filters.key?(:state)
Issue.elastic_search(query, options: options)
end
end
......
......@@ -2,17 +2,32 @@
require 'spec_helper'
RSpec.describe Gitlab::Elastic::GroupSearchResults do
RSpec.describe Gitlab::Elastic::GroupSearchResults, :elastic do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:guest) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::GUEST) } }
let(:filters) { {} }
let(:query) { '*' }
subject(:results) { described_class.new(user, query, group: group) }
subject(:results) { described_class.new(user, query, Project.all, group: group, filters: filters) }
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end
context 'issues search', :sidekiq_inline do
let!(:project) { create(:project, :public, group: group) }
let!(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let!(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:query) { 'foo' }
include_examples 'search issues scope filters by state' do
before do
ensure_elasticsearch_index!
end
end
end
context 'user search' do
let(:query) { guest.username }
......@@ -40,8 +55,6 @@ RSpec.describe Gitlab::Elastic::GroupSearchResults do
end
context 'query performance' do
let(:query) { '*' }
include_examples 'does not hit Elasticsearch twice for objects and counts', %w|projects notes blobs wiki_blobs commits issues merge_requests milestones|
end
end
......@@ -3,12 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
let(:query) { 'hello world' }
let(:repository_ref) { nil }
let(:filters) { {} }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref) }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref, filters: filters) }
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
......@@ -30,9 +31,9 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
it { expect(results.query).to eq('hello world') }
end
describe "search", :sidekiq_might_not_need_inline do
let(:project) { create(:project, :public, :repository, :wiki_repo) }
let(:private_project) { create(:project, :repository, :wiki_repo) }
describe "search", :sidekiq_inline do
let_it_be(:project) { create(:project, :public, :repository, :wiki_repo) }
let_it_be(:private_project) { create(:project, :repository, :wiki_repo) }
before do
[project, private_project].each do |project|
......@@ -56,7 +57,7 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
end
context 'visibility checks' do
let(:project) { create(:project, :public, :wiki_repo) }
let_it_be(:project) { create(:project, :public, :wiki_repo) }
let(:query) { 'term' }
before do
......@@ -67,6 +68,19 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
expect(results.wiki_blobs_count).to eq(1)
end
end
context 'filtering' do
include_examples 'search issues scope filters by state' do
let!(:project) { create(:project, :public) }
let!(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let!(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:query) { 'foo' }
before do
ensure_elasticsearch_index!
end
end
end
end
describe "search for commits in non-default branch" do
......@@ -147,13 +161,14 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
it { expect(results.limited_users_count).to eq(1) }
describe 'pagination' do
let(:query) {}
let(:query) { }
let!(:user2) { create(:user).tap { |u| project.add_user(u, Gitlab::Access::REPORTER) } }
let_it_be(:user2) { create(:user).tap { |u| project.add_user(u, Gitlab::Access::REPORTER) } }
it 'returns the correct page of results' do
expect(results.objects('users', page: 1, per_page: 1)).to eq([project.owner])
expect(results.objects('users', page: 2, per_page: 1)).to eq([user2])
# UsersFinder defaults to order_id_desc, the newer result will be first
expect(results.objects('users', page: 1, per_page: 1)).to eq([user2])
expect(results.objects('users', page: 2, per_page: 1)).to eq([project.owner])
end
it 'returns the correct number of results for one page' do
......
......@@ -171,6 +171,20 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
expect(results.objects('issues')).to be_empty
expect(results.issues_count).to eq 0
end
context 'filtering' do
let!(:project) { create(:project, :public) }
let!(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let!(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:results) { described_class.new(user, 'foo', [project], filters: filters) }
include_examples 'search issues scope filters by state' do
before do
ensure_elasticsearch_index!
end
end
end
end
describe 'notes' do
......
......@@ -4,10 +4,10 @@ module Gitlab
class GroupSearchResults < SearchResults
attr_reader :group
def initialize(current_user, query, limit_projects = nil, group:, default_project_filter: false)
def initialize(current_user, query, limit_projects = nil, group:, default_project_filter: false, filters: {})
@group = group
super(current_user, query, limit_projects, default_project_filter: default_project_filter)
super(current_user, query, limit_projects, default_project_filter: default_project_filter, filters: filters)
end
# rubocop:disable CodeReuse/ActiveRecord
......
......@@ -4,11 +4,11 @@ module Gitlab
class ProjectSearchResults < SearchResults
attr_reader :project, :repository_ref
def initialize(current_user, query, project:, repository_ref: nil)
def initialize(current_user, query, project:, repository_ref: nil, filters: {})
@project = project
@repository_ref = repository_ref.presence
super(current_user, query, [project])
super(current_user, query, [project], filters: filters)
end
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, preload_method: nil)
......
......@@ -7,7 +7,7 @@ module Gitlab
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 20
attr_reader :current_user, :query
attr_reader :current_user, :query, :filters
# Limit search results by passed projects
# It allows us to search only for projects user has access to
......@@ -19,11 +19,12 @@ module Gitlab
# query
attr_reader :default_project_filter
def initialize(current_user, query, limit_projects = nil, default_project_filter: false)
def initialize(current_user, query, limit_projects = nil, default_project_filter: false, filters: {})
@current_user = current_user
@query = query
@limit_projects = limit_projects || Project.all
@default_project_filter = default_project_filter
@filters = filters
end
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, without_count: true, preload_method: nil)
......@@ -190,6 +191,8 @@ module Gitlab
else
params[:search] = query
end
params[:state] = filters[:state] if filters.key?(:state)
end
end
......
......@@ -2957,6 +2957,9 @@ msgstr ""
msgid "Any Author"
msgstr ""
msgid "Any Status"
msgstr ""
msgid "Any branch"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import StateFilter from '~/search/state_filter/components/state_filter.vue';
import { FILTER_STATES } from '~/search/state_filter/constants';
import * as urlUtils from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
setUrlParams: jest.fn(),
}));
function createComponent(props = { scope: 'issues' }) {
return shallowMount(StateFilter, {
propsData: {
...props,
},
});
}
describe('StateFilter', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findGlDropdown = () => wrapper.find(GlDropdown);
const findGlDropdownItems = () => findGlDropdown().findAll(GlDropdownItem);
const findDropdownItemsText = () => findGlDropdownItems().wrappers.map(w => w.text());
const firstDropDownItem = () => findGlDropdownItems().at(0);
describe('template', () => {
describe.each`
scope | showStateDropdown
${'issues'} | ${true}
${'projects'} | ${false}
${'milestones'} | ${false}
${'users'} | ${false}
${'merge_requests'} | ${false}
${'notes'} | ${false}
${'wiki_blobs'} | ${false}
${'blobs'} | ${false}
`(`state dropdown`, ({ scope, showStateDropdown }) => {
beforeEach(() => {
wrapper = createComponent({ scope });
});
it(`does${showStateDropdown ? '' : ' not'} render when scope is ${scope}`, () => {
expect(findGlDropdown().exists()).toBe(showStateDropdown);
});
});
describe('Filter options', () => {
it('renders a dropdown item for each filterOption', () => {
expect(findDropdownItemsText()).toStrictEqual(
Object.keys(FILTER_STATES).map(key => {
return FILTER_STATES[key].label;
}),
);
});
it('clicking a dropdown item calls setUrlParams', () => {
const state = FILTER_STATES[Object.keys(FILTER_STATES)[0]].value;
firstDropDownItem().vm.$emit('click');
expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ state });
});
it('clicking a dropdown item calls visitUrl', () => {
firstDropDownItem().vm.$emit('click');
expect(urlUtils.visitUrl).toHaveBeenCalled();
});
});
});
});
......@@ -6,9 +6,22 @@ RSpec.describe Gitlab::GroupSearchResults do
# group creation calls GroupFinder, so need to create the group
# before so expect(GroupsFinder) check works
let_it_be(:group) { create(:group) }
let(:user) { create(:user) }
let_it_be(:user) { create(:user) }
let(:filters) { {} }
let(:limit_projects) { Project.all }
let(:query) { 'gob' }
subject(:results) { described_class.new(user, 'gob', anything, group: group) }
subject(:results) { described_class.new(user, query, limit_projects, group: group, filters: filters) }
describe 'issues search' do
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let_it_be(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let(:query) { 'foo' }
let(:filters) { { state: 'opened' } }
include_examples 'search issues scope filters by state'
end
describe 'user search' do
subject(:objects) { results.objects('users') }
......
......@@ -5,12 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::ProjectSearchResults do
include SearchHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:query) { 'hello world' }
let(:repository_ref) { nil }
let(:filters) { {} }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref) }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref, filters: filters) }
context 'with a repository_ref' do
context 'when empty' do
......@@ -258,6 +259,24 @@ RSpec.describe Gitlab::ProjectSearchResults do
describe "confidential issues" do
include_examples "access restricted confidential issues"
end
context 'filtering' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let_it_be(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:query) { 'foo' }
include_examples 'search issues scope filters by state'
end
it 'filters issues when state is provided', :aggregate_failures do
closed_issue = create(:issue, :closed, project: project, title: "Revert: #{issue.title}")
results = described_class.new(project.creator, query, project: project, filters: { state: 'opened' })
expect(results.objects('issues')).not_to include closed_issue
expect(results.objects('issues')).to include issue
end
end
describe 'notes search' do
......
......@@ -6,13 +6,14 @@ RSpec.describe Gitlab::SearchResults do
include ProjectForksHelper
include SearchHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, name: 'foo') }
let!(:issue) { create(:issue, project: project, title: 'foo') }
let!(:merge_request) { create(:merge_request, source_project: project, title: 'foo') }
let!(:milestone) { create(:milestone, project: project, title: 'foo') }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, name: 'foo') }
let_it_be(:issue) { create(:issue, project: project, title: 'foo') }
let_it_be(:milestone) { create(:milestone, project: project, title: 'foo') }
let(:merge_request) { create(:merge_request, source_project: project, title: 'foo') }
let(:filters) { {} }
subject(:results) { described_class.new(user, 'foo', Project.all) }
subject(:results) { described_class.new(user, 'foo', Project.all, filters: filters) }
context 'as a user with access' do
before do
......@@ -105,10 +106,10 @@ RSpec.describe Gitlab::SearchResults do
describe '#limited_issues_count' do
it 'runs single SQL query to get the limited amount of issues' do
create(:milestone, project: project, title: 'foo2')
create(:issue, project: project, title: 'foo2')
expect(results).to receive(:issues).with(public_only: true).and_call_original
expect(results).not_to receive(:issues).with(no_args).and_call_original
expect(results).not_to receive(:issues).with(no_args)
expect(results.limited_issues_count).to eq(1)
end
......@@ -165,6 +166,13 @@ RSpec.describe Gitlab::SearchResults do
results.objects('issues')
end
context 'filtering' do
let_it_be(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let_it_be(:opened_issue) { create(:issue, :opened, project: project, title: 'foo open') }
include_examples 'search issues scope filters by state'
end
end
describe '#users' do
......
# frozen_string_literal: true
RSpec.shared_examples 'search issues scope filters by state' do
context 'state not provided' do
let(:filters) { {} }
it 'returns opened and closed issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
context 'all state' do
let(:filters) { { state: 'all' } }
it 'returns opened and closed issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
context 'closed state' do
let(:filters) { { state: 'closed' } }
it 'returns only closed issues', :aggregate_failures do
expect(results.objects('issues')).not_to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
context 'opened state' do
let(:filters) { { state: 'opened' } }
it 'returns only opened issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).not_to include closed_issue
end
end
context 'unsupported state' do
let(:filters) { { state: 'hello' } }
it 'returns only opened issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
end
......@@ -54,6 +54,12 @@ RSpec.describe 'search/_results' do
expect(rendered).to have_selector('[data-track-event=click_text]')
expect(rendered).to have_selector('[data-track-property=search_result]')
end
it 'renders the state filter drop down' do
render
expect(rendered).to have_selector('#js-search-filter-by-state')
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