Commit a355b01a authored by Yannis Roussos's avatar Yannis Roussos

Merge branch 'negated_filter' into 'master'

Fix negated epic param for issues

See merge request gitlab-org/gitlab!44719
parents d9713afa b7a251fa
...@@ -102,7 +102,7 @@ class IssuableFinder ...@@ -102,7 +102,7 @@ class IssuableFinder
items = filter_items(items) items = filter_items(items)
# Let's see if we have to negate anything # Let's see if we have to negate anything
items = filter_negated_items(items) items = filter_negated_items(items) if should_filter_negated_args?
# This has to be last as we use a CTE as an optimization fence # This has to be last as we use a CTE as an optimization fence
# for counts by passing the force_cte param and passing the # for counts by passing the force_cte param and passing the
...@@ -134,13 +134,15 @@ class IssuableFinder ...@@ -134,13 +134,15 @@ class IssuableFinder
by_my_reaction_emoji(items) by_my_reaction_emoji(items)
end end
# Negates all params found in `negatable_params` def should_filter_negated_args?
def filter_negated_items(items) return false unless Feature.enabled?(:not_issuable_queries, params.group || params.project, default_enabled: true)
return items unless Feature.enabled?(:not_issuable_queries, params.group || params.project, default_enabled: true)
# API endpoints send in `nil` values so we test if there are any non-nil # API endpoints send in `nil` values so we test if there are any non-nil
return items unless not_params.present? && not_params.values.any? not_params.present? && not_params.values.any?
end
# Negates all params found in `negatable_params`
def filter_negated_items(items)
items = by_negated_author(items) items = by_negated_author(items)
items = by_negated_assignee(items) items = by_negated_assignee(items)
items = by_negated_label(items) items = by_negated_label(items)
......
...@@ -75,5 +75,18 @@ module EE ...@@ -75,5 +75,18 @@ module EE
items.in_iterations(params.iterations) items.in_iterations(params.iterations)
end end
end end
override :filter_negated_items
def filter_negated_items(items)
items = by_negated_epic(items)
super(items)
end
def by_negated_epic(items)
return items unless not_params[:epic_id].present?
items.not_in_epics(not_params[:epic_id].to_i)
end
end end
end end
...@@ -26,6 +26,7 @@ module EE ...@@ -26,6 +26,7 @@ module EE
scope :no_epic, -> { left_outer_joins(:epic_issue).where(epic_issues: { epic_id: nil }) } scope :no_epic, -> { left_outer_joins(:epic_issue).where(epic_issues: { epic_id: nil }) }
scope :any_epic, -> { joins(:epic_issue) } scope :any_epic, -> { joins(:epic_issue) }
scope :in_epics, ->(epics) { joins(:epic_issue).where(epic_issues: { epic_id: epics }) } scope :in_epics, ->(epics) { joins(:epic_issue).where(epic_issues: { epic_id: epics }) }
scope :not_in_epics, ->(epics) { left_outer_joins(:epic_issue).where('epic_issues.epic_id NOT IN (?) OR epic_issues.epic_id IS NULL', epics) }
scope :no_iteration, -> { where(sprint_id: nil) } scope :no_iteration, -> { where(sprint_id: nil) }
scope :any_iteration, -> { where.not(sprint_id: nil) } scope :any_iteration, -> { where.not(sprint_id: nil) }
scope :in_iterations, ->(iterations) { where(sprint_id: iterations) } scope :in_iterations, ->(iterations) { where(sprint_id: iterations) }
......
---
title: Fix issue filtering by negated epic parameter
merge_request: 44719
author:
type: fixed
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Filter issues by epic', :js do
include FilteredSearchHelpers
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:issue1) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project) }
let_it_be(:issue3) { create(:issue, project: project) }
let_it_be(:issue4) { create(:issue, project: project) }
let_it_be(:epic1) { create(:epic) }
let_it_be(:epic2) { create(:epic) }
let_it_be(:epic_issue1) { create(:epic_issue, issue: issue1, epic: epic1) }
let_it_be(:epic_issue2) { create(:epic_issue, issue: issue2, epic: epic2) }
let_it_be(:epic_issue3) { create(:epic_issue, issue: issue3, epic: epic2) }
let(:js_dropdown) { '#js-dropdown-epic' }
before do
stub_licensed_features(epics: true)
stub_feature_flags(vue_issuables_list: false)
project.add_developer(user)
sign_in(user)
visit project_issues_path(project)
end
it 'filter issues by epic' do
input_filtered_search("epic:=&#{epic1.id}")
expect_issues_list_count(1)
end
it 'filter issues not in the epic' do
input_filtered_search("epic:!=&#{epic1.id}")
expect_issues_list_count(3)
end
end
...@@ -121,6 +121,14 @@ RSpec.describe IssuesFinder do ...@@ -121,6 +121,14 @@ RSpec.describe IssuesFinder do
expect(issues).to contain_exactly(issue_1, issue_2, issue_subepic) expect(issues).to contain_exactly(issue_1, issue_2, issue_subepic)
end end
end end
context 'filter issues not in the epic' do
let(:params) { { not: { epic_id: epic_1.id } } }
it 'returns issues not assigned to the epic' do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue_2, issue_subepic)
end
end
end end
context 'filter by iteration' do context 'filter by iteration' do
......
...@@ -132,6 +132,13 @@ RSpec.describe Issue do ...@@ -132,6 +132,13 @@ RSpec.describe Issue do
end end
end end
describe '.not_in_epics' do
it 'returns only issues not in selected epics' do
expect(described_class.count).to eq 3
expect(described_class.not_in_epics([epic1])).to match_array([epic_issue2.issue, issue_no_epic])
end
end
describe '.distinct_epic_ids' do describe '.distinct_epic_ids' do
it 'returns distinct epic ids' do it 'returns distinct epic ids' do
expect(described_class.distinct_epic_ids.map(&:epic_id)).to match_array([epic1.id, epic2.id]) expect(described_class.distinct_epic_ids.map(&:epic_id)).to match_array([epic1.id, epic2.id])
......
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